當for循環,fork(),printf()相遇時的思考

今天上午課間休息,教操作系統課的老韓看我沒事正在跟同學聊天,就過來給我出了這麼個問題:

"你看這兩段代碼運行結果有什麼不同麼?"

代碼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

順着走下來是不是沒有那麼難了呢?凡事就是這樣,想明白了再回頭會感覺沒什麼,但越怕去想就會越成爲一個巨大的障礙。

感謝韓芳溪老師啓發!

希望能幫到你,歡迎交流!




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