COW技術初窺:
在Linux程序中,fork()會產生一個和父進程徹底相同的子進程,但子進程在此後多會exec系統調用,出於效率考慮,linux中引入了「寫時複製「技術,也就是隻有進程空間的
各段的內容要發生變化時,纔會將父進程的內容複製一份給子進程。
那麼子進程的物理空間沒有代碼,怎麼去取指令執行exec系統調用呢?
在fork以後
exec以前兩個進程用的是
相同的物理空間(內存區),子進程的代碼段、數據段、堆棧都是指向父進程的物理空間,也就是說,二者的
虛擬空間不一樣,但其對應的
物理空間是同一個。當父子進程中有
更改相應段的行爲發生時,再
爲子進程相應的段分配物理空間,若是不是由於exec,內核會給
子進程的數據段、堆棧段分配相應的物理空間(至此二者有各自的進程空間,互不影響),而代碼段繼續共享父進程的物理空間(二者的代碼徹底相同)。而若是是由於exec,因爲二者執行的代碼不一樣,
子進程的代碼段也會分配單獨的物理空間。
在網上看到還有個
細節問題就是,fork以後內核會經過將子進程放在隊列的前面,以讓子進程先執行,以避免父進程執行致使寫時複製,然後子進程執行exec系統調用,因無心義的複製而形成效率的降低。
COW詳述:
如今有一個父進程P1,這是一個主體,那麼它是有靈魂也就身體的。如今在其虛擬地址空間(有相應的數據結構表示)上有:正文段,數據段,堆,棧這四個部分,相應的,內核要爲這四個部分分配各自的物理塊。即:正文段塊,數據段塊,堆塊,棧塊。至於如何分配,這是內核去作的事,在此不詳述。
1. 如今P1用fork()函數爲進程建立一個子進程P2,
內核:
(1)複製P1的正文段,數據段,堆,棧這四個部分,注意是其內容相同。
(2)爲這四個部分分配物理塊,P2的:正文段->PI的正文段的物理塊,
其實就是不爲P2分配正文段塊,讓P2的正文段指向P1的正文段塊,數據段->P2本身的數據段塊(爲其分配對應的塊),堆->P2本身的堆塊,棧->P2本身的棧塊。以下圖所示:同左到右大的方向箭頭表示複製內容。
2. 寫時複製技術:
內核只爲新生成的子進程建立虛擬空間結構,它們來複制於父進程的虛擬究竟結構,可是不爲這些段分配物理內存,它們共享父進程的物理空間,當父子進程中有更改相應段的行爲發生時,再爲子進程相應的段分配物理空間。
3. vfork():這個作法更加火爆,內核連子進程的虛擬地址空間結構也不建立了,直接共享了父進程的虛擬空間,固然了,這種作法就順水推舟的共享了父進程的物理空間
經過以上的分析,相信你們對進程有個深刻的認識,它是怎麼一層層體現出本身來的,進程是一個主體,那麼它就有靈魂與身體,系統必須爲實現它建立相應的實體, 靈魂實體與物理實體。這二者在系統中都有相應的數據結構表示,物理實體更是體現了它的物理意義。如下援引LKD
傳統的fork()系統調用直接把全部的資源複製給新建立的進程。這種實現過於簡單而且效率低下,由於它拷貝的數據也許並不共享,更糟的狀況是,若是新進程打算當即執行一個新的映像,那麼全部的拷貝都將前功盡棄。Linux的fork()使用寫時拷貝(copy-on-write)頁實現。寫時拷貝是一種能夠推遲甚至免除拷貝數據的技術。內核此時並不複製整個進程地址空間,而是讓父進程和子進程共享同一個拷貝。只有在須要寫入的時候,數據纔會被複制,從而使各個進程擁有各自的拷貝。也就是說,
資源的複製只有在須要寫入的時候才進行,在此以前,只是以只讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候。在
頁根本不會被寫入的狀況下—舉例來講,fork()後當即調用exec()—它們就無需複製了。fork()的實際開銷就是複製父進程的頁表以及給子進程建立唯一的進程描述符。在通常狀況下,進程建立後都會立刻運行一個可執行的文件,這種優化能夠避免拷貝大量根本就不會被使用的數據(地址空間裏經常包含數十兆的數據)。因爲Unix強調進程快速執行的能力,因此這個優化是很重要的。這裏補充一點:
Linux COW與exec沒有必然聯繫
PS:實際上COW技術不只僅在Linux進程上有應用,其餘例如C++的String在有的IDE環境下也支持COW技術,即例如:
string str1 = "hello world";
string str2 = str1;
以後執行代碼:
str1[1]='q';
str2[1]='w';
在開始的兩個語句後,
str1
和
str2
存放數據的地址是同樣的,而在修改內容後,
str1
的地址發生了變化,而
str2
的地址仍是原來的,這就是C++中的COW技術的應用,不過VS2005彷佛已經不支持COW。