看来关闭一个FILE
在某些情况下,会将底层文件描述符返回到应用程序实际读取的位置,从而有效地消除读取缓冲的影响。这很重要,因为父级和子级的操作系统级别文件描述符指向相同的文件描述,特别是相同的文件偏移量。
The POSIX 描述fclose() http://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/fclose.html有这样一句话:
[CX] [Option Start] 如果文件尚未位于 EOF,并且该文件能够查找,底层打开文件描述的文件偏移量应设置为流的文件位置如果流是底层文件描述的活动句柄。
(Where CX 表示 ISO C 标准的扩展 http://pubs.opengroup.org/onlinepubs/9699919799.2018edition/help/codes.html#CX, and 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);
}
Then:
$ 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()
每次迭代似乎都会导致无限循环。
Using _exit()
代替exit()
在孩子身上解决了问题在这种情况下, since _exit()
仅退出进程,它不会对 stdio 缓冲区执行任何操作。
With _exit()
,任何输出缓冲区也不会被刷新,所以你需要调用fflush()
手动开启stdout
以及您正在写入的任何其他文件。
但是,如果您以相反的方式执行此操作,即子级读取和缓冲的内容多于其处理的内容,那么子级查找 fd 将很有用,以便父级可以从子级实际离开的位置继续。
另一个解决方案是不混合stdio
with fork()
.