一、fork入門知識
一個進程,包括代碼、數據和分配給進程的資源。fork()函數通過系統調用創建一個與原來進程幾乎完全相同的進程,也就是兩個進程可以做完全相同的事,但如果初始參數或者傳入的變量不同,兩個進程也可以做不同的事。
??? 一個進程調用fork()函數后,系統先給新的進程分配資源,例如存儲數據和代碼的空間。然后把原來的進程的所有值都復制到新的新進程中,只有少數值與原來的進程的值不同。相當于克隆了一個自己。
我們來看一個例子:
[cpp]?view plain?copy
/*?
*??fork_test.c?
*??version?1?
*??Created?on:?2010-5-29?
*??????Author:?wangth?
*/??
#include???
#include????
int?main?()???
{???
pid_t?fpid;?//fpid表示fork函數返回的值??
int?count=0;??
fpid=fork();???
if?(fpid?0)???
printf("error?in?fork!");???
else?if?(fpid?==?0)?{??
printf("i?am?the?child?process,?my?process?id?is?%d/n",getpid());???
printf("我是爹的兒子/n");//對某些人來說中文看著更直白。??
count++;??
}??
else?{??
printf("i?am?the?parent?process,?my?process?id?is?%d/n",getpid());???
printf("我是孩子他爹/n");??
count++;??
}??
printf("統計結果是:?%d/n",count);??
return?0;??
}??
運行結果是:
????i am the child process, my process id is 5574
??? 我是爹的兒子
??? 統計結果是: 1
??? i am the parent process, my process id is 5573
??? 我是孩子他爹
??? 統計結果是: 1
??? 在語句fpid=fork()之前,只有一個進程在執行這段代碼,但在這條語句之后,就變成兩個進程在執行了,這兩個進程的幾乎完全相同,將要執行的下一條語句都是if(fpid<0)……
??? 為什么兩個進程的fpid不同呢,這與fork函數的特性有關。fork調用的一個奇妙之處就是它僅僅被調用一次,卻能夠返回兩次,它可能有三種不同的返回值:
??? 1)在父進程中,fork返回新創建子進程的進程ID;
??? 2)在子進程中,fork返回0;
??? 3)如果出現錯誤,fork返回一個負值;
在fork函數執行完畢后,如果創建新進程成功,則出現兩個進程,一個是子進程,一個是父進程。在子進程中,fork函數返回0,在父進程中,fork返回新創建子進程的進程ID。我們可以通過fork返回的值來判斷當前進程是子進程還是父進程。
引用一位網友的話來解釋fpid的值為什么在父子進程中不同。“其實就相當于鏈表,進程形成了鏈表,父進程的fpid(p 意味point)指向子進程的進程id, 因為子進程沒有子進程,所以其fpid為0.
??? fork出錯可能有兩種原因:
??? 1)當前的進程數已經達到了系統規定的上限,這時errno的值被設置為EAGAIN。
??? 2)系統內存不足,這時errno的值被設置為ENOMEM。
??? 創建新進程成功后,系統中出現兩個基本完全相同的進程,這兩個進程執行沒有固定的先后順序,哪個進程先執行要看系統的進程調度策略。
??? 每個進程都有一個獨特(互不相同)的進程標識符(process ID),可以通過getpid()函數獲得,還有一個記錄父進程pid的變量,可以通過getppid()函數獲得變量的值。
????fork執行完畢后,出現兩個進程,
有人說兩個進程的內容完全一樣啊,怎么打印的結果不一樣啊,那是因為判斷條件的原因,上面列舉的只是進程的代碼和指令,還有變量啊。
??? 執行完fork后,進程1的變量為count=0,fpid!=0(父進程)。進程2的變量為count=0,fpid=0(子進程),這兩個進程的變量都是獨立的,存在不同的地址中,不是共用的,這點要注意。可以說,我們就是通過fpid來識別和操作父子進程的。
??? 還有人可能疑惑為什么不是從#include處開始復制代碼的,這是因為fork是把進程當前的情況拷貝一份,執行fork時,進程已經執行完了int count=0;fork只拷貝下一個要執行的代碼到新的進程。
二、fork進階知識
先看一份代碼:
[cpp]?view plain?copy
/*?
*??fork_test.c?
*??version?2?
*??Created?on:?2010-5-29?
*??????Author:?wangth?
*/??
#include???
#include???
int?main(void)??
{??
int?i=0;??
printf("i?son/pa?ppid?pid??fpid/n");??
//ppid指當前進程的父進程pid??
//pid指當前進程的pid,??
//fpid指fork返回給當前進程的值??
for(i=0;i<2;i++){??
pid_t?fpid=fork();??
if(fpid==0)??
printf("%d?child??%4d?%4d?%4d/n",i,getppid(),getpid(),fpid);??
else??
printf("%d?parent?%4d?%4d?%4d/n",i,getppid(),getpid(),fpid);??
}??
return?0;??
}??
運行結果是:
????i son/pa ppid pid? fpid
??? 0 parent 2043 3224 3225
??? 0 child? 3224 3225??? 0
??? 1 parent 2043 3224 3226
??? 1 parent 3224 3225 3227
??? 1 child???? 1 3227??? 0
??? 1 child???? 1 3226??? 0?
??? 這份代碼比較有意思,我們來認真分析一下:
????第一步:在父進程中,指令執行到for循環中,i=0,接著執行fork,fork執行完后,系統中出現兩個進程,分別是p3224和p3225(后面我都用pxxxx表示進程id為xxxx的進程)。可以看到父進程p3224的父進程是p2043,子進程p3225的父進程正好是p3224。我們用一個鏈表來表示這個關系:
????p2043->p3224->p3225?
??? 第一次fork后,p3224(父進程)的變量為i=0,fpid=3225(fork函數在父進程中返向子進程id),代碼內容為:
[c-sharp]?view plain?copy
for(i=0;i<2;i++){??
pid_t?fpid=fork();//執行完畢,i=0,fpid=3225??
if(fpid==0)??
printf("%d?child??%4d?%4d?%4d/n",i,getppid(),getpid(),fpid);??
else??
printf("%d?parent?%4d?%4d?%4d/n",i,getppid(),getpid(),fpid);??
}??
return?0;??
p3225(子進程)的變量為i=0,fpid=0(fork函數在子進程中返回0),代碼內容為:
[c-sharp]?view plain?copy
for(i=0;i<2;i++){??
pid_t?fpid=fork();//執行完畢,i=0,fpid=0??
if(fpid==0)??
printf("%d?child??%4d?%4d?%4d/n",i,getppid(),getpid(),fpid);??
else??
printf("%d?parent?%4d?%4d?%4d/n",i,getppid(),getpid(),fpid);??
}??
return?0;??
所以打印出結果:
????0?parent 2043 3224 3225
??? 0 child? 3224 3225??? 0
????第二步:假設父進程p3224先執行,當進入下一個循環時,i=1,接著執行fork,系統中又新增一個進程p3226,對于此時的父進程,p2043->p3224(當前進程)->p3226(被創建的子進程)。
??? 對于子進程p3225,執行完第一次循環后,i=1,接著執行fork,系統中新增一個進程p3227,對于此進程,p3224->p3225(當前進程)->p3227(被創建的子進程)。從輸出可以看到p3225原來是p3224的子進程,現在變成p3227的父進程。父子是相對的,這個大家應該容易理解。只要當前進程執行了fork,該進程就變成了父進程了,就打印出了parent。
??? 所以打印出結果是:
????1 parent 2043 3224 3226
??? 1 parent 3224 3225 3227?
????第三步:第二步創建了兩個進程p3226,p3227,這兩個進程執行完printf函數后就結束了,因為這兩個進程無法進入第三次循環,無法fork,該執行return 0;了,其他進程也是如此。
??? 以下是p3226,p3227打印出的結果:
????1 child???? 1 3227??? 0
??? 1 child???? 1 3226??? 0?
??? 細心的讀者可能注意到p3226,p3227的父進程難道不該是p3224和p3225嗎,怎么會是1呢?這里得講到進程的創建和死亡的過程,在p3224和p3225執行完第二個循環后,main函數就該退出了,也即進程該死亡了,因為它已經做完所有事情了。p3224和p3225死亡后,p3226,p3227就沒有父進程了,這在操作系統是不被允許的,所以p3226,p3227的父進程就被置為p1了,p1是永遠不會死亡的,至于為什么,這里先不介紹,留到“三、fork高階知識”講。
??? 總結一下,這個程序執行的流程如下:
這個程序最終產生了3個子進程,執行過6次printf()函數。
??? 我們再來看一份代碼:
[cpp]?view plain?copy
/*?
*??fork_test.c?
*??version?3?
*??Created?on:?2010-5-29?
*??????Author:?wangth?
*/??
#include???
#include???
int?main(void)??
{??
int?i=0;??
for(i=0;i<3;i++){??
pid_t?fpid=fork();??
if(fpid==0)??
printf("son/n");??
else??
printf("father/n");??
}??
return?0;??
}??
它的執行結果是:
????father
??? son
??? father
??? father
??? father
??? father
??? son
??? son
??? father
??? son
??? son
??? son
??? father
??? son?
??? 這里就不做詳細解釋了,只做一個大概的分析。
??? for??????? i=0???????? 1?????????? 2
????????????? father???? father???? father
??????????????????????????????????????? son
?????????????????????????? ?son?????? father
?????????????????????????????????????? ?son
?????????????? son?????? father???? father
?????????????????????????????????????? ?son
?????????????????????????? ?son?????? father
??????????????????????????????????????? son
??? 其中每一行分別代表一個進程的運行打印結果。
??? 總結一下規律,對于這種N次循環的情況,執行printf函數的次數為2*(1+2+4+……+2N-1)次,創建的子進程數為1+2+4+……+2N-1個。(感謝gao_jiawei網友指出的錯誤,原本我的結論是“執行printf函數的次數為2*(1+2+4+……+2N)次,創建的子進程數為1+2+4+……+2N?”,這是錯的)
????網上有人說N次循環產生2*(1+2+4+……+2N)個進程,這個說法是不對的,希望大家需要注意。
數學推理見http://202.117.3.13/wordpress/?p=81(該博文的最后)。
??? 同時,大家如果想測一下一個程序中到底創建了幾個子進程,最好的方法就是調用printf函數打印該進程的pid,也即調用printf("%d/n",getpid());或者通過printf("+/n");來判斷產生了幾個進程。有人想通過調用printf("+");來統計創建了幾個進程,這是不妥當的。具體原因我來分析。
??? 老規矩,大家看一下下面的代碼:
[cpp]?view plain?copy
/*?
*??fork_test.c?
*??version?4?
*??Created?on:?2010-5-29?
*??????Author:?wangth?
*/??
#include???
#include???
int?main()?{??
pid_t?fpid;//fpid表示fork函數返回的值??
//printf("fork!");??
printf("fork!/n");??
fpid?=?fork();??
if?(fpid?0)??
printf("error?in?fork!");??
else?if?(fpid?==?0)??
printf("I?am?the?child?process,?my?process?id?is?%d/n",?getpid());??
else??
printf("I?am?the?parent?process,?my?process?id?is?%d/n",?getpid());??
return?0;??
}??
執行結果如下:
????fork!
??? I am the parent process, my process id is 3361
??? I am the child process, my process id is 3362?
????如果把語句printf("fork!/n");注釋掉,執行printf("fork!");
??? 則新的程序的執行結果是:
????fork!I am the parent process, my process id is 3298
??? fork!I am the child process, my process id is 3299?
??? 程序的唯一的區別就在于一個/n回車符號,為什么結果會相差這么大呢?
????這就跟printf的緩沖機制有關了,printf某些內容時,操作系統僅僅是把該內容放到了stdout的緩沖隊列里了,并沒有實際的寫到屏幕上。但是,只要看到有/n 則會立即刷新stdout,因此就馬上能夠打印了。
??? 運行了printf("fork!")后,“fork!”僅僅被放到了緩沖里,程序運行到fork時緩沖里面的“fork!”? 被子進程復制過去了。因此在子進程度stdout緩沖里面就也有了fork! 。所以,你最終看到的會是fork!? 被printf了2次!!!!
??? 而運行printf("fork! /n")后,“fork!”被立即打印到了屏幕上,之后fork到的子進程里的stdout緩沖里不會有fork! 內容。因此你看到的結果會是fork! 被printf了1次!!!!
????所以說printf("+");不能正確地反應進程的數量。
??? 大家看了這么多可能有點疲倦吧,不過我還得貼最后一份代碼來進一步分析fork函數。
[cpp]?view plain?copy
#include???
#include???
int?main(int?argc,?char*?argv[])??
{??
fork();??
fork()?&&?fork()?||?fork();??
fork();??
return?0;??
}??
問題是不算main這個進程自身,程序到底創建了多少個進程。
??? 為了解答這個問題,我們先做一下弊,先用程序驗證一下,到此有多少個進程。
[c-sharp]?view plain?copy
#include???
int?main(int?argc,?char*?argv[])??
{??
fork();??
fork()?&&?fork()?||?fork();??
fork();??
printf("+/n");??
}??
答案是總共20個進程,除去main進程,還有19個進程。
??? 我們再來仔細分析一下,為什么是還有19個進程。
??? 第一個fork和最后一個fork肯定是會執行的。
??? 主要在中間3個fork上,可以畫一個圖進行描述。
??? 這里就需要注意&&和||運算符。
??? A&&B,如果A=0,就沒有必要繼續執行&&B了;A非0,就需要繼續執行&&B。
??? A||B,如果A非0,就沒有必要繼續執行||B了,A=0,就需要繼續執行||B。
??? fork()對于父進程和子進程的返回值是不同的,按照上面的A&&B和A||B的分支進行畫圖,可以得出5個分支。
加上前面的fork和最后的fork,總共4*5=20個進程,除去main主進程,就是19個進程了。
三、fork高階知識
這一塊我主要就fork函數講一下操作系統進程的創建、死亡和調度等。因為時間和精力限制,我先寫到這里,下次找個時間我爭取把剩下的內容補齊。
?
評論
查看更多