簡體   English   中英

getline() 重復讀取文件,當使用 fork() 時

[英]getline() is repeatedly reading the file, when fork() is used

我正在開發一個簡單的 shell 程序,一個命令行解釋器,我想逐行讀取文件中的輸入,所以我使用了 getline() 函數。 第一次,程序正常工作,但是,當它到達文件末尾時,它不是終止,而是從頭開始讀取文件並無限運行。 以下是 main 函數中與 getline() 相關的一些代碼:

int main(int argc,char *argv[]){
    int const IN_SIZE = 255;
    char *input = NULL;
    size_t len = IN_SIZE;
    // get file address
    fileAdr = argv[2];

    // open file
    srcFile = fopen(fileAdr, "r");

    if (srcFile == NULL) {
        printf("No such file!\n");
        exit(-1);
    }

    while (getline( &input, &len, srcFile) != -1) {
        strtok(input, "\n");
        printf("%s\n", input);
        // some code that parses input, firstArgs == input
        execSimpleCmd(firstArgs);            
    }
    fclose(srcFile);
}

我在我的程序中使用 fork() 並且很可能它會導致這個問題。

void execSimpleCmd(char **cmdAndArgs) {

    pid_t pid = fork();
    if (pid < 0) {
        // error
        fprintf(stderr, "Fork Failed");
        exit(-1);
    } else if (pid == 0) {
        // child process
        if (execvp(cmdAndArgs[0], cmdAndArgs) < 0) {
            printf("There is no such command!\n");
        }
        exit(0);
    } else {
        // parent process
        wait(NULL);
        return;
    }
}

此外,有時程序會讀取和打印多行的組合。 例如,如果輸入文件如下:

ping
ww    
ls
ls -l
pwd

它會打印類似 pwdg、pwdww 等的內容。如何修復它?

在某些情況下,關閉FILE似乎會尋找底層文件描述符回到應用程序實際讀取的位置,從而有效地撤消讀取緩沖的影響。 這很重要,因為父級和子級的操作系統級文件描述符指向相同的文件描述,特別是相同的文件偏移量。

fclose()POSIX 描述有這句話:

[CX] [Option Start] 如果文件不在 EOF 處,並且該文件是能夠搜索的,如果流處於活動狀態,則底層打開文件描述的文件偏移量應設置為流的文件位置處理底層文件描述。

(其中CX 意味着對 ISO C 標准的擴展,並且exit()當然在所有流上運行fclose() 。)

我可以用這個程序(在 Debian 9.8 上)重現奇怪的行為:

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

#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char *argv[]){
    FILE *f;
    if ((f = fopen("testfile", "r")) == NULL) {
        perror("fopen");
        exit(1);
    }

    int right = 0;
    if (argc > 1)
        right = 1;

    char *line = NULL;
    size_t len = 0;
    // first line 
    getline(&line, &len, f);
    printf("%s", line);

    pid_t p = fork();
    if (p == -1) {
        perror("fork");
    } else if (p == 0) {
        if (right)
            _exit(0);  // exit the child 
        else
            exit(0);   // wrong way to exit
    } else {
        wait(NULL);  // parent
    }

    // rest of the lines
    while (getline(&line, &len, f) > 0) {
        printf("%s", line);
    }

    fclose(f);
}

然后:

$ printf 'a\nb\nc\n' > testfile
$ gcc -Wall -o getline getline.c
$ ./get
getline   getline2  
$ ./getline
a
b
c
b
c

使用strace -f ./getline運行它清楚地顯示了孩子正在尋找文件描述符:

clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f63794e0710) = 25117
strace: Process 25117 attached
[pid 25116] wait4(-1,  <unfinished ...>
[pid 25117] lseek(3, -4, SEEK_CUR)      = 2
[pid 25117] exit_group(1)               = ?

(我沒有看到不涉及分叉的代碼的回溯,但我不知道為什么。)

所以,發生的事情是主程序上的 C 庫從文件中讀取一個數據塊,應用程序打印第一行。 在 fork 之后,child 退出,並尋找 fd 回到應用程序級文件指針所在的位置。 然后父進程繼續,處理讀取緩沖區的其余部分,當它完成時,它繼續從文件中讀取。 因為文件描述符被找回,從第二個開始的行再次可用。

在您的情況下,每次迭代中重復的fork()似乎會導致無限循環。

在這種情況下,在子進程中使用_exit()而不是exit()解決了這個問題,因為_exit()只退出進程,它不會對 stdio 緩沖區做任何內務處理。

使用_exit() ,也不會刷新任何輸出緩沖區,因此您需要在stdout和您正在寫入的任何其他文件上手動調用fflush()

然而,如果你反過來這樣做,孩子閱讀和緩沖的時間比它處理的要多,那么孩子找回 fd 是有用的,這樣父母就可以從孩子實際離開的地方繼續。

另一種解決方案是不要將stdiofork()混合使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM