從一道面試題分析Linux進程+IO緩衝區機制

下面的程序打印出多少個“*“    (小弟今年遇到的騰訊一面面試題,據說其他公司的面試題中也有這個題目)

#include <unistd.h>
#include <stdio.h>

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


 

答案打印出8個*,而不是6個。

 

 


分析:


如果不考慮IO機制,一般可能會想,i = 0時,fork後有兩個進程,打印兩個*,i=1,時有四個進程,再打印4個*。所以一共6個。其實不是這樣的。

        進程機制:
         fork分叉後子進程似乎父進程的副本,子進程會複製父進程的數據空間、堆和棧。而共享代碼段。

         C語言標準IO的緩衝機制:
         C語言的標準IO是帶緩衝的,一般調用printf後,並不是立即把要打印的內容立即打印在控制檯界面上,而是輸出到一個緩衝區,對應標準輸出的叫標準輸出緩衝,對應標準出錯的叫標準出錯緩衝,當然還有標準輸入緩衝。

         緩衝機制:
         緩衝機制一般分爲:全緩衝、行緩衝、無緩衝。

  •          全緩衝:緩衝區滿了以後,才發生真正的IO。我們通常用的磁盤文件IO就是這樣的。當然你可以調用flush類函數強制刷新緩衝。
  •          行緩衝:緩衝區滿了以後或者緩衝區收到一個換行符(表示已輸入或輸出一行),後才發生真正的IO,比如標準輸出和標準輸入默認的緩衝機制就是行緩衝。(行緩衝還有一些規則,參考APUE)
  •          無緩衝:立即發生IO,通常標準出錯是不帶緩衝的。所以建議用輸出信息來調試程序時,最後用標準出錯IO,以免調試信息延遲輸出。

        上面三種我們都能用flush和關閉文件之類的函數強制刷新緩衝。C語言還提供了接口讓我們改變默認的緩衝機制,以及緩衝區的大小。

       回到我們的問題,根據linux進程機制,題目中的標準輸出文件描敘符也是會被子進程複製的。所以fork後所有的進程共享一個控制檯窗口(這個解釋好像有點多餘,看不懂跳過)。IO的緩衝區是用malloc申請的,是屬於堆區,所以也是子進程要從父進程複製的。那麼i= 0時,有兩個進程,他們各自輸出了一個*,而且由於標準輸出默認採用的是行緩衝機制,所以此時,它們只是各自把一個*複製到了標準輸出的緩衝區,沒有打印到c控制檯窗口上。 接下來是關鍵時刻,接下來 i = 1,再一個運行到fork()後,子進程複製父進程的堆區,所以後面出生的子進程也和父進程有着一樣的標準輸出緩衝區,而且標準輸出緩衝區中同樣也有着一個*。然後運行大printf語句,所有的進程都又各自向自己的標準輸出緩衝區輸送了一個*。  然後 i=2,程序結束了,標準輸出緩衝區的內容打印到控制檯窗口上,你就看到幾個*了。
        好的,我們現在統計一下。每個進程都一個輸出了幾個*。
        我們把進程按照出生的先後分爲兩撥,第一波是i = 0時就出現的,包括主線程和第一個字進程,在整個循環中他們向自己的標準輸出緩衝區都輸出了兩個*。
        第二波是在i = 1時產生的兩個子進程,它們各自像自己的標準輸出緩衝區輸出了一個*,但是由於它們從父進程的緩衝區裏複製了一個*,所以它們的標準輸出緩衝區中都有兩個*。
        最後程序結束,關閉標準輸出文件時,IO發生,一共向控制檯窗口打印了:兩個x兩個 + 兩個x兩個 = 8個 *。

運行結果截圖
185719r7g3jpvpvgba05ja.png 

如果把程序中的printf語句中加上一個\n符號,改爲printf("*\n"),就是下面的結果,打印出了6個*。



PS:不同平臺默認的緩衝機制可能不一樣,上面總結的是一般情況,我當初在win7上和ubuntu12.04上測試的結果是相符的。

如果分析中有錯誤,歡迎指正!。



       去騰訊面試的前一兩天,我去隔壁宿舍聽到他說起這個問題。由於去他們宿舍有其他事,我沒注意他說的這個題目。後來騰訊一面,面試官居然問我這個問題。我想糟了,大腦一片空白,爲什麼我當初沒注意這個問題呢? 於是我就被騰訊一面刷了。
後來想想這個題目不難啊。這個題目考的就是linux進程分叉和IO緩衝機制,我以前看apue時總結過好不好,結果還是被刷了。人生真是寂寞如雪啊

發佈了134 篇原創文章 · 獲贊 243 · 訪問量 54萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章