linux系統下 fork()系統調用: 關於父子進程緩存問題的小坑

linux系統下 fork()系統調用, 關於父子進程緩存問題的小坑

1. 首先看一個簡單示例程序如下

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

int main(int argc, char **argv)
{
    printf("before fork: in father...\n");

    pid_t child = fork();
    assert( child >= 0 );

    if ( 0 == child )
        printf("in child...\n");

    if ( child > 0 )
        printf("after  fork: in father...\n");
}

在我本機測試, 輸出如下:

    before fork: in father...
    after  fork: in father...
    in child...

輸出和我們預想一致. 一共輸出3行. 目前一切正常.
請繼續往下看.

2. 這個示例程序和第一個基本相同, 但是輸出時, 換行符”\n”略有區別.

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

int main(int argc, char **argv)
{
    // 注意這裏沒有換行, 多加了幾個空格, 使得打印容易識別.
    printf("before fork: in father...    ");

    pid_t child = fork();
    assert( child >= 0 );

    if ( 0 == child )
        printf("in child...\n");

    if ( child > 0 )
        printf("after  fork: in father...\n");
}

程序輸出如下:

    before fork: in father...    after  fork: in father...
    before fork: in father...    in child...

這次輸出就不在我們意料之中了!!!
“before fork: in father… ”
是在fork()調用之前的父進程中調用輸出的.
但是竟然在子進程中也輸出了 !!!
感覺好像是在fork之前父進程中的printf打印的字符, 被copy到子進程中了?

沒錯, 正是如此.原理如下:

我們這次在fork之前printf打印語句時, 沒有帶”\n”參數( 終端是行緩存的. )
所以這條語句執行後, 打印信息並沒有被立刻輸出到屏幕.而是緩存起來了.
終端stdout是行緩存, 輸出只有在遇到’\n’字符,或者輸出超過緩存長度時, 纔會刷新緩存–真正寫入文件( 這裏的寫文件就是終端stdout )

這樣當調用fork() 函數時, 父進程中的輸出緩存還沒有刷新到文件.
fork()調用除了會複製父進程的所有已打開文件描述符, 還會複製父進程的緩存到子進程中.
所以父進程的緩存 “before fork: in father… ” 就被copy到子進程中了..
緩存複製到子進程空間後, 就像子進程自己調用了printf的效果一樣.
結果程序輸出就出現了上面詭異的一幕.

至於程序1爲什麼沒有出現這樣的一幕, 因爲在fork之前父進程printf 帶了’\n’, stdout是行緩存, 遇到’\n’時就會刷新輸出到stdout的緩存.
在fork時, 父進程的緩存已刷新到文件.緩存被刪除. 所以不會copy到子進程空間.

3. 這次對程序1 輸出重定向到文件, 查看結果.

重定向輸出到fork.log中, 代碼如下:

$   ./fork-out  > fork.log
$   cat fork.log

輸出如下:

    before fork: in father...
    after  fork: in father...
    before fork: in father...
    in child...

咦等等, 程序1 不是正常嗎, 爲什麼我僅僅重定向了一次, 結果就成這樣了.
不是說’\n’ 在終端stdout是行緩存的嗎, 遇到’\n’就會刷新輸出緩存!

是的, 之前說的沒錯. 請注意:”終端stdout是行緩存的”, 但是文件重定向之後, 這時候輸出stdout文件已經不是終端了, 而是重定向到了fork.log文件.
fork.log 是一個普通文件, 普通文件的緩存是基於長度的, 也就是說輸出到普通文件的數據, 在緩存後, 只有達到了緩存上限, 或者手動調用 flush/sync 等系統調用刷新, 纔會清空緩存並刷新到文件中.
而 “行緩存” 是隻有終端stdout纔有的.

同時注意終端stderr是沒有緩存的, 因爲輸出到stderr的信息都是敏感信息. 所以系統並不緩存.
但是在stderr重定向到文件後, 也會遇到相同的緩存級別改變的情況. 讀者感興趣請自行嘗試.

4. 所以正確做法是:

調用fork程序前, 手動調用flush/fflush函數手動刷新輸出緩存.
避免重定向後出現父子進程詭異的輸出copy問題.

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