fork的返回值
fork簡介:??????
?? fork英文原意是“分岔,分支”的意思,而在操作系統(tǒng)中,乃是著名的Unix(或類Unix,如Linux,Minix)中用于創(chuàng)建子進(jìn)程的系統(tǒng)調(diào)用。?
【NOTE1】?
fork () 的作用是什么?換句話說,你用 fork () 的目的是什么??
――是為了產(chǎn)生一個新的進(jìn)程,地球人都知道 :)
產(chǎn)生一個什么樣的進(jìn)程??
――和你本來調(diào)用 fork () 的那個進(jìn)程基本一樣的進(jìn)程,其實(shí)就是你原來進(jìn)程的副本;?
真的完全一樣嗎??
――當(dāng)然不能完全一樣,你要兩個除了 pid 之外其它一模一樣的進(jìn)程干什么,就算memory?
再多也不用這么擺譜吧??
哪里不一樣??
――當(dāng)然最重要的是 fork () 之后執(zhí)行的代碼不一樣,you know, i know :)?
怎么實(shí)現(xiàn)呢??
――如果是 Windows,它會讓你在 fork () 里面提供一大堆東西,指明這個那個什么的……?
我用的是 unix 啊?
――所以很簡單,unix 會讓兩個進(jìn)程(不錯,原來是一個,unix 替你復(fù)制了一個,現(xiàn)在有兩個)?
在 fork () 之后產(chǎn)生不同:返回值不同。其中一個進(jìn)程(使用新的 pid)里面的 fork () 返回零,?
這個進(jìn)程就是“子進(jìn)程”;而另一個進(jìn)程(使用原來的 pid)中的 fork () 返回前面那個子進(jìn)程的?
pid,他自己被稱為“父進(jìn)程”?
然后呢??
――寫代碼的人又不笨,當(dāng)然就根據(jù)返回值是否非零來判斷了,現(xiàn)在我是在子進(jìn)程里面呢,還是在?
父進(jìn)程里面?在子進(jìn)程里面就執(zhí)行子進(jìn)程該執(zhí)行的代碼,在父進(jìn)程里面就執(zhí)行父進(jìn)程的代碼……?
??? 有鐵桿 windows fans 借此說明,windows 好啊,子進(jìn)程用子進(jìn)程的代碼,父進(jìn)程用父進(jìn)程的,?
你 unix 笨了吧,子進(jìn)程包含父進(jìn)程、子進(jìn)程的代碼,父進(jìn)程包含父進(jìn)程子進(jìn)程的代碼,豈不是多占用內(nèi)存了嗎??
――據(jù)我所知,unix 代碼段都是可重入代碼,也就是說,進(jìn)程復(fù)制,并不復(fù)制代碼段,若干個進(jìn)程?
共享同一代碼段,增加的只是全局共享數(shù)據(jù)和對文件描述符的引用等,另外就是堆棧。你一個代碼?
長達(dá) 10M 的進(jìn)程,fork () 出三四個子進(jìn)程,只是增加一點(diǎn)內(nèi)存占用(如果你沒有使用很多全局變量?
的話),而不是占用 40M 以上的內(nèi)存。?
【NOTE2】?
程序?? 從?? fork?? 開始分支?? (稱分支不準(zhǔn)確),?? 一路是主進(jìn)程?? pid?? >?? 0?? (pid?? 是子進(jìn)程ID)?? 一路是子進(jìn)程?? pid?? ==?? 0?? 自此分成兩個任務(wù)?
其實(shí)fork的時候已經(jīng)兩個分支了,數(shù)據(jù)段被復(fù)制了一份,因此pid有兩份???????
執(zhí)行pid=fork()時,返回值賦給pid在兩個進(jìn)程中運(yùn)行,???????
fork會返回給父進(jìn)程的那個>0的值,告訴調(diào)用者新建進(jìn)程的pid????
子進(jìn)程的fork返回值是0?????
更不用說if...else的比較也是在兩個進(jìn)程中都做的了????
【NOTE3】?
fork的精辟剖析?
程序如下:?
#include <unistd.h>;?
#include <sys/types.h>;?
main ()
{ pid_t pid;?
pid=fork();?
?? if (pid < 0) printf("error in fork!");?
else if (pid == 0)?
?? printf("i am the child process, my process id is %dn",getpid());?
else?
printf("i am the parent process, my process id is %dn",getpid());?
}?
結(jié)果是?
[root@localhost c]# ./a.out?
i am the child process, my process id is 4286?
i am the parent process, my process id is 4285?
一:?
要搞清楚fork的執(zhí)行過程,就必須先講清楚操作系統(tǒng)中的“進(jìn)程(process)”概念。一個進(jìn)程,主要包含三個元素:?
o. 一個可以執(zhí)行的程序;?
o. 和該進(jìn)程相關(guān)聯(lián)的全部數(shù)據(jù)(包括變量,內(nèi)存空間,緩沖區(qū)等等);?
o. 程序的執(zhí)行上下文(execution context)。?
不妨簡單理解為,一個進(jìn)程表示的,就是一個可執(zhí)行程序的一次執(zhí)行過程中的一個狀態(tài)。操作系統(tǒng)對進(jìn)程的管理,典型的情況,是通過進(jìn)程表完成的。進(jìn)程表中的每 一個表項(xiàng),記錄的是當(dāng)前操作系統(tǒng)中一個進(jìn)程的情況。對于單 CPU的情況而言,每一特定時刻只有一個進(jìn)程占用 CPU,但是系統(tǒng)中可能同時存在多個活動的(等待執(zhí)行或繼續(xù)執(zhí)行的)進(jìn)程。一個稱為“程序計(jì)數(shù)器(program counter, pc)”的寄存器,指出當(dāng)前占用 CPU的進(jìn)程要執(zhí)行的下一條指令的位置。當(dāng)分給某個進(jìn)程的 CPU時間已經(jīng)用完,操作系統(tǒng)將該進(jìn)程相關(guān)的寄存器的值,保存到該進(jìn)程在進(jìn)程表中對應(yīng)的表項(xiàng)里面;把將要接替這個進(jìn)程占用 CPU的那個進(jìn)程的上下文,從進(jìn)程表中讀出,并更新相應(yīng)的寄存器(這個過程稱為“上下文交換(process context switch)”,實(shí)際的上下文交換需要涉及到更多的數(shù)據(jù),那和fork無關(guān),不再多說,主要要記住程序寄存器 pc指出程序當(dāng)前已經(jīng)執(zhí)行到哪里,是進(jìn)程上下文的重要內(nèi)容,換出 CPU的進(jìn)程要保存這個寄存器的值,換入CPU的進(jìn)程,也要根據(jù)進(jìn)程表中保存的本進(jìn)程執(zhí)行上下文信息,更新這個寄存器)。?
好了,有這些概念打底,可以說fork了。當(dāng)你的程序執(zhí)行到下面的語句:?
pid=fork();?
操作系統(tǒng)創(chuàng)建一個新的進(jìn)程(子進(jìn)程),并且在進(jìn)程表中相應(yīng)為它建立一個新的表項(xiàng)。新進(jìn)程和原有進(jìn)程的可執(zhí)行程序是同一個程序;上下文和數(shù)據(jù),絕大部分就是 原進(jìn)程(父進(jìn)程)的拷貝,但它們是兩個相互獨(dú)立的進(jìn)程!此時程序寄存器pc,在父、子進(jìn)程的上下文中都聲稱,這個進(jìn)程目前執(zhí)行到fork調(diào)用即將返回(此 時子進(jìn)程不占有CPU,子進(jìn)程的pc不是真正保存在寄存器中,而是作為進(jìn)程上下文保存在進(jìn)程表中的對應(yīng)表項(xiàng)內(nèi))。問題是怎么返回,在父子進(jìn)程中就分道揚(yáng) 鑣。?
父進(jìn)程繼續(xù)執(zhí)行,操作系統(tǒng)對fork的實(shí)現(xiàn),使這個調(diào)用在父進(jìn)程中返回剛剛創(chuàng)建的子進(jìn)程的pid(一個正整數(shù)),所以下面的if語句中pid<0, pid==0的兩個分支都不會執(zhí)行。所以輸出i am the parent process...?
子進(jìn)程在之后的某個時候得到調(diào)度,它的上下文被換入,占據(jù) CPU,操作系統(tǒng)對fork的實(shí)現(xiàn),使得子進(jìn)程中fork調(diào)用返回0。所以在這個進(jìn)程(注意這不是父進(jìn)程了哦,雖然是同一個程序,但是這是同一個程序的另 外一次執(zhí)行,在操作系統(tǒng)中這次執(zhí)行是由另外一個進(jìn)程表示的,從執(zhí)行的角度說和父進(jìn)程相互獨(dú)立)中pid=0。這個進(jìn)程繼續(xù)執(zhí)行的過程中,if語句中 pid<0不滿足,但是pid= =0是true。所以輸出i am the child process...?
為什么看上去程序中互斥的兩個分支都被執(zhí)行了?在一個程序的一次執(zhí)行中,這當(dāng)然是不可能的;但是你看到的兩行輸出是來自兩個進(jìn)程,這兩個進(jìn)程來自同一個程序的兩次執(zhí)行。?
fork之后,操作系統(tǒng)會復(fù)制一個與父進(jìn)程完全相同的子進(jìn)程,雖說是父子關(guān)系,但是在操作系統(tǒng)看來,他們更像兄弟關(guān)系,這2個進(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)程號,如果fork不成功,父進(jìn)程會返回錯誤。?
可以這樣想象,2個進(jìn)程一直同時運(yùn)行,而且步調(diào)一致,在fork之后,他們分別作不同的工作,也就是分岔了。這也是fork為什么叫fork的原因。?
在程序段里用了fork()之后程序出了分岔,派生出了兩個進(jìn)程。具體哪個先運(yùn)行就看該系統(tǒng)的調(diào)度算法了。?
如果需要父子進(jìn)程協(xié)同,可以通過原語的辦法解決。?
二:?
進(jìn)程的創(chuàng)建:?
創(chuàng)建一個進(jìn)程的系統(tǒng)調(diào)用很簡單.我們只要調(diào)用fork函數(shù)就可以了.?
#include <unistd.h>?
pid_t fork();?
當(dāng)一個進(jìn)程調(diào)用了fork以后,系統(tǒng)會創(chuàng)建一個子進(jìn)程.這個子進(jìn)程和父進(jìn)程不同的地方只有他的進(jìn)程ID和父進(jìn)程ID,其他的都是一樣.就象父進(jìn)程克隆 (clone)自己一樣.當(dāng)然創(chuàng)建兩個一模一樣的進(jìn)程是沒有意義的.為了區(qū)分父進(jìn)程和子進(jìn)程,我們必須跟蹤fork的返回值. 當(dāng)fork掉用失敗的時候(內(nèi)存不足或者是用戶的最大進(jìn)程數(shù)已到)fork返回-1,否則fork的返回值有重要的作用.對于父進(jìn)程fork返回子進(jìn)程的 ID,而對于fork子進(jìn)程返回0.我們就是根據(jù)這個返回值來區(qū)分父子進(jìn)程的. 父進(jìn)程為什么要創(chuàng)建子進(jìn)程呢?前面我們已經(jīng)說過了Linux是一個多用戶操作系統(tǒng),在同一時間會有許多的用戶在爭奪系統(tǒng)的資源.有時進(jìn)程為了早一點(diǎn)完成任 務(wù)就創(chuàng)建子進(jìn)程來爭奪資源. 一旦子進(jìn)程被創(chuàng)建,父子進(jìn)程一起從fork處繼續(xù)執(zhí)行,相互競爭系統(tǒng)的資源.有時候我們希望子進(jìn)程繼續(xù)執(zhí)行,而父進(jìn)程阻塞,直到子進(jìn)程完成任務(wù).這個時候 我們可以調(diào)用wait或者waitpid系統(tǒng)調(diào)用.?
?? 總結(jié)一下有三:?
1,派生子進(jìn)程的進(jìn)程,即父進(jìn)程,其pid不變;?
2,對子進(jìn)程來說,fork返回給它0,但它的pid絕對不會是0;之所以fork返回0給它,是因?yàn)樗S時可以調(diào)用getpid()來獲取自己的pid;?
3,fork之后父子進(jìn)程除非采用了同步手段,否則不能確定誰先運(yùn)行,也不能確定誰先結(jié)束。認(rèn)為子進(jìn)程結(jié)束后父進(jìn)程才從fork返回的,這是不對的,fork不是這樣的,vfork才這樣。?
【NOTE4】?
首先必須有一點(diǎn)要清楚,函數(shù)的返回值是儲存在寄存器eax中的。?
其次,當(dāng)fork返回時,新進(jìn)程會返回0是因?yàn)樵诔跏蓟蝿?wù)結(jié)構(gòu)時,將eax設(shè)置為0;?
在fork中,把子進(jìn)程加入到可運(yùn)行的隊(duì)列中,由進(jìn)程調(diào)度程序在適當(dāng)?shù)臅r機(jī)調(diào)度運(yùn)行。也就是從此時開始,當(dāng)前進(jìn)程分裂為兩個并發(fā)的進(jìn)程。?
無論哪個進(jìn)程被調(diào)度運(yùn)行,都將繼續(xù)執(zhí)行fork函數(shù)的剩余代碼,執(zhí)行結(jié)束后返回各自的值。?
【NOTE5】?
對于fork來說,父子進(jìn)程共享同一段代碼空間,所以給人的感覺好像是有兩次返回,其實(shí)對于調(diào)用fork的父進(jìn)程來說,如果fork出來的子進(jìn)程沒有得到 調(diào)度,那么父進(jìn)程從fork系統(tǒng)調(diào)用返回,同時分析sys_fork知道,fork返回的是子進(jìn)程的id。再看fork出來的子進(jìn)程,由 copy_process函數(shù)可以看出,子進(jìn)程的返回地址為ret_from_fork(和父進(jìn)程在同一個代碼點(diǎn)上返回),返回值直接置為0。所以當(dāng)子進(jìn) 程得到調(diào)度的時候,也從fork返回,返回值為0。?
??? 關(guān)鍵注意兩點(diǎn):1.fork返回后,父進(jìn)程或子進(jìn)程的執(zhí)行位置。(首先會將當(dāng)前進(jìn)程eax的值做為返回值)2.兩次返回的pid存放的位置。(eax中)?
進(jìn)程調(diào)用copy_process得到lastpid的值(放入eax中,fork正常返回后,父進(jìn)程中返回的就是lastpid)?
子進(jìn)程任務(wù)狀態(tài)段tss的eax被設(shè)置成0,?
fork.c 中?
p->tss.eax=0;(如果子進(jìn)程要執(zhí)行就需要進(jìn)程切換,當(dāng)發(fā)生切換時,子進(jìn)程tss中的eax值就調(diào)入eax寄存器,子進(jìn)程執(zhí)行時首先會將eax的內(nèi)容做為返回值)?
當(dāng)子進(jìn)程開始執(zhí)行時,copy_process返回eax的值。?
fork()后,就是兩個任務(wù)同時進(jìn)行,父進(jìn)程用他的tss,子進(jìn)程用自己的tss,在切換時,各用各的eax中的值.?
所以,“一次調(diào)用兩次返回”是2個不同的進(jìn)程!?
看這一句:pid=fork()?
當(dāng)執(zhí)行這一句時,當(dāng)前進(jìn)程進(jìn)入fork()運(yùn)行,此時,fork()內(nèi)會用一段嵌入式匯編進(jìn)行系統(tǒng)調(diào)用:int 0x80(具體代碼可參見內(nèi)核版本0.11的unistd.h文件的133行_syscall0函數(shù))。這時進(jìn)入內(nèi)核根據(jù)此前寫入eax的系統(tǒng)調(diào)用功能號 便會運(yùn)行sys_fork系統(tǒng)調(diào)用。接著,sys_fork中首先會調(diào)用C函數(shù)find_empty_process產(chǎn)生一個新的進(jìn)程,然后會調(diào)用C函數(shù) copy_process將父進(jìn)程的內(nèi)容復(fù)制給子進(jìn)程,但是子進(jìn)程tss中的eax值賦值為0(這也是為什么子進(jìn)程中返回0的原因),當(dāng)賦值完成后, copy_process會返回新進(jìn)程(該子進(jìn)程)的pid,這個值會被保存到eax中。這時子進(jìn)程就產(chǎn)生了,此時子進(jìn)程與父進(jìn)程擁有相同的代碼空間,程 序指針寄存器eip指向相同的下一條指令地址,當(dāng)fork正常返回調(diào)用其的父進(jìn)程后,因?yàn)閑ax中的值是新創(chuàng)建的子進(jìn)程號,所以,fork()返回子進(jìn)程 號,執(zhí)行else(pid>0);當(dāng)產(chǎn)生進(jìn)程切換運(yùn)行子進(jìn)程時,首先會恢復(fù)子進(jìn)程的運(yùn)行環(huán)境即裝入子進(jìn)程的tss任務(wù)狀態(tài)段,其中的eax 值(copy_process中置為0)也會被裝入eax寄存器,所以,當(dāng)子進(jìn)程運(yùn)行時,fork返回的是0執(zhí)行if(pid==0)。?
【NOTE5】?
理解它關(guān)鍵在于理解堆棧的切換和壓棧,彈棧!?
關(guān)于子進(jìn)程的返回:?
子進(jìn)程復(fù)制了父進(jìn)程的棧內(nèi)容,從高到低?
SS?
ESP?
EFLAGS?
CS?
EIP -----此是int 0x80 的下一條指令,也是子進(jìn)程開始執(zhí)行的地方!!!!?
DS?
ES?
FS?
EDX?
ECX?
EBX?
GS?
ESI?
EDI?
EBP?
EAX(0)?
由于 EAX = 0,所以子進(jìn)程返回 0 給 fork.?
注:新進(jìn)程的用戶棧設(shè)為其父進(jìn)程的用戶棧(最后彈出的SS,ESP)。如果父子進(jìn)程以copy_on_write方式共用用戶堆棧?
(Linux之下就是這樣的),而且在此之前父進(jìn)程修改了該堆棧(如果父進(jìn)程先返回,這幾乎是肯定的),那么,系統(tǒng)已經(jīng)為父進(jìn)程創(chuàng)建了該用戶棧的副本,父進(jìn)程原來的用戶棧留給了子進(jìn)程。那么新進(jìn)程的系統(tǒng)棧已經(jīng)清空,新進(jìn)程回到了用戶態(tài),返回到了函數(shù)fork。?
【NOTE6】?
關(guān)于fork的討論與評價(jià):?
fork好不好?相比其他操作系統(tǒng)如Windows,Windows會有諸如CreateProcess這樣的函數(shù)來創(chuàng)建一個與生俱來兩手空空的獨(dú)立的新進(jìn)程。然后還有一大堆參數(shù),指手畫腳的告訴你這個那個是什么。。。?
煩!!!?
K.I.S.S. (Keep it simple,stupid.)是Unix的至高原則。?
fork 起源于 Unix 操作系統(tǒng)。那是貝爾實(shí)驗(yàn)室的 K&R (這兩人是Unix和C語言之父) 的一項(xiàng)天才發(fā)明!!!?? Linux由于與生俱來就與Unix血濃于水,所以繼承了它的這個天才發(fā)明。?
這種方法效率是很高的。因?yàn)閺?fù)制的代價(jià)是很低的。在計(jì)算機(jī)網(wǎng)絡(luò)的實(shí)現(xiàn)中,以及在 client/server 系統(tǒng)中的server 一方的實(shí)現(xiàn)中,fork 常常是最自然,最有效,最適宜的手段。很多人甚至懷疑,到底是先有 fork 還是先有 client/server,因?yàn)?fork 似乎就是專門為此而設(shè)計(jì)的 !更重要的好處是,這樣有利于父子進(jìn)程間通過 pipe 建立起一種簡單有效的進(jìn)程間通信管道,并且產(chǎn)生了操作系統(tǒng)的用戶界面即 shell 的管道機(jī)制。這一點(diǎn),對于 Unix 的發(fā)展和應(yīng)用推廣,對于 Uinx 程序設(shè)計(jì)環(huán)境的形成,對于 Unix 程序設(shè)計(jì)風(fēng)格的形成,都有非常深遠(yuǎn)的影響。可以說這是一項(xiàng)天才的發(fā)明,它在很大程度上改變了操作系統(tǒng)的發(fā)展方向。
總結(jié)
- 上一篇: 2020快手食品行业数据价值报告
- 下一篇: 互联网日报 | 理想汽车交付量突破300