fork與vfork

 

 

先看一個fork的例子:html

int glob = 4;

int main(void) 
{
    int var, pid;
    var = 88; 

    if ((pid = fork()) < 0) {
        printf("vfork error");
        exit(-1);

    } else if (pid == 0) { /* 子進程 */
        var++;
        glob++;
        exit(0);
    }   

    printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var);

    return 0;
}

 

運行結果:c++

[root@localhost tmp]# ./a.out 
pid=15297, glob=4, var=88

可見,子進程修改的局部變量var和全局變量glob後,父進程是不可見的。shell

 

若是把代碼中的fork替換成vfork,再次運行,獲得的結果:函數

[root@localhost tmp]# ./a.out 
pid=15304, glob=5, var=89

可見,父進程共享了子進程的修改操做。性能

 

在使用vfork時,若是子進程使用return語句結束,會發生什麼呢?spa

int glob = 4;

int main(void) 
{
    int var, pid;
    var = 88; 

    if ((pid = vfork()) < 0) {
        printf("vfork error");
        exit(-1);

    } else if (pid == 0) { /* 子進程 */
        var++;
        glob++;
        return 0;
    }   

    printf("pid=%d, glob=%d, var=%d\n", getpid(), glob, var);

    return 0;
}

在個人機器上,致使了無限循環(直到vfork調用出錯),這是由於子進程調用return語句破壞了父進程的棧。設計

 

 

fork與vfork的區別:code

  • fork 是 建立一個子進程,並把父進程的地址空間copy到子進程中;
  • vfork是 建立一個子進程,並和父進程的地址空間share一塊兒用。

 

咱們知道,fork一般採用寫時複製技術(copy-on-write, COW)建立子進程,以提升進程clone的性能;但在更早尚未COW的年代,fork建立子進程時時須要完整的複製父進程地址空間到子進程中,若是咱們建立子進程的目的是爲了調用exec,那麼這種複製就顯得既低效又無必要。而vfork讓子進程共享父進程的地址空間,而不做克隆操做,就是爲了節省這種沒必要要的複製開銷。htm

回到上面return致使程序crash的例子,return會釋放局部變量,並彈棧,回到上級函數執行。exit直接退掉。若是你用c++ 你就知道,return會調用局部對象的析構函數,exit不會。(注:exit不是系統調用,是glibc對系統調用 _exit()或_exitgroup()的封裝)對象

可見,子進程調用exit() 沒有修改函數棧,因此,父進程得以順利執行。而子進程調用return,至關於在父進程的棧上執行了彈棧操做,父進程也就跪了。

 

注意:

一、vfork保證子進程先運行,在它調用exec或exit以後父進程纔可能被調度運行;

二、子進程在調用exec或exit以前是在父進程的地址空間中運行的。

可見,vfork的設計初衷是爲了應對那些子進程須要立刻調用exec的場景,所以不對父進程的地址空間作任何複製。

 

 


 

再看一個fork的有趣例子,

int main(void)
{
    int i, pid = 0;  

    for (i = 0; i < 2; i++) {

        pid = fork();

        if (pid == 0) {
            printf("pid:%d\n", getpid());
        }   
    }   

    return 0;
}

問題是,執行這段代碼,一共產生了幾個進程呢?

從執行結果來看,printf函數打印了3次,fork被調用了3次,連上main進程一共有4個進程。

 

再看下面這個例子,一共打印了多少個 「_」 呢?

int main(void)
{
    int i;

    for(i=0; i<2; i++){

        fork();

        printf("-");

    }

    return 0;
}

按照上面的例子,程序運行過程當中一共有4個進程,把main進程記爲A,則有

i=0時,A進程 fork調用,產生子進程B1,而後A、B1各打印一個"_";

i=1時,A進程 fork調用,產生子進程B2,而後A、B2各打印一個"_";

與此同時,B1進程fork調用,產生子進程C1,而後B一、C1各打印一個"_";

看起來,好像有6個"_"被打印了,但這段代碼的執行結果倒是8個,這是爲啥呢?

 

先來看下,這4個進程間的關係以下:

A --> B1  --> C1

 |--> B2

 

可見,B一、B2繼承自A,而C1繼承自B1。

一、B1是在i=0時複製A的,此時A尚未調用過printf函數;

二、B2是在i=1時複製A的,此時A已經調用過一次printf函數;

三、C1是在i=1時複製B1的,此時B1已經調用過一次printf函數;

咱們知道,fork進程會讓子進程完整複製父進程的地址空間,這也就包括了I/O緩衝區,這就是爲何最終打印了8個"_"的緣由。

 

 

參考文檔:

http://coolshell.cn/articles/12103.html

http://coolshell.cn/articles/7965.html

相關文章
相關標籤/搜索