一個現有進程能夠調用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)實際用戶的進程總數超過了系統限制。編程
用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以後是父進程先執行仍是子進程先執行是不肯定的,這取決於內核所使用的調度算法。在上述程序中,父進程先執行,子進程在其以後執行。
下邊的一道題摘自陳皓的博文:
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函數緩存效用,咱們稍微改動一下程序:
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("-");這個語句,咱們就能夠很清楚的知道,哪一個子進程複製了父進程標準輸出緩中區裏的的內容,而致使了屢次輸出了。以下圖所示,就是陰影並雙邊框了那兩個子進程:
《UNIX環境高級編程 第二版》