先看一個fork的例子:html
int glob = 4; int main(void) { int var, pid; var = 88; if ((pid = fork()) < 0) { printf("vfork error"); exit(-1); } else if (pid == 0) { /* 子進程 */ var++; glob++; exit(0); } printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var); return 0; }
運行結果:c++
[root@localhost tmp]# ./a.out pid=15297, glob=4, var=88
可見,子進程修改的局部變量var和全局變量glob後,父進程是不可見的。shell
若是把代碼中的fork替換成vfork,再次運行,獲得的結果:函數
[root@localhost tmp]# ./a.out pid=15304, glob=5, var=89
可見,父進程共享了子進程的修改操做。性能
在使用vfork時,若是子進程使用return語句結束,會發生什麼呢?spa
int glob = 4; int main(void) { int var, pid; var = 88; if ((pid = vfork()) < 0) { printf("vfork error"); exit(-1); } else if (pid == 0) { /* 子進程 */ var++; glob++; return 0; } printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var); return 0; }
在個人機器上,致使了無限循環(直到vfork調用出錯),這是由於子進程調用return語句破壞了父進程的棧。設計
fork與vfork的區別:code
咱們知道,fork一般採用寫時複製技術(copy-on-write, COW)建立子進程,以提升進程clone的性能;但在更早尚未COW的年代,fork建立子進程時時須要完整的複製父進程地址空間到子進程中,若是咱們建立子進程的目的是爲了調用exec,那麼這種複製就顯得既低效又無必要。而vfork讓子進程共享父進程的地址空間,而不做克隆操做,就是爲了節省這種沒必要要的複製開銷。htm
回到上面return致使程序crash的例子,return會釋放局部變量,並彈棧,回到上級函數執行。exit直接退掉。若是你用c++ 你就知道,return會調用局部對象的析構函數,exit不會。(注:exit不是系統調用,是glibc對系統調用 _exit()或_exitgroup()的封裝)對象
可見,子進程調用exit() 沒有修改函數棧,因此,父進程得以順利執行。而子進程調用return,至關於在父進程的棧上執行了彈棧操做,父進程也就跪了。
注意:
一、vfork保證子進程先運行,在它調用exec或exit以後父進程纔可能被調度運行;
二、子進程在調用exec或exit以前是在父進程的地址空間中運行的。
可見,vfork的設計初衷是爲了應對那些子進程須要立刻調用exec的場景,所以不對父進程的地址空間作任何複製。
再看一個fork的有趣例子,
int main(void) { int i, pid = 0; for (i = 0; i < 2; i++) { pid = fork(); if (pid == 0) { printf("pid:%d\n", getpid()); } } return 0; }
問題是,執行這段代碼,一共產生了幾個進程呢?
從執行結果來看,printf函數打印了3次,fork被調用了3次,連上main進程一共有4個進程。
再看下面這個例子,一共打印了多少個 「_」 呢?
int main(void) { int i; for(i=0; i<2; i++){ fork(); printf("-"); } return 0; }
按照上面的例子,程序運行過程當中一共有4個進程,把main進程記爲A,則有
i=0時,A進程 fork調用,產生子進程B1,而後A、B1各打印一個"_";
i=1時,A進程 fork調用,產生子進程B2,而後A、B2各打印一個"_";
與此同時,B1進程fork調用,產生子進程C1,而後B一、C1各打印一個"_";
看起來,好像有6個"_"被打印了,但這段代碼的執行結果倒是8個,這是爲啥呢?
先來看下,這4個進程間的關係以下:
A --> B1 --> C1
|--> B2
可見,B一、B2繼承自A,而C1繼承自B1。
一、B1是在i=0時複製A的,此時A尚未調用過printf函數;
二、B2是在i=1時複製A的,此時A已經調用過一次printf函數;
三、C1是在i=1時複製B1的,此時B1已經調用過一次printf函數;
咱們知道,fork進程會讓子進程完整複製父進程的地址空間,這也就包括了I/O緩衝區,這就是爲何最終打印了8個"_"的緣由。
參考文檔: