fork()与fopen()相遇时的混乱

时间:2018-09-28 20:59:58

标签: c fork system filestream

我发现,如果在关闭文件流之前执行fork(),打开的文件流将被弄乱。众所周知,当父子进程想要修改文件流时,并发,即竞争条件可能发生。但是,即使子进程从未接触过文件流,它仍然具有未定义的行为。我想知道是否有人可以通过在分支和退出子进程的阶段内核如何处理文件流来解释这一点。

下面是一个奇怪行为的快速摘要:

#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)。这将使上述奇怪行为静音,但是在更复杂的条件下,仍然可以观察到其他情况。如果有人可以给我一些关于这个问题的见解,将不胜感激。谢谢

1 个答案:

答案 0 :(得分:0)

正如评论中所说,您需要在调用exec之前关闭打开的文件描述符。

this blogpost(第4节)中,有一个简单的代码示例可用于确保关闭所有fds,即使在您并不总是知道当前打开了哪些文件的复杂应用程序中:

for ( i=getdtablesize(); i>2; --i) 
close(i); /* close all descriptors */

(稍作修改以保持stdin,stdout,stderr打开)

有点hacky,但是可以用。如果要避免这种情况,还可以在打开的每个文件描述符上设置O_CLOEXEC标志。由于使用fopen时不会直接调用open(),因此可以通过在其上添加'e'标志来实现(使用glibc> = 2.7时):

FILE* fp = fopen("test.txt", "er");

调用exec*()时,所有带有此标志的文件描述符都会自动关闭。