【发布时间】:2019-03-04 21:08:59
【问题描述】:
我发现如果我们在关闭它之前执行 fork(),打开的文件流会变得混乱。众所周知,当父进程和子进程想要修改文件流时,可能会发生并发,即竞争条件。但是,即使子进程从未接触过文件流,它仍然具有未定义的行为。我想知道是否有人可以从子进程被分叉和退出的阶段内核如何处理文件流来解释这一点。
下面是一个奇怪行为的快速sn-p:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
// Open file
FILE* fp = fopen("test.txt", "r");
int count = 0;
char* buffer = NULL;
size_t capacity = 0;
ssize_t line = 0;
while ( (line = getline(&buffer, &capacity, fp)) != -1 ) {
if (line > 0 && buffer[line - 1] == '\n') // remove the end '\n'
buffer[line - 1] = 0;
pid_t pid = fork();
if (pid == 0) {
// fclose(fp); // Magic line here: when you add this, everything is fine
if (*buffer == '2')
execlp("xyz", "xyz", NULL);
else
execlp("pwd", "pwd", NULL);
exit(1);
} else {
waitpid(pid, NULL, 0);
}
count++;
}
printf("Loops: %d\n", count);
return 0;
}
只需将代码复制到一个新文件中(例如 test.c)。并用简单的内容创建一个 .txt 文件 test.txt
1
2
3
4
然后运行
$ gcc test.c && ./a.out
文件中有 4 行。预计循环将读取每一行并准确执行 4 次 (1 2 3 4)。我选择让它在第二个循环中执行无效命令“xyz”。然后,你会发现循环实际上执行了 6 次(1 2 3 4 3 4)!事实是,当执行的所有四个命令都有效时,什么都不会出错。但是如果执行了一个无效的命令,它之后的每个命令都会被执行两次。 (请注意,这种奇怪的行为只发生在 Linux 机器上,我的 Mac OS 运行良好,不确定 Windows。所以问题与平台有关?)
看起来每当我 fork() 时,父级中的文件流不再被承诺为旧 fp(非确定性行为),即使我的子进程没有触及它。
我找到的一个临时解决方案是:子进程中的 fclose(fp)。这将使上述奇怪的行为沉默,但在更复杂的情况下,仍然可以观察到其他事情。如果有人能给我一些关于这个问题的见解,我们将不胜感激。谢谢
【问题讨论】:
-
在调用
execlp之前需要关闭打开的文件描述符,见here。所以你的临时解决方案不是临时的,而是真正的。 “其他事情”可能有不同的原因。 -
如果您同时进行以下操作,您的所有问题都会得到解决: (1) 在致电
fork之前立即致电fflush(0); (2) 当exec失败时调用_exit而不是exit。在子进程中调用fclose(fp)其实是错误。 -
附录:在调用
exec之前在子进程中调用close(fileno(fp))可能是合适的,和/或在fopen之后立即在该文件描述符上设置 close-on-exec 位.但是,这是否真的正确取决于您未向我们展示的完整程序的详细信息。 -
非常感谢大家,我会尝试这些解决方案
-
查看我对问题 Why does forking my process cause the file to be read infinitely? 的回答,尤其是标题为 POSIX 和 Exegesis 的部分。我相当肯定这将解释你所看到的。这是非常微妙的。真正看到这种事情发生也花了 30 年的时间——Linux 最终选择利用这种草率(至少,这就是“从这里”的感觉)。
标签: c fork system filestream