今天一位朋友去一個不錯的外企面試linux開發職位,面試官出了一個以下的題目:
linux
給出以下C程序,在linux下使用gcc編譯:面試
#include "stdio.h"shell
#include "sys/types.h"函數
#include "unistd.h"3d
int main()cdn
{blog
pid_t pid1;繼承
pid_t pid2;進程
pid1 = fork();開發
pid2 = fork();
printf("pid1:%d, pid2:%d\n", pid1, pid2);
}
要求以下:
已知從這個程序執行到這個程序的全部進程結束這個時間段內,沒有其它新進程執行。
一、請說出執行這個程序後,將一共運行幾個進程。
二、若是其中一個進程的輸出結果是「pid1:1001, pid2:1002」,寫出其餘進程的輸出結果(不考慮進程執行順序)。
明顯這道題的目的是考察linux下fork的執行機制。下面咱們經過分析這個題目,談談linux下fork的運行機制。
預備知識
這裏先列出一些必要的預備知識,對linux下進程機制比較熟悉的朋友能夠略過。
一、進程能夠看作程序的一次執行過程。在linux下,每一個進程有惟一的PID標識進程。PID是一個從1到32768的正整數,其中1通常是特殊進程init,其它進程從2開始依次編號。當用完32768後,從2從新開始。
二、linux中有一個叫進程表的結構用來存儲當前正在運行的進程。可使用「ps aux」命令查看全部正在運行的進程。
三、進程在linux中呈樹狀結構,init爲根節點,其它進程均有父進程,某進程的父進程就是啓動這個進程的進程,這個進程叫作父進程的子進程。
四、fork的做用是複製一個與當前進程同樣的進程。新進程的全部數據(變量、環境變量、程序計數器等)數值都和原進程一致,可是是一個全新的進程,並做爲原進程的子進程。
解題的關鍵
有了上面的預備知識,咱們再來看看解題的關鍵。我認爲,解題的關鍵就是要認識到fork將程序切成兩段。看下圖:
上圖表示一個含有fork的程序,而fork語句能夠當作將程序切爲A、B兩個部分。而後整個程序會以下運行:
step一、設由shell直接執行程序,生成了進程P。P執行完Part. A的全部代碼。
step二、當執行到pid = fork();時,P啓動一個進程Q,Q是P的子進程,和P是同一個程序的進程。Q繼承P的全部變量、環境變量、程序計數器的當前值。
step三、在P進程中,fork()將Q的PID返回給變量pid,並繼續執行Part. B的代碼。
step四、在進程Q中,將0賦給pid,並繼續執行Part. B的代碼。
這裏有三個點很是關鍵:
一、P執行了全部程序,而Q只執行了Part. B,即fork()後面的程序。(這是由於Q繼承了P的PC-程序計數器)
二、Q繼承了fork()語句執行時當前的環境,而不是程序的初始環境。
三、P中fork()語句啓動子進程Q,並將Q的PID返回,而Q中的fork()語句不啓動新進程,僅將0返回。
解題
下面利用上文闡述的知識進行解題。這裏我把兩個問題放在一塊兒進行分析。
一、從shell中執行此程序,啓動了一個進程,咱們設這個進程爲P0,設其PID爲XXX(解題過程不需知道其PID)。
二、當執行到pid1 = fork();時,P0啓動一個子進程P1,由題目知P1的PID爲1001。咱們暫且無論P1。
三、P0中的fork返回1001給pid1,繼續執行到pid2 = fork();,此時啓動另外一個新進程,設爲P2,由題目知P2的PID爲1002。一樣暫且無論P2。
四、P0中的第二個fork返回1002給pid2,繼續執行完後續程序,結束。因此,P0的結果爲「pid1:1001, pid2:1002」。
五、再看P2,P2生成時,P0中pid1=1001,因此P2中pid1繼承P0的1001,而做爲子進程pid2=0。P2從第二個fork後開始執行,結束後輸出「pid1:1001, pid2:0」。
六、接着看P1,P1中第一條fork返回0給pid1,而後接着執行後面的語句。然後面接着的語句是pid2 = fork();執行到這裏,P1又產生了一個新進程,設爲P3。先無論P3。
七、P1中第二條fork將P3的PID返回給pid2,由預備知識知P3的PID爲1003,因此P1的pid2=1003。P1繼續執行後續程序,結束,輸出「pid1:0, pid2:1003」。
八、P3做爲P1的子進程,繼承P1中pid1=0,而且第二條fork將0返回給pid2,因此P3最後輸出「pid1:0, pid2:0」。
九、至此,整個執行過程完畢。
所得答案:
一、一共執行了四個進程。(P0, P1, P2, P3)
二、另外幾個進程的輸出分別爲:
pid1:1001, pid2:0
pid1:0, pid2:1003
pid1:0, pid2:0
進一步能夠給出一個以P0爲根的進程樹:
驗證
下面咱們去linux下實際執行這個程序,來驗證咱們的答案。
程序以下圖:
用gcc編譯、執行後結果以下:
因爲咱們不太可能剛巧碰上PID分配到1001的狀況,因此具體數值可能和答案有所差異。不過將這裏的2710看作基數的話,結果和咱們上面的解答是一致的。
總結
應該說這不是一道特別難或特別刁鑽的題目,可是因爲fork函數運行機制的複雜性,造就了當兩個fork並排時,問題就變得很複雜。解這個題的關鍵,一是要對linux下進程的機制有必定認識,二是抓住上文提到的幾個關於fork的關鍵點。朋友說,這個題給的時間是5分鐘,應該說時間還算充裕,可是在面試的場合下,仍是很考驗一我的對進程、fork的掌握程度和現場推理能力。
但願本文能幫助朋友們對fork的執行機制有一個明晰的認識。