出處:http://hi.baidu.com/okeyes888/item/14559ae22eb72da9ce2d4f04 加自己的修改?。ùa驗(yàn)證)
fork確實(shí)創(chuàng)建了一個(gè)子進(jìn)程并完全復(fù)制父進(jìn)程,但是子進(jìn)程是從fork后面那個(gè)指令開(kāi)始執(zhí)行的。 fork和線程,進(jìn)程的理解2011-10-11 10:09 本文分為三部分: 1. 什么是fork? 2. fork用途? 3. fork怎么工作?
1. 什么是fork? Fork源于OS中多線程任務(wù)的需要。在傳統(tǒng)的Unix環(huán)境下,有兩個(gè)基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork( )用來(lái)創(chuàng)建一個(gè)新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)完全拷貝;函數(shù)族e(cuò)xec( )用來(lái)啟動(dòng)另外的進(jìn)程以取代當(dāng)前運(yùn)行的進(jìn)程。 下面說(shuō)一下進(jìn)程和線程。 進(jìn)程的簡(jiǎn)單理解就是:一個(gè)進(jìn)程表示的就是一個(gè)可執(zhí)行程序的一次執(zhí)行過(guò)程中的一個(gè)狀態(tài)。 一個(gè)進(jìn)程,主要包含三個(gè)元素: 一個(gè)可以執(zhí)行的程序; --- 代碼段
"代碼段",顧名思義,就是存放了程序代碼的數(shù)據(jù),假如機(jī)器中有數(shù)個(gè)進(jìn)程運(yùn)行相同的一個(gè)程序,那么它們就可以使用相同的代碼段。 "堆棧段"存放的就是子程序的返回地址、子程序的參數(shù)以及程序的局部變量。 而數(shù)據(jù)段則存放程序的全局變量,常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空間)。
一般的CPU都有上述三種段寄存器,以方便操作系統(tǒng)的運(yùn)行。這三個(gè)部分也是構(gòu)成一個(gè)完整的執(zhí)行序列的必要的部分。系統(tǒng)如果同時(shí)運(yùn)行數(shù)個(gè)相同的程序,它們之間就不能使用同一個(gè)堆棧段和數(shù)據(jù)段。 操作系統(tǒng)對(duì)進(jìn)程管理,最典型的是通過(guò)進(jìn)程表完成的。進(jìn)程表里再通過(guò)一個(gè)稱為“程序計(jì)數(shù)器(program counter, pc)”的寄存器來(lái)完成“上下文的切換”。(實(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è)寄存器)。 進(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)程。
下面繼續(xù)說(shuō)fork了。當(dāng)程序執(zhí)行到下面的語(yǔ)句:pid=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)題是怎么返回。它們的返回順序是不確定的,取決于OS內(nèi)的調(diào)度。如果想明確它們的執(zhí)行順序,就得實(shí)現(xiàn)“同步”,或者是使用vfork()。這里假設(shè)父進(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í)行。所以一般執(zhí)行fork后都會(huì)有兩個(gè)輸出。
2. Fork用途歸結(jié)起來(lái)有兩個(gè): 第一, 一個(gè)進(jìn)程希望復(fù)制自身,從而父子進(jìn)程能執(zhí)行不同代碼段。 第二, 進(jìn)程想執(zhí)行另外一個(gè)程序 歸結(jié)起來(lái)說(shuō)就是實(shí)現(xiàn)多線程。C語(yǔ)言多線程實(shí)現(xiàn)需要自己控制來(lái)實(shí)現(xiàn),這個(gè)比JAVA要復(fù)雜。
3. Fork怎么工作?先看一個(gè)例子: #include <unistd.h>; #include <sys/types.h>; int main () { pid_t pid; pid=fork(); // 1)從這里開(kāi)始程序分岔,父子進(jìn)程都從這一句開(kāi)始執(zhí)行一次 if (pid < 0) printf("error!"); else if (pid == 0) printf("child process, process id is %dn", getpid()); else // pid > 0 printf("parent process, process id is %dn",getpid()); return 0; } 結(jié)果: [root@localhost yezi]# ./a.out
對(duì)于上面程序段有以下幾個(gè)關(guān)鍵點(diǎn):
(1)返回值的問(wèn)題: 正確返回:父進(jìn)程中返回子進(jìn)程的pid,因此> 0;子進(jìn)程返回0 子進(jìn)程是父進(jìn)程的一個(gè)拷貝。即,子進(jìn)程從父進(jìn)程得到了數(shù)據(jù)段和堆棧段的拷貝,這些需要分配新的內(nèi)存;而對(duì)于只讀的代碼段,通常使用共享內(nèi)存的方式訪問(wèn)。父進(jìn)程與子進(jìn)程的不同之處在于:fork的返回值不同——父進(jìn)程中的返回值為子進(jìn)程的進(jìn)程號(hào),而子進(jìn)程為0。只有父進(jìn)程執(zhí)行的getpid()才是他自己的進(jìn)程號(hào)。對(duì)子進(jìn)程來(lái)說(shuō),fork返回給它0,但它的pid絕對(duì)不會(huì)是0;之所以fork返回0給它,是因?yàn)樗S時(shí)可以調(diào)用getpid()來(lái)獲取自己的pid;
(2) fork返回后,子進(jìn)程和父進(jìn)程都從調(diào)用fork函數(shù)的下一條語(yǔ)句開(kāi)始執(zhí)行。這也是程序中會(huì)打印兩個(gè)結(jié)果的原因。 fork之后,操作系統(tǒng)會(huì)復(fù)制一個(gè)與父進(jìn)程完全相同的子進(jìn)程。不過(guò)這在操作系統(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è)先運(yùn)行,與操作系統(tǒng)的調(diào)度算法有關(guān),而且這個(gè)問(wèn)題在實(shí)際應(yīng)用中并不重要,如果需要父子進(jìn)程協(xié)同,可以通過(guò)原語(yǔ)的辦法實(shí)現(xiàn)同步來(lái)加以解決。 為了加深理解,看下面例子: #include <stdio.h> int main()
輸出的結(jié)果: (1) the first child's pid=12623
(2) the first child's pid=12638
(3) the first child's pid=12642
很奇妙的結(jié)果。不過(guò)理解了“子進(jìn)程和父進(jìn)程都從調(diào)用fork函數(shù)的下一條語(yǔ)句開(kāi)始執(zhí)行”了也不奇怪了。同是這里引入理解fork的第三點(diǎn)
(3) fork函數(shù)不同于其他函數(shù),在于它可能會(huì)有兩個(gè)或是多個(gè)返回值,而且是同時(shí)返回兩個(gè)值。繼續(xù)分析上面的例子。 理解上例的關(guān)鍵在于fork()的返回點(diǎn)在哪里。Fork()同時(shí)返回兩個(gè)值。其中pid=0的這個(gè)返回值用來(lái)執(zhí)行子進(jìn)程的代碼,而大于0的一個(gè)返回值為父進(jìn)程的代碼塊。第一次fork調(diào)用的時(shí)候生叉分為兩個(gè)進(jìn)程,假設(shè)為a父進(jìn)程和b子進(jìn)程。他們分別各自在第二次fork調(diào)用之前打印了b和a各一次;在第一次叉分的這兩個(gè)進(jìn)程中都含有
當(dāng)然在使用fork中還有很多細(xì)節(jié),比如輸出時(shí),對(duì)緩沖區(qū)的不同處理會(huì)使父子進(jìn)程執(zhí)行過(guò)程中輸出不同,以及fork后,子進(jìn)程的exec和exit的一些實(shí)現(xiàn)細(xì)節(jié)。以后再說(shuō)。 |
|
來(lái)自: 紫火神兵 > 《網(wǎng)絡(luò)編程》