在閱讀這篇文章的時候我希望長話短說,需要你事先接收一個觀點,那就是:我們的操作系統(tǒng)就是一堆進程,每一個進程都是由已有的進程創(chuàng)造出來的。
以linux操作系統(tǒng)為例,在啟動之后第一個誕生的進程是PID為0的名叫“idle”的進程,后續(xù)所有的進程都是由它通過fork()創(chuàng)建的,包括我們熟知的PID為1的“init”進程。也許一杯咖啡的時間,等系統(tǒng)完全啟動,我們可以登錄以后,在命令行執(zhí)行我們自己寫的程序,這又是通過終端進程創(chuàng)建了新的進程。這就是在前面所說的操作系統(tǒng)的運行就是進程的不斷創(chuàng)建和銷毀的過程。
在linux系統(tǒng)函數(shù)中,fork()是用來創(chuàng)建新的子進程的函數(shù)。根據(jù)linux編程手冊,在fork函數(shù)執(zhí)行完畢后,如果創(chuàng)建新進程成功,則出現(xiàn)兩個進程,一個是子進程,一個是父進程。在子進程中,fork函數(shù)返回0,在父進程中,fork返回新創(chuàng)建子進程的進程ID。我們可以通過fork返回的值來判斷當前進程是子進程還是父進程。
需要注意的是,在fork之后兩個進程用的是相同的物理空間(內(nèi)存區(qū)),子進程的代碼段、數(shù)據(jù)段、堆棧都是指向父進程的物理空間,也就是說,兩者的虛擬空間不同,其對應的物理空間是一個。這是出于效率的考慮,在linux中被稱為“寫時復制”(COW)技術(shù),只有當父子進程中有更改相應段的行為發(fā)生時,再為子進程相應的段分配物理空間。另外fork之后內(nèi)核會將子進程排在隊列的前面,以讓子進程先執(zhí)行,以免父進程執(zhí)行導致寫時復制,而后子進程執(zhí)行exec系統(tǒng)調(diào)用,因無意義的復制而造成效率的下降。
有關(guān)父子進程和fork的概念介紹完了,在這里想繼續(xù)分享一道很有意思的筆試題。
寫出下面程序的運行結(jié)果:
int main()
{
pid_t cld_pid;
int status;
int a=1,b=2;
for(int i = 0; i < 2; i++ ){
if ((cld_pid = fork()) == 0){
a+=1;
printf("a=%d\n",a);
}
else{
b+=1;
printf("b=%d\n",b);
}
}
wait(&status);
return 0;
}
這其實就是考察了如果一個進程在for循環(huán)進行fork,會產(chǎn)生什么樣的結(jié)果,哪些變量的值已經(jīng)改變,哪些變量的值沒有改變。

實際上它的運行一共產(chǎn)生了三個子進程:
首先父進程fork出了子進程1,同時自己執(zhí)行了b自加和打印,得到“b=3”。此時在第一輪循環(huán)中,子進程1的變量分別為i=0,a=1,b=2。第二輪循環(huán)中,創(chuàng)建了子進程2,同時執(zhí)行b的自加和打印,得到“b=4”,耗盡循環(huán)條件。而子進程2的變量為i=1,a=1,b=3。
對于子進程1,會執(zhí)行a的自加和打印,得到“a=2”,完成第一輪循環(huán)。在第二輪循環(huán)中創(chuàng)建了子子進程1,并對b自加和打印,得到“b=3”。子子進程1的變量分別為i=1,a=2,b=2。
對于子進程2,會執(zhí)行a的自加和打印,得到“a=2”,然后由于i=1,結(jié)束。
對于子子進程1,會執(zhí)行a的自加和打印,得到“a=3”,而由于i已經(jīng)為1,再也沒有新的循環(huán)了。