原文地址:http://blog.csdn.net/songxueyu/article/details/9115393
fork()函數是linux裏多進程編程的基礎,爲linux成爲強大的多用戶操作系統提供了強有力的支持。
但是對於很多初學者而言,雖然知道怎麼寫多進程的程序,知道怎麼fork()出一個子進程,卻很少有人能夠理解fork()的最有特點的一個性質:一次調用,兩次執行。
進程在內存裏有三部分的數據——代碼段、堆棧段和數據段。這三個部分是構成一個完整的執行序列的必要的部分。
代碼段——存放了程序代碼的內存空間。這個最容易理解,不就是程序在機器內的表示而已嘛。注意假如機器中有數個進程運行相同的一個程序,那麼它們就可以使用相同的代碼段。也就是說如果fork()出來了一個子進程,子進程和父進程實際上使用的是相同的代碼段。
堆棧段——存放的就是子程序的返回地址、子程序的參數以及程序的局部變量。比如說寫了這樣一個程序:
- int a;
- void main()
- {
- int b;
- int c=func();
- }
- int func()
- {
- int d;
- return 0;
- }
數據段——存放程序的全局變量,常數以及動態數據分配的數據空間(比如用malloc之類的函數取得的空間)。
好了,知道了上面三個段之後有什麼用呢?用處可大了,這就說明了系統中的每一個進程都需要由這三個段來組成。不管是父進程還是fork()出來的子進程。但是上面也提到,由於子進程和父進程運行的是同樣的程序(只是程序裏的不同部分),它們使用相同的代碼段,但是會擁有各自的數據段和堆棧段。
我們通常會這樣寫一個程序:
- void main()
- {
- pid_t pid;
- pid=fork();
- if(pid==0)
- {
- //子進程任務
- }
- else if(pid>0)
- {
- //父進程任務
- }
- }
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變量。它存放在堆棧段裏。