今天上午課間休息,教操作系統課的老韓看我沒事正在跟同學聊天,就過來給我出了這麼個問題:
"你看這兩段代碼運行結果有什麼不同麼?"
代碼1:#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
int main()
{
printf("1 2 3");
fork();
}
代碼2:
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
int main()
{
printf("1 2 3\n");
fork();
}
直觀看好像兩段代碼都應該輸出“1 2 3”,只不過代碼2的“1 2 3”換行了而已,fork()出的子進程同樣擁有主進程的所有“狀態”,那麼既然都輸出了……好像還是沒什麼區別
“你說計算機應該怎麼處理‘\n’呢?”
把當前行都補滿空白字符?這樣下一次輸出就能新開一行?
“那你理解緩衝區的概念麼?”
buffer?好像我也只是用,沒了解過機制。
“課下去探索探索,試一試!”
帶着這些問題,首先了解一下printf()的打印機制。
雖然帶着“print”,這條命令處理起來卻應該分爲兩步,第一步,系統將printf()中傳進的數據寫進進程的一塊兒叫做緩衝區的內存上,第二步,對於標準輸出而言(行緩衝),系統判斷如果緩衝區中有“\n”,或是EOF,或是主動刷出,或是緩衝區滿,或是文件描述符關閉,或是exit,就會把數據從緩衝區中輸出出來(flush)。也就是說對於printf()代表的行緩衝而言,數據只是先被存儲在了緩衝區,直到碰見上述情況纔會輸出。如果緩衝區內有內容,注意,當進程退出時(exit)也會自動進行輸出,即使進程中並沒有printf()語句。
有了這些知識,再看兩段代碼,結果很明顯,代碼1中主進程的緩衝區因爲執行時沒有輸出條件,所以內容被子進程繼承,兩個進程結束時觸發exit,輸出緩衝區內容,結果爲
1 2 31 2 3
而代碼2因爲執行時有“\n”條件所以主進程輸出1 2 3後緩衝區刷空,子進程繼承來空的buffer自然無法有輸出,結果爲
1 2 3
理解了這個問題那麼兩段代碼分別交換一下fork()和printf() 這兩句的執行順序呢?
很輕鬆就能想到,因爲先fork()父子進程間沒有繼承來的buffer,兩個進程分別按規矩輸出就好
結果1:1 2 31 2 3
結果2:
1 2 3
1 2 3
那麼這樣呢?
代碼3:
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
int main()
{
fork();
printf("pid = %d, ppid = %d",getpid(),getppid());
fork();}
代碼4:
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
int main()
{
fork();
printf("pid = %d, ppid = %d\n",getpid(),getppid());
fork();
}
結果原理流程圖用圖片來說明,注意的是兩個fork()創建了4個進程,每個進程間的繼承關係要注意,要繼承的話也會繼承父進程緩衝區內容。結果的輸出順序取決於進程的執行順序,不一定固定。
代碼3流程:
代碼4流程:
理清了這個問題,讓我們再進一步,對於for循環中fork()+printf()怎麼理清分析呢?
代碼5:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
for(int i = 0;i < 2; i++)
{
fork();
printf("pid = %d, ppid = %d, i = %d",getpid(),getppid(),i);
}
}
代碼6:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
for(int i = 0;i < 2; i++)
{
fork();
printf("pid = %d, ppid = %d, i = %d\n",getpid(),getppid(),i);
}
}
對於主進程循環中的f第二次fork(),到底是隻從主進程中產生新的子進程,還是與第一次產生的子進程一起產生兩個新的子進程?
我們不妨把for循環拆成這樣#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int i = 0;
fork();
printf("pid = %d, ppid = %d, i = %d",getpid(),getppid(),i);
int i = 1;
fork();
printf("pid = %d, ppid = %d, i = %d",getpid(),getppid(),i);
}
有沒有發現很像我們討論的代碼3 和代碼4的情況呢?只是末尾多了一個printf()輸出,比這代碼3 4 的流程分析,我們很容易就得出輸出結果.
代碼5主進程輸出A X 0A X 1,fork1進程輸出B A 0B A 1,fork2繼承主進程buffer後又輸出自己的狀態A X 0C A 1,fork3繼承fork1後又輸出自己的狀態B A 0D B1。
代碼6
主進程輸出
A X 0
A X 1
fork1輸出
B A 0
B A 1
fork2輸出
C A 1
fork3輸出
D B 1
順着走下來是不是沒有那麼難了呢?凡事就是這樣,想明白了再回頭會感覺沒什麼,但越怕去想就會越成爲一個巨大的障礙。
感謝韓芳溪老師啓發!
希望能幫到你,歡迎交流!