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