fork的傳回值

來源:互聯網
上載者:User

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

結果是 

[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的執行過程,就必須先講清楚作業系統中的“進程(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 程式設計風格的形成,都有非常深遠的影響。可以說這是一項天才的發明,它在很大程度上改變了作業系統的發展方向。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.