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

文章屬於轉載: http://www.cnblogs.com/leoo2sk/archive/2009/12/11/talk-about-fork-in-linux.html

 

vfork(建立一個新的進程)
相關函數
wait,execve
表頭文件
#include<unistd.h>
定義函數
pid_t vfork(void);
函數說明
vfork()會產生一個新的子進程,其子進程會複製父進程的數據與堆棧空間,並繼承父進程的用戶代碼,組代碼,環境變量、已打開的文件代碼、工作目錄和資源限制等。Linux 使用copy-on-write(COW)技術,只有當其中一進程試圖修改欲複製的空間時纔會做真正的複製動作,由於這些繼承的信息是複製而來,並非指相同的內存空間,因此子進程對這些變量的修改和父進程並不會同步。此外,子進程不會繼承父進程的文件鎖定和未處理的信號。注意,Linux不保證子進程會比父進程先執行或晚執行,因此編寫程序時要留意
死鎖或競爭條件的發生。

返回值
如果vfork()成功則在父進程會返回新建立的子進程代碼(PID),而在新建立的子進程中則返回0。如果vfork 失敗則直接返回-1,失敗原因存於errno中。
錯誤代碼
EAGAIN 內存不足。ENOMEM 內存不足,無法配置核心所需的數據結構空間。
範例
#include<unistd.h>
main()
{
if(vfork() = =0)
{
printf(“This is the child process\n”);
}else{
printf(“This is the parent process\n”);
}
}
執行
this is the parent process
this is the child process

 

今天一位朋友去一個不錯的外企面試linux開發職位,面試官出了一個如下的題目:

      給出如下C程序,在linux下使用gcc編譯:

#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"

int main()
{
    pid_t pid1;
    pid_t pid2;

    pid1 = fork();
    pid2 = fork();

    printf("pid1:%d, pid2:%d\n", pid1, pid2);
}

      要求如下:

      已知從這個程序執行到這個程序的所有進程結束這個時間段內,沒有其它新進程執行。

      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爲根的進程樹:

驗證

      下面我們去linux下實際執行這個程序,來驗證我們的答案。

      程序如下圖:

      用gcc編譯、執行後結果如下:

      由於我們不太可能剛巧碰上PID分配到1001的情況,所以具體數值可能和答案有所差別。不過將這裏的2710看做基數的話,結果和我們上面的解答是一致的。

總結

      應該說這不是一道特別難或特別刁鑽的題目,但是由於fork函數運行機制的複雜性,造就了當兩個fork並排時,問題就變得很複雜。解這個題的關鍵,一是要對linux下進程的機制有一定認識,二是抓住上文提到的幾個關於fork的關鍵點。朋友說,這個題給的時間是5分鐘,應該說時間還算充裕,但是在面試的場合下,還是很考驗一個人對進程、fork的掌握程度和現場推理能力。

      希望本文能幫助朋友們對fork的執行機制有一個明晰的認識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章