理解fork()的一次調用兩次執行

原文地址:http://blog.csdn.net/songxueyu/article/details/9115393

fork()函數是linux裏多進程編程的基礎,爲linux成爲強大的多用戶操作系統提供了強有力的支持。

但是對於很多初學者而言,雖然知道怎麼寫多進程的程序,知道怎麼fork()出一個子進程,卻很少有人能夠理解fork()的最有特點的一個性質:一次調用,兩次執行。

進程在內存裏有三部分的數據——代碼段、堆棧段和數據段。這三個部分是構成一個完整的執行序列的必要的部分。

代碼段——存放了程序代碼的內存空間。這個最容易理解,不就是程序在機器內的表示而已嘛。注意假如機器中有數個進程運行相同的一個程序,那麼它們就可以使用相同的代碼段。也就是說如果fork()出來了一個子進程,子進程和父進程實際上使用的是相同的代碼段

堆棧段——存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。比如說寫了這樣一個程序:

[cpp] view plain copy
  1. int a;  
  2. void main()  
  3. {  
  4.     int b;  
  5.     int c=func();  
  6. }  
  7.   
  8. int func()  
  9. {  
  10.     int d;  
  11.     return 0;  
  12. }  
這個程序裏哪些變量是存放在堆棧段裏的呢?不考慮編譯器優化,實際上變量b,c,d都是會存放在堆棧裏的。而a則會存放在接下來說的數據段裏。

數據段——存放程序的全局變量,常數以及動態數據分配的數據空間(比如用malloc之類的函數取得的空間)。

好了,知道了上面三個段之後有什麼用呢?用處可大了,這就說明了系統中的每一個進程都需要由這三個段來組成。不管是父進程還是fork()出來的子進程。但是上面也提到,由於子進程和父進程運行的是同樣的程序(只是程序裏的不同部分),它們使用相同的代碼段,但是會擁有各自的數據段和堆棧段。

我們通常會這樣寫一個程序:

[cpp] view plain copy
  1. void main()  
  2. {  
  3.     pid_t pid;  
  4.     pid=fork();  
  5.     if(pid==0)  
  6.     {  
  7.         //子進程任務  
  8.     }  
  9.     else if(pid>0)  
  10.     {  
  11.         //父進程任務  
  12.     }  
  13. }  
執行過程是這樣的:

1.操作系統分配內存給父進程,包括上面提到的三個段,就是會在堆棧段裏有一塊空間是用來存放pid變量的。

2.接着內核調度父進程執行fork()函數(這個函數裏實際上使用了系統調用),這時候子進程纔會出現,內核會將父進程的數據段和堆棧段作一個拷貝給子進程,注意這時子進程的堆棧段裏一定會有一個空間用來存放pid變量!然後系統調用成功,內核給父進程堆棧段裏的pid變量賦上子進程的pid號,而給子進程堆棧段裏的pid變量賦上0。

3.接下來還是交給內核調度決定執行的是子進程還是父進程(一般內核會先給子進程執行)。如果是父進程,它的下一句代碼就是判斷pid變量的大小,它會去它的堆棧段裏存放pid變量的地方取出pid來進行比較,它會發現pid>0,所以接下來它就去執行——父進程任務;如果是子進程,由於同樣的代碼段,它也會去比較它自己的pid變量,發現pid=0,所以接下來它會去執行——子進程任務。

注:左邊父進程,右邊子進程

這樣,fork()函數就實現了一次調用,兩次執行。關鍵就是在於父子進程擁有不同的堆棧段,而內核給這兩個堆棧段裏的pid賦上不同的值。

最後,我們來看看編譯後的彙編程序,就能證實我的說法,也能更好地理解。

esp是堆棧指針寄存器,可以看到,在調用fork()函數之後將28(%esp)和0進行了比較,顯然,28(%esp)就是pid變量。它存放在堆棧段裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章