2017-05-17 47 views
4

我正在試驗一些用於shell實現的C代碼,發現fgets()在我分叉進程後返回重複行,我無法理解,我將不勝感激任何幫助。使用fgets讀取輸入將返回C中的重複行

我的問題是:分叉是否更改父進程中打開文件中的偏移量?這似乎發生在我的程序中。

從下面@Vadim波諾馬廖夫和我的理解答案: 與fgets()不是線程安全的(或嚴格來說,它是,但把一個進程導致標準輸入以某種方式被初始化,導致共享文件偏移的更改)。

的代碼是這樣的:

int main() { 

    char buf[200]; 
    int r; 
    pid_t pid = 0; 

    while(getcmd(buf, 200, pid) >= 0) { 
    fprintf(stderr, "current pid: %d\n", getpid()); 
    pid = fork(); 
    // Without forking the fgets() reads all lines normally 
    if(pid == 0) 
     exit(0); 

    wait(&r); 
    } 

    return 0; 
} 

的getcmd()函數只是一個包裝:

int 
getcmd(char *buf, int nbuf, pid_t pid) 
{ 
    memset(buf, 0, nbuf); 
    if (fgets(buf, nbuf, stdin) == NULL) { 
    fprintf(stderr, "EOF !!!\n"); 
    return -1; 
    } 
    fprintf(stderr, "pid: %d -- getcmd buf ======= --> %s\n", getpid(), buf); 
    return 0; 
} 

我也有一個輸入文件臨時用一些隨機文本:

line 1 
line 2 
line 3 

編譯後,我跑a.out < temp,輸出顯示6行被打印,通常一些行被複制。但是,如果我刪除線

pid = fork() 
... 

然後輸出變爲正常(僅示出所有的線一個接一個,這意味着與fgets()被調用3次)。

任何想法是什麼問題?

輸出(這是什麼讓):

pid: 10361 -- getcmd buf ======= --> line1 

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line2 

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line3 

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line2 

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line3 

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line3 

current pid: 10361 
EOF !!! 

而且我希望看到這一點:

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line1 

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line2 

current pid: 10361 
pid: 10361 -- getcmd buf ======= --> line3 

EOF 

一個編譯版本以供參考:

#include <stdio.h> 
#include <stdlib.h> 
#include <wait.h> 
#include <zconf.h> 
#include <unistd.h> 
#include <memory.h> 

int 
getcmd(char *buf, int nbuf, pid_t pid) 
{ 
    memset(buf, 0, nbuf); 
    if (fgets(buf, nbuf, stdin) == NULL) { 
    fprintf(stderr, "EOF !!!\n"); 
    return -1; 
    } 
    fprintf(stderr, "pid: %d -- getcmd buf ======= --> %s\n", getpid(), buf); 
    return 0; 
} 

int main() { 

    char buf[200]; 
    int r; 
    pid_t pid = 0; 

    while(getcmd(buf, 200, pid) >= 0) { 
    fprintf(stderr, "current pid: %d\n", getpid()); 
    pid = fork(); 
    // Without forking the fgets() reads all lines normally 
    if(pid == 0) 
     exit(0); 

    wait(&r); 
    } 

    return 0; 
} 

謝謝!

+0

能否請您編輯你的問題*顯示*實際(和預期)輸出(完整,複製粘貼爲文本)?還請包括一個實際的[最小,完整和可驗證示例](http://stackoverflow.com/help/mcve),這是我們可以輕鬆地複製和測試自己的東西。 –

+0

@Someprogrammerdude嗨,我添加了輸出。總之,我不希望看到重複的行被讀取。 –

+0

父母和孩子正在共享相同的文件描述符,你期望進入關於標準輸入的分叉是什麼? –

回答

4
  1. 它已經提到,父母和孩子共享當前位置文件描述符0(標準輸入)
  2. 似乎對流的libc運行時初始化(標準輸入,標準輸出,標準錯誤)包含了一些東西改變當前標準輸入位置:

    > strace -f ./a.out < temp 2>&1 | less 
    .... 
    write(2, "pid: 29487 -- getcmd buf ======="..., 45pid: 29487 -- getcmd buf ======= --> line 1 
    clone(child_stack=0,flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,child_tidptr=0x7f34940f19d0) = 29488 
    Process 29488 attached 
    [pid 29487] wait4(-1, <unfinished ...> 
    [pid 29488] lseek(0, -14, SEEK_CUR)  = 7 
    [pid 29488] exit_group(0)    = ? 
    [pid 29488] +++ exited with 0 +++ 
    <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29488 
    

請注意lseek的(0,-14,SEEK_CUR)在兒童(PID 29488)

  • 因此,在我的環境中(openSUSE Leap 42.2,glibc-2.22-4.3。1)該程序無限循環,並有在所有

  • 改變與fgets沒有EOF()讀取()中的示例

    .... 
    if (read(0, buf, nbuf) == 0) { 
    .... 
    while(getcmd(buf, 7, pid) >= 0) { 
    .... 
    
  • 和程序運行正常(三線和EOF)

    1. 並再次運行strace -f - 不再需要兒童的lseek()!

    2. 結論 - 似乎流功能(在stdio.h中聲明)必須非常謹慎(在這個例子中一樣)使用多進程環境中,因爲很多副作用

    +0

    感謝您的好評! –

    +0

    其實我也嘗試使用pos = lseek(0,0,SEEK_CUR)獲取當前偏移量,並在lseek(0,pos,SEEK_SET)之後重置wait()後的文件位置。它也變成了工作。 –