<?php
$pid = pcntl_fork();
if ($pid == -1)
{
die("could not fork");
}
elseif($pid == 0)
{
echo "I'm the child process \n";
}
else
{
echo "I'm the parent process \n";
exit;
}
要搞清楚fork的執(zhí)行過(guò)程楷掉,就必須先弄清楚操作系統(tǒng)中”進(jìn)程(process)”的概念吵瞻。
一個(gè)進(jìn)程运嗜,主要包含三個(gè)元素:
1. 一個(gè)可以執(zhí)行的程序仲翎;
2. 和該進(jìn)程相關(guān)聯(lián)的全部數(shù)據(jù)(包括變量痹扇,內(nèi)存空間,緩沖區(qū)等等)溯香;
3. 程序的執(zhí)行上下文(execution context)鲫构;
不妨簡(jiǎn)單理解為,一個(gè)進(jìn)程表示的就是一個(gè)可執(zhí)行程序的一次執(zhí)行過(guò)程中的一個(gè)狀態(tài)玫坛。操作系統(tǒng)對(duì)進(jìn)程的管理结笨,典型的情況,是通過(guò)進(jìn)程表完成的。進(jìn)程表中的每一個(gè)表項(xiàng)炕吸,記錄的是當(dāng)前操作系統(tǒng)中一個(gè)進(jìn)程的情況伐憾。對(duì)于單 CPU的情況而言,每一特定時(shí)刻只有一個(gè)進(jìn)程占用 CPU赫模,但是系統(tǒng)中可能同時(shí)存在多個(gè)活動(dòng)的(等待執(zhí)行或繼續(xù)執(zhí)行的)進(jìn)程树肃。
一個(gè)稱為”程序計(jì)數(shù)器(program counter, pc)”的寄存器,指出當(dāng)前占用 CPU的進(jìn)程要執(zhí)行的下一條指令的位置瀑罗。
當(dāng)分給某個(gè)進(jìn)程的 CPU時(shí)間已經(jīng)用完扫外,操作系統(tǒng)將該進(jìn)程相關(guān)的寄存器的值,保存到該進(jìn)程在進(jìn)程表中對(duì)應(yīng)的表項(xiàng)里面廓脆;把將要接替這個(gè)進(jìn)程占用 CPU的那個(gè)進(jìn)程的上下文筛谚,從進(jìn)程表中讀出,并更新相應(yīng)的寄存器(這個(gè)過(guò)程稱為”上下文交換(process context switch)”停忿,實(shí)際的上下文交換需要涉及到更多的數(shù)據(jù)驾讲,那和fork無(wú)關(guān),不再多說(shuō)席赂,主要要記住程序寄存器pc指出程序當(dāng)前已經(jīng)執(zhí)行到哪里吮铭,是進(jìn)程上 下文的重要內(nèi)容,換出 CPU的進(jìn)程要保存這個(gè)寄存器的值颅停,換入CPU的進(jìn)程谓晌,也要根據(jù)進(jìn)程表中保存的本進(jìn)程執(zhí)行上下文信息,更新這個(gè)寄存器)癞揉。
好了纸肉,有這些概念打底,可以說(shuō)fork了喊熟,當(dāng)你的程序執(zhí)行到下面的語(yǔ)句:
pid = pcntl_fork();
操作系統(tǒng)創(chuàng)建一個(gè)新的進(jìn)程(子進(jìn)程)柏肪,并且在進(jìn)程表中相應(yīng)為它建立一個(gè)新的表項(xiàng)。新進(jìn)程和原有進(jìn)程的可執(zhí)行程序是同一個(gè)程序芥牌;上下文和數(shù)據(jù)烦味,絕大部分就是原進(jìn)程(父進(jìn)程)的拷貝,但它們是兩個(gè)相互獨(dú)立的進(jìn)程壁拉!此時(shí)程序寄存器pc在父谬俄、子進(jìn)程的上下文中都聲稱,這個(gè)進(jìn)程目前執(zhí)行到fork調(diào)用即將返回(此時(shí)子進(jìn)程不占有CPU弃理,子進(jìn)程的pc不是真正保存在寄存器中溃论,而是作為進(jìn)程上下文保存在進(jìn)程表中的對(duì)應(yīng)表項(xiàng)內(nèi))。問(wèn)題是怎么返回案铺,在父子進(jìn)程中就分道揚(yáng)鑣蔬芥。
父進(jìn)程繼續(xù)執(zhí)行操作系統(tǒng)對(duì)fork的實(shí)現(xiàn)梆靖,使這個(gè)調(diào)用在父進(jìn)程中返回剛剛創(chuàng)建的子進(jìn)程的pid(一個(gè)正整數(shù))控汉,所以后面的if語(yǔ)句中pid<0, pid==0的兩個(gè)分支都不會(huì)執(zhí)行笔诵。所以輸出:i am the parent process…
接著子進(jìn)程在之后的某個(gè)時(shí)候得到調(diào)度,它的上下文被換入姑子,占據(jù) CPU乎婿,操作系統(tǒng)對(duì)fork的實(shí)現(xiàn)使得子進(jìn)程中fork調(diào)用返回0,所以在這個(gè)進(jìn)程中pid=0(注意這不是父進(jìn)程了哦街佑,雖然是同一個(gè)程序谢翎,但是這是同一個(gè)程序的另外一次執(zhí)行,在操作系統(tǒng)中這次執(zhí)行是由另外一個(gè)進(jìn)程表示的沐旨,從執(zhí)行的角度說(shuō)和父進(jìn)程相互獨(dú)立)森逮。這個(gè)進(jìn)程在繼續(xù)執(zhí)行的過(guò)程中,if語(yǔ)句中 pid<0不滿足磁携,但是pid==0是true褒侧,所以輸出:i am the child process…
我想你比較困惑的就是:
為什么看上去程序中互斥的兩個(gè)分支都被執(zhí)行了,在一個(gè)程序的一次執(zhí)行中谊迄,這當(dāng)然是不可能的闷供,事實(shí)上你看到的兩行輸出是來(lái)自兩個(gè)獨(dú)立的進(jìn)程,而這兩個(gè)進(jìn)程來(lái)自同一個(gè)程序的兩次執(zhí)行统诺。
-------------------------------
fork之后歪脏,操作系統(tǒng)會(huì)復(fù)制一個(gè)與父進(jìn)程完全相同的子進(jìn)程,雖說(shuō)是父子關(guān)系粮呢,但是在操作系統(tǒng)看來(lái)婿失,他們更像兄弟關(guān)系,這2個(gè)進(jìn)程共享代碼空間啄寡,但是數(shù)據(jù)空間是互相獨(dú)立的移怯,子進(jìn)程數(shù)據(jù)空間中的內(nèi)容是父進(jìn)程的完整拷貝,指令指針也完全相同这难,但只有一點(diǎn)不同舟误,如果fork成功,子進(jìn)程中fork的返回值是0姻乓,父進(jìn)程中fork的返回值是子進(jìn)程的進(jìn)程號(hào)嵌溢,如果fork失敗,父進(jìn)程會(huì)返回錯(cuò)誤蹋岩。
可以這樣想象赖草,2個(gè)進(jìn)程一直同時(shí)運(yùn)行,而且步調(diào)一致剪个,在fork之后秧骑,他們分別作不同的工作,也就是分岔了,這也是fork為什么叫fork的原因乎折。
至于哪一個(gè)進(jìn)程最先運(yùn)行绒疗,這與操作系統(tǒng)平臺(tái)的調(diào)度算法有關(guān),而且這個(gè)問(wèn)題在實(shí)際應(yīng)用中并不重要骂澄,如果需要父子進(jìn)程協(xié)同運(yùn)作吓蘑,可以通過(guò)控制語(yǔ)法結(jié)構(gòu)的辦法解決。
-------------------------------
fork前子進(jìn)程可以繼承父進(jìn)程的東西坟冲,但是在pcntl_fork()后子進(jìn)程和父進(jìn)程就沒(méi)有任何繼承關(guān)系了磨镶。在子進(jìn)程里創(chuàng)建的東西是子進(jìn)程的,在父進(jìn)程創(chuàng)建的東西是父進(jìn)程的健提,可以完全看成是兩個(gè)獨(dú)立的進(jìn)程琳猫。
-------------------------------
在程序段里用了pcntl_fork()之后程序出了分岔,派生出了兩個(gè)進(jìn)程私痹,具體哪個(gè)先運(yùn)行就看該系統(tǒng)的調(diào)度算法了脐嫂。
在這里,我們可以這么認(rèn)為侄榴,在運(yùn)行到”pid=pcntl_fork();”時(shí)系統(tǒng)派生出一個(gè)跟主程序一模一樣的子進(jìn)程雹锣。該進(jìn)程的”pid=pcntl_fork();”一句中 pid得到的就是子進(jìn)程本身的pid;子進(jìn)程結(jié)束后癞蚕,父進(jìn)程的”pid=pcntl_fork();”中pid得到的就是父進(jìn)程本身的pid蕊爵,因此該程序有兩行輸出。
-------------------------------
pcntl_fork()函數(shù)復(fù)制了當(dāng)前進(jìn)程的PCB桦山,并向父進(jìn)程返回了派生子進(jìn)程的pid攒射,父子進(jìn)程并行,打印語(yǔ)句的先后完全看系統(tǒng)的調(diào)度算法恒水,打印的內(nèi)容控制則靠pid變量來(lái)控制会放。因?yàn)槲覀冎纏cntl_fork()向父進(jìn)程返回了派生子進(jìn)程的pid,是個(gè)正整數(shù)钉凌;而派生子進(jìn)程的pid變量并沒(méi)有被改變咧最,這一區(qū)別使得我們看到了他們的不同輸出。
-------------------------------
1. 派生子進(jìn)程的進(jìn)程御雕,即父進(jìn)程矢沿,其pid不變;
2. 對(duì)子進(jìn)程來(lái)說(shuō)酸纲,fork()函數(shù)返回給它0, 但它自身的pid絕對(duì)不會(huì)是0捣鲸;之所以fork()函數(shù)返回0給它,是因?yàn)樗S時(shí)可以調(diào)用getpid()來(lái)獲取自己的pid闽坡;
3. fork之后父栽惶、子進(jìn)程除非采用了同步手段愁溜,否則不能確定誰(shuí)先運(yùn)行,也不能確定誰(shuí)先結(jié)束外厂。認(rèn)為子進(jìn)程結(jié)束后父進(jìn)程才從fork返回的冕象,這是不對(duì)的,fork不是這樣的酣衷,vfork才這樣交惯。