天天看點

fork函數傳回值

fork簡介:      

   fork英文原意是“分岔,分支”的意思,而在作業系統中,乃是著名的Unix(或類Unix,如Linux,Minix)中用于建立子程序的系統調用。 

【NOTE1】 

fork () 的作用是什麼?換句話說,你用 fork () 的目的是什麼? 

――是為了産生一個新的程序,地球人都知道 :)

産生一個什麼樣的程序? 

――和你本來調用 fork () 的那個程序基本一樣的程序,其實就是你原來程序的副本; 

真的完全一樣嗎? 

――當然不能完全一樣,你要兩個除了 pid 之外其它一模一樣的程序幹什麼,就算memory 

再多也不用這麼擺譜吧? 

哪裡不一樣? 

――當然最重要的是 fork () 之後執行的代碼不一樣,you know, i know :) 

怎麼實作呢? 

――如果是 Windows,它會讓你在 fork () 裡面提供一大堆東西,指明這個那個什麼的…… 

我用的是 unix 啊 

――是以很簡單,unix 會讓兩個程序(不錯,原來是一個,unix 替你複制了一個,現在有兩個) 

在 fork () 之後産生不同:傳回值不同。其中一個程序(使用新的 pid)裡面的 fork () 傳回零, 

這個程序就是“子程序”;而另一個程序(使用原來的 pid)中的 fork () 傳回前面那個子程序的 

pid,他自己被稱為“父程序” 

然後呢? 

――寫代碼的人又不笨,當然就根據傳回值是否非零來判斷了,現在我是在子程序裡面呢,還是在 

父程序裡面?在子程序裡面就執行子程序該執行的代碼,在父程序裡面就執行父程序的代碼…… 

    有鐵杆 windows fans 借此說明,windows 好啊,子程序用子程序的代碼,父程序用父程序的, 

你 unix 笨了吧,子程序包含父程序、子程序的代碼,父程序包含父程序子程序的代碼,豈不是多占用記憶體了嗎? 

――據我所知,unix 代碼段都是可重入代碼,也就是說,程序複制,并不複制代碼段,若幹個程序 

共享同一代碼段,增加的隻是全局共享資料和對檔案描述符的引用等,另外就是堆棧。你一個代碼 

長達 10M 的程序,fork () 出三四個子程序,隻是增加一點記憶體占用(如果你沒有使用很多全局變量 

的話),而不是占用 40M 以上的記憶體。 

【NOTE2】 

程式   從   fork   開始分支   (稱分支不準确),   一路是主程序   pid   >   0   (pid   是子程序ID)   一路是子程序   pid   ==   0   自此分成兩個任務 

其實fork的時候已經兩個分支了,資料段被複制了一份,是以pid有兩份       

執行pid=fork()時,傳回值賦給pid在兩個程序中運作,       

fork會傳回給父程序的那個>0的值,告訴調用者建立程序的pid    

子程序的fork傳回值是0     

更不用說if...else的比較也是在兩個程序中都做的了    

【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()); 

結果是 

[[email protected] c]# ./a.out 

i am the child process, my process id is 4286 

i am the parent process, my process id is 4285 

一: 

要搞清楚fork的執行過程,就必須先講清楚作業系統中的“程序(process)”概念。一個程序,主要包含三個元素: 

o. 一個可以執行的程式; 

o. 和該程序相關聯的全部資料(包括變量,記憶體空間,緩沖區等等); 

o. 程式的執行上下文(execution context)。 

不妨簡單了解為,一個程序表示的,就是一個可執行程式的一次執行過程中的一個狀态。作業系統對程序的管理,典型的情況,是通過程序表完成的。程序表中的每一個表項,記錄的是目前作業系統中一個程序的情況。對于單 CPU的情況而言,每一特定時刻隻有一個程序占用 CPU,但是系統中可能同時存在多個活動的(等待執行或繼續執行的)程序。一個稱為“程式計數器(program counter, pc)”的寄存器,指出目前占用 CPU的程序要執行的下一條指令的位置。當分給某個程序的 CPU時間已經用完,作業系統将該程序相關的寄存器的值,儲存到該程序在程序表中對應的表項裡面;把将要接替這個程序占用 CPU的那個程序的上下文,從程序表中讀出,并更新相應的寄存器(這個過程稱為“上下文交換(process context switch)”,實際的上下文交換需要涉及到更多的資料,那和fork無關,不再多說,主要要記住程式寄存器 pc指出程式目前已經執行到哪裡,是程序上下文的重要内容,換出 CPU的程序要儲存這個寄存器的值,換入CPU的程序,也要根據程序表中儲存的本程序執行上下文資訊,更新這個寄存器)。 

好了,有這些概念打底,可以說fork了。當你的程式執行到下面的語句: 

pid=fork(); 

