一個頗有趣的fork面試程序,和你們分享下經驗

爲了暑期的騰訊實習,拿到offer以後一直在linux的道路上不斷學習。
這兩天逛了下酷殼大神的blog(http://coolshell.cn/articles/7965.html),偶然看到一個關於fork小問題,雖然以前想通了,不過仍是值得回味而且和你們分享下的。html

你們有興趣能夠想一想,下面輸出了多少個「g」?linux

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
   int i;
   for(i=0; i<2; i++){
      fork();
      printf("g");
   }

   wait(NULL);
   wait(NULL);

   return 0;
}

也許你很快就說,那麼簡單,第一次循環,fork後2個進程,第二次再fork出4個,一共6個進程,確定是6個「g」面試

恭喜你,若是我是面試官,那麼你已經跪了 = =shell

答案:會輸出8個「g」bash

有一個很重要的東西是,在fork()的調用處,整個父進程空間會原模原樣地複製到子進程中,包括指令,變量值,程序調用棧,環境變量,緩衝區,等等。oop

等等!!什麼,你說所有複製過去?那麼我能夠解釋爲:
上面的那個程序爲何會輸入8個「-」,這是由於printf(「-「);語句有buffer學習

咱們來試試printf的緩衝區:spa

#include <stdio.h>
#include <unistd.h>    //for sleep()

int main(void)
{
    printf("/nstart the dead loop/n");
    while(1)
    {    
        printf("/b->");
        fflush(stdout);//刷新輸出緩衝區
        usleep(100000);
    }
    return 1;
}

哦哦!!若是不作fflush這個動做,上邊的輸出便不會顯示到屏幕上咯?
除非其中有換行操做或者緩衝區,這也許就是所謂的「到終端行規程」!code

呃呃,不要脫離了重點,咱們仍是回到剛纔的fork問題上~htm

既然咱們知道了printf有緩衝區,那麼,又如何?

容我引用下酷殼大神美麗的圖解:

clipboard.png

你能夠清楚的看到,用陰影框標註出來的兩個「最終版子進程(i=2)」

他們在fork拷貝的時候,就將 parent的緩衝區一併拷貝過去,這時「g」還在緩衝區
那麼,在輸出的時候,兩個子進程才一併把「g」從緩衝區輸出來,那麼是否是兩個子進程沒人多輸出了一個?
1+1+1+1+12+12=8個

OK,我知道你和我同樣可能有點懵懵的,我改下程序咯~

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
   int i;
   for(i=0; i<2; i++){
      fork();
      printf("g  ppid=%d, pid=%d, i=%d ", getppid(), getpid(), i);
   }

   wait(NULL);
   wait(NULL);

   return 0;

你能夠gcc下這個程序,在個人電腦裏輸出是這樣的:

g  ppid=3082, pid=3128, i=0 g  ppid=3128, pid=3130, i=1 
g  ppid=3128, pid=3129, i=0 g  ppid=3129, pid=3131, i=1 
g  ppid=3128, pid=3129, i=0 g  ppid=3128, pid=3129, i=1 
g  ppid=3082, pid=3128, i=0 g  ppid=3082, pid=3128, i=1

能夠看到ppid爲3082 pid爲3128的輸出有3次
一樣的,ppid爲3128 pid爲3129的輸出有3次

你將顯然發現,本來i=0時應該只會有兩個「g」被輸出,可是如今竟然詭異地多出了兩個?究竟是哪兩個呢?
我索性標註下這個輸出結果吧~

(1)g  ppid=3082, pid=3128, i=0 (5)g  ppid=3128, pid=3130, i=1 
(2)g  ppid=3128, pid=3129, i=0 (6)g  ppid=3129, pid=3131, i=1 
(3)g  ppid=3128, pid=3129, i=0 (7)g  ppid=3128, pid=3129, i=1 
(4)g  ppid=3082, pid=3128, i=0 (8)g  ppid=3082, pid=3128, i=1

明顯的,(1)和(4)是「不可能」有兩個的,(2)和(3)也是,如今你知道了吧,i=0時的兩個進程,在下一次fork的時候各自成爲了下一個父進程,在這一次fork時,他們的信息被徹底複製到了子進程,那麼最後一步每個子進程緩衝區裏還存有多餘的「g」(見上圖)

你還能夠用pstree -p | grep fork命令試下
能夠得出一個樹狀的結構:

$ pstree -p | grep fork
|-bash(3082)-+-fork(3128)-+-fork(3129)---fork(3131)
|            |            `-fork(3130)

那麼,最後咱們再試試,在printf一個g的後面加上一個換行符(或者fflush)

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
   int i;
   for(i=0; i<2; i++){
      fork();
      printf("g\n");
   }

   wait(NULL);
   wait(NULL);

   return 0;
}

輸出的結果將是換行的6個「g
緣由就在於,每一次換行,緩衝區的「g」,就會把數據「刷」出緩衝區

(其實或是EOF,或是緩中區滿,或是文件描述符關閉,或是主動flush,或是程序退出)

下次你們在面試的時候若是遇到這個問題,相信均可以迎刃而解了吧 ~~~ (再次感謝酷殼大神,你們對個人見解若是有問題歡迎交流!)

相關文章
相關標籤/搜索