今天一位朋友去一個不錯的外企面試linux開發職位,面試官出了一個如下的題目:
給出如下C程式,在linux下使用gcc編譯:
1 #include "stdio.h"
2
3 #include "sys/types.h"
4
5 #include "unistd.h"
6
7 int main()
8
9 {
10
11 pid_t pid1;
12
13 pid_t pid2;
14
15 pid1 = fork();
16
17 pid2 = fork();
18
19 printf("pid1:%d, pid2:%d\n", pid1, pid2);
20
21 }
要求如下:
已知從這個程式執行到這個程式的所有程序結束這個時間段内,沒有其它新程序執行。
1、請說出執行這個程式後,将一共運作幾個程序。
2、如果其中一個程序的輸出結果是“pid1:1001, pid2:1002”,寫出其他程序的輸出結果(不考慮程序執行順序)。
明顯這道題的目的是考察linux下fork的執行機制。下面我們通過分析這個題目,談談linux下fork的運作機制。
預備知識
這裡先列出一些必要的預備知識,對linux下程序機制比較熟悉的朋友可以略過。
1、程序可以看做程式的一次執行過程。在linux下,每個程序有唯一的PID辨別程序。PID是一個從1到32768的正整數,其中1一般是特殊程序init,其它程序從2開始依次編号。當用完32768後,從2重新開始。
2、linux中有一個叫程序表的結構用來存儲目前正在運作的程序。可以使用“ps aux”指令檢視所有正在運作的程序。
3、程序在linux中呈樹狀結構,init為根節點,其它程序均有父程序,某程序的父程序就是啟動這個程序的程序,這個程序叫做父程序的子程序。
4、fork的作用是複制一個與目前程序一樣的程序。新程序的所有資料(變量、環境變量、程式計數器等)數值都和原程序一緻,但是是一個全新的程序,并作為原程序的子程序。
解題的關鍵
有了上面的預備知識,我們再來看看解題的關鍵。我認為,解題的關鍵就是要認識到fork将程式切成兩段。看下圖:

上圖表示一個含有fork的程式,而fork語句可以看成将程式切為A、B兩個部分。然後整個程式會如下運作:
step1、設由shell直接執行程式,生成了程序P。P執行完Part. A的所有代碼。
step2、當執行到pid = fork();時,P啟動一個程序Q,Q是P的子程序,和P是同一個程式的程序。Q繼承P的所有變量、環境變量、程式計數器的目前值。
step3、在P程序中,fork()将Q的PID傳回給變量pid,并繼續執行Part. B的代碼。
step4、在程序Q中,将0賦給pid,并繼續執行Part. B的代碼。
這裡有三個點非常關鍵:
1、P執行了所有程式,而Q隻執行了Part. B,即fork()後面的程式。(這是因為Q繼承了P的PC-程式計數器)
2、Q繼承了fork()語句執行時目前的環境,而不是程式的初始環境。
3、P中fork()語句啟動子程序Q,并将Q的PID傳回,而Q中的fork()語句不啟動新程序,僅将0傳回。
解題
下面利用上文闡述的知識進行解題。這裡我把兩個問題放在一起進行分析。
1、從shell中執行此程式,啟動了一個程序,我們設這個程序為P0,設其PID為XXX(解題過程不需知道其PID)。
2、當執行到pid1 = fork();時,P0啟動一個子程序P1,由題目知P1的PID為1001。我們暫且不管P1。
3、P0中的fork傳回1001給pid1,繼續執行到pid2 = fork();,此時啟動另一個新程序,設為P2,由題目知P2的PID為1002。同樣暫且不管P2。
4、P0中的第二個fork傳回1002給pid2,繼續執行完後續程式,結束。是以,P0的結果為“pid1:1001, pid2:1002”。
5、再看P2,P2生成時,P0中pid1=1001,是以P2中pid1繼承P0的1001,而作為子程序pid2=0。P2從第二個fork後開始執行,結束後輸出“pid1:1001, pid2:0”。
6、接着看P1,P1中第一條fork傳回0給pid1,然後接着執行後面的語句。而後面接着的語句是pid2 = fork();執行到這裡,P1又産生了一個新程序,設為P3。先不管P3。
7、P1中第二條fork将P3的PID傳回給pid2,由預備知識知P3的PID為1003,是以P1的pid2=1003。P1繼續執行後續程式,結束,輸出“pid1:0, pid2:1003”。
8、P3作為P1的子程序,繼承P1中pid1=0,并且第二條fork将0傳回給pid2,是以P3最後輸出“pid1:0, pid2:0”。
9、至此,整個執行過程完畢。
所得答案:
1、一共執行了四個程序。(P0, P1, P2, P3)
2、另外幾個程序的輸出分别為:
pid1:1001, pid2:0
pid1:0, pid2:1003
pid1:0, pid2:0
進一步可以給出一個以P0為根的程序樹:
總結
應該說這不是一道特别難或特别刁鑽的題目,但是由于fork函數運作機制的複雜性,造就了當兩個fork并排時,問題就變得很複雜。解這個題的關鍵,一是要對linux下程序的機制有一定認識,二是抓住上文提到的幾個關于fork的關鍵點。朋友說,這個題給的時間是5分鐘,應該說時間還算充裕,但是在面試的場合下,還是很考驗一個人對程序、fork的掌握程度和現場推理能力。
希望本文能幫助朋友們對fork的執行機制有一個明晰的認識。