Q34:fork與vfork

fork

創建一個和當前進程映像一樣的集成可以通過fork()系統調用:

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

成功調用fork()會常見一個新的進程,它幾乎與調用fork()的集成一模一樣,這兩個進程都會繼續運行。
在子進程中,成功的fork()調用會返回0,在父進程中fork()會返回子進程的pid。
如果出現錯誤,fork()返回一個負值。

最常見的fork()用法,是創建一個新的進程,然後使用exec()載入二進制映像,替換當前的映像。
在這種情況下,派生(fork)了新的進程,而這個子進程會執行一個新的二進制可執行文件的映像。這種“派生加執行”的方式是很常見的。

在早期的Unix系統中,創建進程比較原始。當調用fork時,內核會把所有的內部數據結構複製一份,複製進程的頁表項,然後把父進程的地址空間中的內容逐頁的複製到子進程的地址空間中。
但從內核角度來說,逐頁的複製方式是十分耗時的。現代的Unix系統採取了更多的優化,例如Linux,採用了寫時複製的方法,而不是對父進程空間進程整體複製。

vfork

在實現寫時複製之前,Unix的設計者就一直非常關注fork之後立即執行exec所造成的地址空間的浪費。BSD的開發者在之後引入了vfork()系統調用。

#Include <sys/types.h>
#include <unistd.h>

pid_t vfork(void);

除了子進程必須要立即執行一次對exec的系統調用,或者調用_exit()退出,對vfork()的成功調用所產生的結果和fork()是一樣的。
vfork( )會掛起父進程直到子進程終止或者運行了一個新的可執行文件的映像。通過這樣的方式,vfork( )避免了地址空間的按頁複製。在這個過程中,父進程和子進程共享相同的地址空間和頁表項。實際上vfork( )只完成了一件事:複製內部的內核數據結構。 因此,子進程也就不能修改地址空間中的任何內存。

vfork( )是一個歷史遺留產物,Linux本不應該實現它。需要注意的是,即使增加了寫時複製,vfork( )也要比fork( )快,因爲它沒有進行頁表項的複製。然而,寫時複製的出現減少了對於替換fork( )爭論。實際上,直到2.2.0內核,vfork( )只是一個封裝過的fork( )。因爲對vfork( )的需求要小於fork( ),所以vfork( )的這種實現方式是可行的。

fork與vfork的區別

  1. fork()的子進程拷貝父進程的數據段和代碼段;vfork()的子進程和父進程共享數據段。
  2. fork()的父子進程執行次序不確定;vfork()保證子進程先運行,在調用exec或者exit之前與父進程數據是共享的,當其調用了exec或者exit之後父進程纔可能被調度運行。
  3. vfork()保證子進程先運行,在它調用exec或者exit之後,父進程纔可能被調度運行。如果在調用這兩個函數之前子進程依賴於父進程的進一步動作,則會導致死鎖。
  4. 當需要改變共享數據段中變量的值,則拷貝父進程。

寫時複製

Linux採用了寫時複製的方法,以減少fork時對父進程空間進程整體複製帶來的開銷。

寫時複製是一種採取了惰性優化方法來避免複製時的系統開銷。它的前提很簡單:
如果有多個進程要讀取它們自己的那部分資源的副本,那麼複製是不必要的。每個進程只要保存一個指向這個資源的指針就可以了。只要沒有進程要去修改自己的“副本”,就存在着這樣的幻覺:每個進程好像獨佔那個資源。從而就避免了複製帶來的負擔。如果一個進程要修改自己的那份資源“副本”,那麼就會複製那份資源,並把複製的那份提供給進程。不過其中的複製對進程來說是透明的。這個進程就可以修改複製後的資源了,同時其他的進程仍然共享那份沒有修改過的資源。所以這就是名稱的由來:在寫入時進行復制

寫時複製的主要好處在於:
如果進程從來就不需要修改資源,則不需要進行復制。惰性算法的好處就在於它們儘量推遲代價高昂的操作,直到必要的時刻纔會去執行。

在使用虛擬內存的情況下,寫時複製(Copy-On-Write)是以頁爲基礎進行的。所以,只要進程不修改它全部的地址空間,那麼就不必複製整個地址空間。在fork()調用結束後,父進程和子進程都相信它們有一個自己的地址空間,但實際上它們共享父進程的原始頁,接下來這些頁又可以被其他的父進程或子進程共享。

寫時複製在內核中的實現非常簡單。與內核頁相關的數據結構可以被標記爲只讀和寫時複製。如果有進程試圖修改一個頁,就會產生一個缺頁中斷。內核處理缺頁中斷的方式就是對該頁進行一次透明覆制。這時會清除頁面的COW屬性,表示着它不再被共享。

現代的計算機系統結構中都在內存管理單元(MMU)提供了硬件級別的寫時複製支持,所以實現是很容易的。

在調用fork()時,寫時複製是有很大優勢的。因爲大量的fork之後都會跟着執行exec,那麼複製整個父進程地址空間中的內容到子進程的地址空間完全是在浪費時間:如果子進程立刻執行一個新的二進制可執行文件的映像,它先前的地址空間就會被交換出去。寫時複製可以對這種情況進行優化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章