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