作業系統建立一個新的程序(子程序),并且在程序表中相應為它建立一個新的表項。新程序和原有程序的可執行程式是同一個程式;上下文和資料,絕大部分就是原程序(父程序)的拷貝,但它們是兩個互相獨立的程序!此時程式寄存器pc,在父、子程序的上下文中都聲稱,這個程序目前執行到fork調用即将傳回(此時子程序不占有CPU,子程序的pc不是真正儲存在寄存器中,而是作為程序上下文儲存在程序表中的對應表項内)。問題是怎麼傳回,在父子程序中就分道揚镳。 

父程序繼續執行,作業系統對fork的實作,使這個調用在父程序中傳回剛剛建立的子程序的pid(一個正整數),是以下面的if語句中pid<0, pid==0的兩個分支都不會執行。是以輸出i am the parent process... 

子程序在之後的某個時候得到排程,它的上下文被換入,占據 CPU,作業系統對fork的實作,使得子程序中fork調用傳回0。是以在這個程序(注意這不是父程序了哦,雖然是同一個程式,但是這是同一個程式的另外一次執行,在作業系統中這次執行是由另外一個程序表示的,從執行的角度說和父程序互相獨立)中pid=0。這個程序繼續執行的過程中,if語句中 pid<0不滿足,但是pid= =0是true。是以輸出i am the child process... 

為什麼看上去程式中互斥的兩個分支都被執行了?在一個程式的一次執行中,這當然是不可能的;但是你看到的兩行輸出是來自兩個程序,這兩個程序來自同一個程式的兩次執行。 

fork之後,作業系統會複制一個與父程序完全相同的子程序,雖說是父子關系,但是在作業系統看來,他們更像兄弟關系,這2個程序共享代碼空間,但是資料空間是互相獨立的,子程序資料空間中的内容是父程序的完整拷貝,指令指針也完全相同,但隻有一點不同,如果fork成功,子程序中fork的傳回值是0,父程序中fork的傳回值是子程序的程序号,如果fork不成功,父程序會傳回錯誤。 

可以這樣想象,2個程序一直同時運作,而且步調一緻,在fork之後,他們分别作不同的工作,也就是分岔了。這也是fork為什麼叫fork的原因。 

在程式段裡用了fork()之後程式出了分岔,派生出了兩個程序。具體哪個先運作就看該系統的排程算法了。 

如果需要父子程序協同,可以通過原語的辦法解決。 

二: 

程序的建立: 

建立一個程序的系統調用很簡單.我們隻要調用fork函數就可以了. 

#include <unistd.h> 

pid_t fork(); 

當一個程序調用了fork以後,系統會建立一個子程序.這個子程序和父程序不同的地方隻有他的程序ID和父程序ID,其他的都是一樣.就象父程序克隆 (clone)自己一樣.當然建立兩個一模一樣的程序是沒有意義的.為了區分父程序和子程序,我們必須跟蹤fork的傳回值. 當fork掉用失敗的時候(記憶體不足或者是使用者的最大程序數已到)fork傳回-1,否則fork的傳回值有重要的作用.對于父程序fork傳回子程序的 ID,而對于fork子程序傳回0.我們就是根據這個傳回值來區分父子程序的. 父程序為什麼要建立子程序呢?前面我們已經說過了Linux是一個多使用者作業系統,在同一時間會有許多的使用者在争奪系統的資源.有時程序為了早一點完成任務就建立子程序來争奪資源. 一旦子程序被建立,父子程序一起從fork處繼續執行,互相競争系統的資源.有時候我們希望子程序繼續執行,而父程序阻塞,直到子程序完成任務.這個時候我們可以調用wait或者waitpid系統調用. 

   總結一下有三: 

1,派生子程序的程序,即父程序,其pid不變; 

2,對子程序來說,fork傳回給它0,但它的pid絕對不會是0;之是以fork傳回0給它,是因為它随時可以調用getpid()來擷取自己的pid; 

3,fork之後父子程序除非采用了同步手段,否則不能确定誰先運作,也不能确定誰先結束。認為子程序結束後父程序才從fork傳回的,這是不對的,fork不是這樣的,vfork才這樣。 

【NOTE4】 

首先必須有一點要清楚,函數的傳回值是儲存在寄存器eax中的。 

其次,當fork傳回時,新程序會傳回0是因為在初始化任務結構時,将eax設定為0; 

在fork中,把子程序加入到可運作的隊列中,由程序排程程式在适當的時機排程運作。也就是從此時開始,目前程序分裂為兩個并發的程序。 

無論哪個程序被排程運作,都将繼續執行fork函數的剩餘代碼,執行結束後傳回各自的值。 

【NOTE5】 

