我正在开发一个简单的shell程序,命令行解释器,我想逐行读取文件输入,因此我使用了getline()函数。但是,该程序首次正常工作,但是,当它到达文件的末尾时,它不是终止,而是从开始读取文件,并且无限地运行。以下是主要功能中的一些代码,与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("%sn", input);
// some code that parses input, firstArgs == input
execSimpleCmd(firstArgs);
}
fclose(srcFile);
}
我在程序中使用叉(),很可能会导致此问题。
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
会在某些情况下查找基础文件描述符回到应用程序实际读取的位置,从而有效地消除了读取缓冲的效果。这很重要,因为父母的OS级文件描述符和子女指向相同的文件描述,尤其是相同的文件偏移。
fclose()
的POSIX描述具有以下短语:
[cx] [选项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 'anbncn' > 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库读取文件中的数据块,并且应用程序打印了第一行。叉子后,孩子退出,并将FD寻求回到应用级文件指针所在的位置。然后,父母继续,处理读取缓冲区的其余部分,并且完成后,它会继续从文件中读取。由于文件描述符已回溯,所以从第二个开始的行可以再次可用。
在您的情况下,每次迭代中重复的fork()
似乎都会导致无限循环。
在儿童中使用_exit()
代替exit()
解决了问题在这种情况下,由于_exit()
仅退出了该过程,因此它不会对STDIO缓冲区进行任何管家。
使用_exit()
,任何输出缓冲区也不会冲洗,因此您需要在stdout
和您写的任何其他文件上手动致电fflush()
。
但是,如果您以相反的方式进行此操作,而孩子的阅读和缓冲远远超过其处理,那么对孩子来说,寻找FD将很有用,以便父母可以继续从孩子真正离开的地方继续。
另一种解决方案不是将stdio
与fork()
。