gpt4 book ai didi

c - 使用 fork() 时,getline() 重复读取文件

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:32:51 24 4
gpt4 key购买 nike

我正在开发一个简单的 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 似乎会将底层文件描述符查找回应用程序实际读取到的位置,从而有效地消除读取缓冲的影响。这很重要,因为父项和子项的操作系统级文件描述符指向相同的文件描述,尤其是相同的文件偏移量。

POSIX description of fclose()有这个短语:

[CX] [Option Start] If the file is not already at EOF, and the file is one capable of seeking, the file offset of the underlying open file description shall be set to the file position of the stream if the stream is the active handle to the underlying file description.

(其中 CX means an extension to the ISO C standardexit() 当然会在所有流上运行 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 运行它清楚地显示了 child 正在寻找文件描述符:

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) = ?

(我没有看到用不涉及 fork 的代码的seek back,但我不知道为什么。)

那么,主程序上的 C 库从文件中读取数据 block ,然后应用程序打印第一行。 fork 之后,child 退出,并寻找 fd 回到应用程序级文件指针所在的位置。然后父级继续,处理读取缓冲区的其余部分,完成后,它继续从文件中读取。因为文件描述符被找回,所以从第二个开始的行再次可用。

在您的情况下,每次迭代中重复的 fork() 似乎会导致无限循环。

在子级中使用 _exit() 而不是 exit() 解决了问题在这种情况下,因为 _exit() 只退出进程,它不对 stdio 缓冲区进行任何管理。

使用 _exit(),任何输出缓冲区也不会刷新,因此您需要在 stdout 上手动调用 fflush()以及您正在写入的任何其他文件。

但是,如果您以相反的方式执行此操作,则子进程读取和缓冲的内容多于其处理的内容,那么子进程返回 fd 将很有用,这样父进程就可以从子进程实际离开的地方继续.

另一种解决方案是不将 stdiofork() 混合使用。

关于c - 使用 fork() 时,getline() 重复读取文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54912662/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com