對于fork來說,父子程序共享同一段代碼空間,是以給人的感覺好像是有兩次傳回,其實對于調用fork的父程序來說,如果fork出來的子程序沒有得到排程,那麼父程序從fork系統調用傳回,同時分析sys_fork知道,fork傳回的是子程序的id。再看fork出來的子程序,由 copy_process函數可以看出,子程序的傳回位址為ret_from_fork(和父程序在同一個代碼點上傳回),傳回值直接置為0。是以當子程序得到排程的時候,也從fork傳回,傳回值為0。 

    關鍵注意兩點:1.fork傳回後,父程序或子程序的執行位置。(首先會将目前程序eax的值做為傳回值)2.兩次傳回的pid存放的位置。(eax中) 

程序調用copy_process得到lastpid的值(放入eax中,fork正常傳回後,父程序中傳回的就是lastpid) 

子程序任務狀态段tss的eax被設定成0, 

fork.c 中 

p-&gt;tss.eax=0;(如果子程序要執行就需要程序切換,當發生切換時,子程序tss中的eax值就調入eax寄存器,子程序執行時首先會将eax的内容做為傳回值) 

當子程序開始執行時,copy_process傳回eax的值。 

fork()後,就是兩個任務同時進行,父程序用他的tss,子程序用自己的tss,在切換時,各用各的eax中的值. 

是以,“一次調用兩次傳回”是2個不同的程序! 

看這一句:pid=fork() 

當執行這一句時,目前程序進入fork()運作,此時,fork()内會用一段嵌入式彙編進行系統調用:int 0x80(具體代碼可參見核心版本0.11的unistd.h檔案的133行_syscall0函數)。這時進入核心根據此前寫入eax的系統調用功能号便會運作sys_fork系統調用。接着,sys_fork中首先會調用C函數find_empty_process産生一個新的程序,然後會調用C函數 copy_process将父程序的内容複制給子程序,但是子程序tss中的eax值指派為0(這也是為什麼子程序中傳回0的原因),當指派完成後, copy_process會傳回新程序(該子程序)的pid,這個值會被儲存到eax中。這時子程序就産生了,此時子程序與父程序擁有相同的代碼空間,程式指針寄存器eip指向相同的下一條指令位址,當fork正常傳回調用其的父程序後,因為eax中的值是新建立的子程序号,是以,fork()傳回子程序号,執行else(pid&gt;0);當産生程序切換運作子程序時,首先會恢複子程序的運作環境即裝入子程序的tss任務狀态段,其中的eax 值(copy_process中置為0)也會被裝入eax寄存器,是以,當子程序運作時,fork傳回的是0執行if(pid==0)。 

【NOTE5】 

了解它關鍵在于了解堆棧的切換和壓棧,彈棧! 

關于子程序的傳回: 

子程序複制了父程序的棧内容,從高到低 

SS 

ESP 

EFLAGS 

CS 

EIP -----此是int 0x80 的下一條指令,也是子程序開始執行的地方!!!! 

DS 

ES 

FS 

EDX 

ECX 

EBX 

GS 

ESI 

EDI 

EBP 

EAX(0) 

由于 EAX = 0,是以子程序傳回 0 給 fork. 

注:新程序的使用者棧設為其父程序的使用者棧(最後彈出的SS,ESP)。如果父子程序以copy_on_write方式共用使用者堆棧 

(Linux之下就是這樣的),而且在此之前父程序修改了該堆棧(如果父程序先傳回,這幾乎是肯定的),那麼,系統已經為父程序建立了該使用者棧的副本,父程序原來的使用者棧留給了子程序。那麼新程序的系統棧已經清空,新程序回到了使用者态,傳回到了函數fork。 

【NOTE6】 

關于fork的讨論與評價: 

fork好不好?相比其他作業系統如Windows,Windows會有諸如CreateProcess這樣的函數來建立一個與生俱來兩手空空的獨立的新程序。然後還有一大堆參數,指手畫腳的告訴你這個那個是什麼。。。 

煩!!! 

K.I.S.S. (Keep it simple,stupid.)是Unix的至高原則。 

fork 起源于 Unix 作業系統。那是貝爾實驗室的 K&R (這兩人是Unix和C語言之父) 的一項天才發明!!!   Linux由于與生俱來就與Unix血濃于水,是以繼承了它的這個天才發明。 

這種方法效率是很高的。因為複制的代價是很低的。在計算機網絡的實作中,以及在 client/server 系統中的server 一方的實作中,fork 常常是最自然,最有效,最适宜的手段。很多人甚至懷疑,到底是先有 fork 還是先有 client/server,因為 fork 似乎就是專門為此而設計的 !更重要的好處是,這樣有利于父子程序間通過 pipe 建立起一種簡單有效的程序間通信管道,并且産生了作業系統的使用者界面即 shell 的管道機制。這一點,對于 Unix 的發展和應用推廣,對于 Uinx 程式設計環境的形成,對于 Unix 程式設計風格的形成,都有非常深遠的影響。可以說這是一項天才的發明,它在很大程度上改變了作業系統的發展方向。

繼續閱讀