删除FIFO后读取数据

6

我创建了一个FIFO,写入数据并删除了它。 令我惊讶的是,在删除后,我仍然能够从这个FIFO中读取数据,这是为什么呢?

#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX_BUF 256
int main()
{
    int fd;
    char * myfifo = "/tmp/myfifo";

    /* create the FIFO (named pipe) */
    mkfifo(myfifo, 0666);

    int pid = fork();
    if (pid != 0)
    {
        /* write "Hi" to the FIFO */
        fd = open(myfifo, O_WRONLY);
        write(fd, "Hi", sizeof("Hi"));
        close(fd);

        /* remove the FIFO */
        unlink(myfifo);
    }
    else 
    {
        wait(NULL);
        char buf[MAX_BUF];

        /* open, read, and display the message from the FIFO */
        fd = open(myfifo, O_RDONLY);
        read(fd, buf, MAX_BUF);
        printf("Received: %s\n", buf);
        close(fd);

        return 0;
    }


    return 0;
}

1
这是正常的Unix行为。不喜欢的内容会被保留,只要至少有一个进程具有至少一个文件描述符,该文件描述符通过内核与该不喜欢的内容相关联。这不仅适用于FIFO,而且适用于所有类型的文件。 - Petr Skocik
什么是正确的使用方式?我应该在什么时候使用unlink调用? - triple fault
在打开文件后立即取消链接是完全可以的。事实上,我认为这比通过 at_exit 按名称取消链接要好,因为名称对实际文件来说是一种弱引用(当您的程序运行时,某人可能已经替换了该文件,现在您的程序可能正在删除其他内容而不是它打开的文件)。在 Unix 上,取消链接并处理指向已取消链接内容的文件描述符是一种相当标准的做法。 - Petr Skocik
在同时对读取器和写入器进行打开/写操作之后立即使用unlink是可以的吗? - triple fault
1
你只需要解除链接一次。第二次调用unlink将会失败(虽然,C语言不会抛出异常)。因为在FIFO上打开只有在另一端调用open时才会成功,所以不存在过早解除链接的风险。 - Petr Skocik
1个回答

1

除非您将O_NONBLOCK标志传递给open(2),否则打开FIFO会阻塞,直到另一端打开。来自man 7 fifo

必须在两端(读取和写入)打开FIFO才能传递数据。通常,打开FIFO会阻塞,直到另一端也打开。

进程可以以非阻塞模式打开FIFO。在这种情况下,仅为读取打开将成功,即使尚未在写入端打开,仅为写入打开也会失败,并显示ENXIO(没有此设备或地址),除非另一端已经打开。

也就是说,在打开FIFO时,父/子进程会隐式同步。因此,当父进程调用unlink(2)时,子进程早已打开了FIFO。因此,在父进程对其调用unlink(2)之前,子进程将始终找到FIFO对象并打开它。

关于unlink(2)的一点说明: unlink(2)仅从文件系统中删除文件名;只要至少有一个进程使用该文件(在这种情况下为FIFO),底层对象将持续存在。只有在该进程终止或关闭文件描述符后,操作系统才会释放相关资源。顺便说一句,这与本问题的范围无关,但似乎值得注意。
另外还有几点(不相关的)备注:
  • 不要在子进程上调用wait(2)。它将返回一个错误(您立即忽略),因为子进程没有分叉任何进程。
  • mkfifo(3)fork(2)open(2)read(2)write(2)close(2)unlink(2) 都可能失败并返回-1。您应该优雅地处理可能的错误,而不是忽略它们。对于这些玩具程序的常见策略是使用perror(3)打印一个描述性错误消息并终止。
  • 如果您只想进行父子进程通信,请使用管道:它更容易设置,您不需要取消链接它,并且它不会暴露在文件系统中(但您需要在分叉之前使用pipe(2)创建它,以便子进程可以访问它)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接