773
技術社區[雲棲]
從一道麵試題分析Linux進程+IO緩衝區機製
父子孫-兩次FORK,2的三次方
下麵的程序打印出多少個“*“ (小弟今年遇到的騰訊一麵麵試題,據說其他公司的麵試題中也有這個題目)
- #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個 *。
運行結果截圖:
如果把程序中的printf語句中加上一個\n符號,改為printf("*\n"),就是下麵的結果,打印出了6個*。
如果分析中有錯誤,歡迎指正!。
去騰訊麵試的前一兩天,我去隔壁宿舍聽到他說起這個問題。由於去他們宿舍有其他事,我沒注意他說的這個題目。後來騰訊一麵,麵試官居然問我這個問題。我想糟了,大腦一片空白,為什麼我當初沒注意這個問題呢? 於是我就被騰訊一麵刷了。

後來想想這個題目不難啊。這個題目考的就是linux進程分叉和IO緩衝機製,我以前看apue時總結過好不好,結果還是被刷了。人生真是寂寞如雪啊!
最後更新:2017-04-03 12:55:41