我正在开发一个简单的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
会将底层文件描述符追回到应用程序实际读取的位置,从而有效地消除了读取缓冲的影响。这很重要,因为父级和子级的OS级文件描述符指向相同的文件描述,特别是相同的文件偏移。
POSIX description of fclose()
有这句话:
[CX] [选项开始]如果文件尚未处于EOF,并且文件是能够搜索的文件,则如果流是活动的,则基础打开文件描述的文件偏移量应设置为流的文件位置处理基础文件描述。
(当然CX means an extension to the ISO C standard和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之后,子进程退出,并将fd追回到应用程序级文件指针所在的位置。然后父进程继续,处理读缓冲区的其余部分,当它完成时,它继续从文件中读取。由于搜索了文件描述符,因此从第二个开始的行再次可用。
在你的情况下,每次迭代重复的fork()
似乎导致无限循环。
在这种情况下使用_exit()
而不是exit()
解决了这个问题,因为_exit()
只退出进程,它不会使用stdio缓冲区进行任何内务处理。
使用_exit()
时,任何输出缓冲区也不会被刷新,因此您需要在fflush()
上手动调用stdout
以及您要写入的任何其他文件。
但是,如果你反过来这样做,孩子阅读和缓冲比处理更多,那么孩子寻找fd以便父母可以从孩子实际离开的地方继续前进将是有用的。
另一个解决方案是不要将stdio
与fork()
混合。