Unix/Linux中的fork函數

fork函數介紹

  一個現有進程能夠調用fork函數建立一個新進程。該函數定義以下:html

#include <unistd.h>
pid_t fork(void);
// 返回:若成功則在子進程中返回0,在父進程中返回子進程ID,若出錯則返回-1

  fork函數調用一次,返回兩次。它在調用進程(稱爲父進程)中返回一次,返回值是新派生進程(稱爲子進程)的進程ID號;在子進程中返回一次,返回值爲0。所以,返回值自己告知當前進程是子進程仍是父進程。linux

  fork在子進程返回0而不是父進程的進程ID的緣由在於:任何子進程只有一個父進程,並且子進程老是能夠經過調用getppid取得父進程的進程ID。相反,父進程能夠有不少子進程,並且沒法獲取各個子進程的進程ID。若是父進程想要跟蹤全部子進程的進程ID,那麼它必須記錄每次調用fork的返回值。另外,進程ID 0老是由內核交換進程使用,因此一個子進程的進程ID不可能爲0。面試

  子進程和父進程繼續執行fork調用以後的指令。子進程是父進程的副本。例如,子進程得到父進程數據空間、堆和棧的副本。父、子進程並不共享這些存儲空間部分。父、子進程共享代碼段。算法

  因爲在fork以後常常跟隨着exec,因此如今的不少實現並不執行一個父進程數據段、堆和棧的徹底複製。做爲替代,使用了寫時複製(Copy-On-Write, COW)技術。這些區域由父、子進程共享,並且內核將它們的訪問權限改變爲只讀的。若是父、子進程中的任一個試圖修改這些區域,則內核只爲修改區域的那塊內存製做一個副本,一般是虛擬存儲器系統的一「頁」。shell

  使fork失敗的兩個主要緣由是:1)系統中已經有太多的進程(一般意味着某個方面出了問題);2)實際用戶的進程總數超過了系統限制。編程

附:exec函數

  用fork函數建立子進程後,子進程每每要調用一種exec函數以執行另外一個程序。當進程調用一種exec函數時,該進程執行的程序徹底替換爲新程序,而新程序則從其main函數開始執行。由於調用exec並不建立新進程,因此先後的進程ID並未改變。exec函數只是用一個全新的程序替換了當前進程的正文、數據、堆和棧端。緩存

  關於exec函數詳細的請參考:ide

  APUE 8.10節函數

  進程控制spa

一個簡單的例子

  簡單示例程序以下:

 1 #include <unistd.h>
 2 #include <stdio.h>
 3 
 4 int main()
 5 {
 6     pid_t fpid;
 7     fpid = fork();
 8     
 9     if(fpid < 0)
10     {
11         printf("error in fork!");
12     }
13     else if(fpid == 0)
14     {
15         printf("\n=================================================\n");
16         printf("I am the child process, my process ID is %d\n", getpid());
17         printf("My parent process ID is %d\n", getppid());
18         printf("=================================================\n");
19     }
20     else
21     {
22         printf("\n=================================================\n");
23         printf("I am the parent process, my process ID is %d\n", getpid());
24         printf("=================================================\n");
25         sleep(5);
26     }
27 
28     return 0;
29 }

  程序的運行結果是:

  

  按照慣常,程序按順序執行,最終輸出應該只有if...else if...else中一個條件下的結果,但很明顯咱們這邊輸出了兩個條件下的結果。具體緣由在於經過fork函數建立的子進程也會複製父進程的存儲空間(數據、堆、棧等,包括程序計數器),建立了屬於本身的存儲空間,並從fork函數後開始執行。利用pstree命令能夠看到子進程(ID 18406)確實繼承自父進程(ID 18405):

  

  通常來講,在fork以後是父進程先執行仍是子進程先執行是不肯定的,這取決於內核所使用的調度算法。在上述程序中,父進程先執行,子進程在其以後執行。

關於fork面試題

題目及答案  

  下邊的一道題摘自陳皓的博文

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 int main()
 6 {
 7     int i;
 8     for(i = 0; i < 2; i++)
 9     {
10         fork();
11         printf("-");
12     }
13 
14     sleep(10);
15     return 0;
16 }

  請問上述程序會輸出幾個‘-’?6個仍是8個?

  若是咱們不考慮printf函數的緩存效果,程序的最終輸出是6個‘-’。但由於printf函數有緩存的效用,最終致使輸出了8個‘-’。具體緣由可參照陳皓的博文

不考慮printf函數緩存效用

  爲了去除printf函數緩存效用,咱們稍微改動一下程序:

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 int main()
 6 {
 7     int i;
 8     for(i = 0; i < 2; i++)
 9     {
10         fork();
11         printf("-\n");
12     }
13 
14     sleep(10);
15     return 0;
16 }

  這下輸出就正確了。下邊兩圖是程序輸出結果和相應的進程(forktest3)樹狀圖:

  

  

更直觀的解析

  爲了更直觀,咱們能夠修改程序以下:

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 int main()
 6 {
 7     int i;
 8     for(i = 0; i < 2; i++)
 9     {
10         fork();
11         printf("ppid=%d, pid=%d, i=%d \n", getppid(), getpid(), i);
12     }
13 
14     sleep(10);    //讓進程停留十秒,這樣咱們能夠用pstree查看一下進程樹
15     return 0;
16 }

  輸出結果以下:

  

  陳皓還給出了圖示解釋:

  

  上圖中,相同顏色的是同一個進程。

  而對於printf("-");這個語句,咱們就能夠很清楚的知道,哪一個子進程複製了父進程標準輸出緩中區裏的的內容,而致使了屢次輸出了。以下圖所示,就是陰影並雙邊框了那兩個子進程:

  

其餘可參考資料

  Forking vs Threading

  孤兒進程與殭屍進程[總結]

  從一道面試題談linux下fork的運行機制

  經典的 Fork 炸彈解析

本文參考資料

  《UNIX環境高級編程 第二版》

  一個fork的面試題

相關文章
相關標籤/搜索