封闭管道的不同端部

AAA*_*AAA 2 linux c pipe

我使用以下命令为 IPC 编写了以下代码pipe()

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
    
    
int main(void) {
    char message_buffer[15] = "Hello World \n";  
    char read_buffer[15];
    int fd[2]; 
   
    int return_value = pipe(fd);
   
    if (return_value < 0) {
        printf("Error creating the pipe");
    }
    
    int rc = fork();
    if (rc  < 0) {
        printf("Error forking a child");
    }
    
    if (rc > 0) {
        close(fd[0]);
        write(fd[1], message_buffer, 15);
        close(fd[1]);
        wait(NULL);
    } else {
        close(fd[1]);
        read(fd[0], read_buffer, 15);
        close(fd[0]);
        printf("The Message: %s", read_buffer);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我是管道新手,我有以下问题:

  1. 我不明白为什么父级需要在写入之前关闭读取端,并且在写入之后需要关闭写入端?
  2. 对于孩子来说也是如此,为什么它需要在读取之前关闭写入端,然后为什么需要在读取后关闭读取端?
  3. 由于父进程和子进程同时运行,如果子进程在父进程写入消息时读取消息,会发生什么情况?
  4. 由于父级和子级同时运行,如果子级读取而父级尚未在管道中写入任何内容,会发生什么情况?

我的问题似乎很愚蠢,但请帮忙回答,因为我正在为课程考试学习普通管道。

VL-*_*-80 7

问题 1 和 2的答案位于pipe 手册页(“示例”部分):

分叉后,每个进程都会关闭管道不需要的文件描述符(请参阅管道(7))。

由于管道是单向的,因此它具有指定的端部 -读取端和写入端。如果父级将使用此管道向子级写入数据,则父级没有必要保持读取端打开。相反,如果子进程要从管道读取数据,则不需要打开写入端。

但用户@ilkkachu描述了关闭不需要的管道末端的一个更重要的原因。请查看链接的答案。

编辑:

您还问为什么父级在写入后需要关闭写入端,为什么子级在读取后需要关闭读取端。

他们不必这样做。如果两个程序要继续运行并使用管道交换数据,则它们必须保持管道打开。在一个简短的示例程序中,该程序仅演示管道的使用,并且在传输一条消息后终止,父进程和子进程可能会关闭管道文件描述符,以便在程序终止之前正确清理资源。

问题 #3 和 #4 的答案位于pipe(7) 手册页中。

你的问题#3:

由于父进程和子进程可以同时运行,如果子进程在父进程写入消息时读取消息,会发生什么情况?

子级将能够读取管道中已由父级写入的任何可用数据。根据手册页:

POSIX.1 规定小于 PIPE_BUF 字节的写入必须是原子的:输出数据作为连续序列写入管道。超过 PIPE_BUF 字节的写入可能是非原子的:内核可能会将数据与其他进程写入的数据交错。POSIX.1 要求 PIPE_BUF 至少为 512 字节。(在 Linux 上,PIPE_BUF 为 4096 字节。)

你的问题#4:

由于父级和子级可以同时运行,如果子级读取而父级尚未在管道中写入任何内容,会发生什么情况?

手册页说:

如果进程尝试从空管道读取数据,则 read(2) 将阻塞,直到数据可用。如果进程尝试写入完整管道(见下文),则 write(2) 会阻塞,直到从管道读取足够的数据以允许写入完成。

回答评论中的问题:

对于问题1和2,这意味着如果我没有关闭不需要的末端,这不会以任何方式影响程序?

它不应阻止管道工作,但会在程序使用的资源上产生一些占用空间。通过关闭管道不需要的端部,这些资源就不会被保留。

对于问题3,这意味着孩子将阅读父母正在写的内容,孩子如何知道父母已经完成了需要写的内容?

手册页说:

管道提供的通信通道是字节流:没有消息边界的概念。

这意味着管道不关心你传输的数据。它不知道“消息”意味着什么,也不知道父级是否已完成写入或想要写入更多数据。

您将需要实现自己的技术来确定什么是“完整消息”。例如,父级可以通过发送特殊字符(例如,\0或者实际上在使用管道的特定上下文中有意义的任何其他字符)来向子级表示已写入完整消息。