为什么我不能重用管道与多个子进程通信?

Why can't I reuse a pipe to communicate with multiple child processes?

如果我的父进程有 n 个子进程,为什么我不能使用一个管道将父进程的 pid 发送到所有子进程? post 简要介绍了这一点, 但是我觉得解释的不是很清楚

这是我的代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void main(void){

    int n;

    printf("How many child processes would you like to create?");
    scanf("%d", &n);

    pid_t pid, child, ppid, pidout;
    int fd[2];
    pipe(fd);
    
    for (int i = 0; i < n; i++){
        pid = fork();
        switch(pid){
        
            case -1:
                printf("error forking at index %i", i);
                exit(1);
                
            case 0: 
                close(fd[1]);
                child = getpid();
                read(fd[0], &pidout, 4);
                printf("\nNode : %i\nMy pid is : %i\nMy parent's pid is : %i\n\n", i, child, pidout);
                exit(0);
                
            default:    
                close(fd[0]);
                ppid = getpid();
                write(fd[1], &ppid, 4);
                if (!i){
                    printf("\nNode : %i, My pid is : %i\n\n", i, ppid);
                }
        }
    }
    for (int i = 0; i < n; i++){
            wait();
    }

}

在这里,我尝试重用同一个管道将数据从父级发送到子级,但是在将父级的 pid 发送到第一个子级后管道中断了。我知道解决这个问题的方法是使用 n-1 个管道:每个通信一个。但是,我不明白 为什么 此代码不起作用。任何澄清将不胜感激。谢谢!

(注意:我不是管道和 Unix IPC 专家。)

您可能看到的是管道的预期行为,即通过管道的东西从另一侧出来,而不只是留在那里供其他人观察。一个进程写入,另一个进程读取 - 管道不保留所有数据,并被清除。

也许一个不同的系统调用可以工作,它允许在不清除管道缓冲区的情况下达到峰值,尽管这听起来不像是一个优雅的解决方案。另一种可能的方法是设置一个进程间共享内存区域形式的“海报板”客栈,父进程写入该内存区域,子进程可以读取。

编辑: 您还遇到了在创建所有子项之前过早关闭管道 read-end 的问题。请参阅@MarcoLucidi 的回答。

要阅读的相关文档包括(对于 Linux)syscalls(2), pipe(2), pipe(7), fifo(7), dup2(2), close(2)

fork(2) fails, you probably want to use errno(3) and perror(3)明白为什么失败了。大多数其他系统调用也可能失败,您需要阅读其文档,并处理它们的失败案例。

你当然需要阅读Advanced Linux Programming

对于非 Linux(例如 FreeBSD)的 Unix 系统,您应该参考它们的文档。

如果您的 C 编译器是 GCC, I recommend reading its documentation then using gcc -Wall -Wextra -g when compiling, and learn to use the GDB 调试器。

如果您不熟悉 C,请阅读 Modern C book and refer to websites such as this C reference 网站。

研究现有 open source projects coded in C (for example on github.com or gitlab.com) 应该是鼓舞人心的。

您可能对 Frama-C or the Clang static analyzer 等项目感兴趣。

我认为这里的问题是您 close() 父级管道的读取端过早,当其他分叉子级尝试从中读取时,他们得到 EBADF。你应该总是检查return值read()write()

如果您在最后移动 close(fd[0]);,在 wait() 调用之后,代码应该按预期工作。

此处重写代码以说明我的意思:

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

int main(void)
{
        int n;

        printf("How many child processes would you like to create?");
        if (scanf("%d", &n) != 1) {
                fprintf(stderr, "invalid number\n");
                exit(EXIT_FAILURE);
        }


        pid_t pid, child, ppid, pidout;
        int fd[2];
        if (pipe(fd) < 0) {
                perror("pipe");
                exit(EXIT_FAILURE);
        }

        for (int i = 0; i < n; i++) {
                pid = fork();
                switch (pid) {
                case -1:
                        printf("error forking at index %i", i);
                        exit(EXIT_FAILURE);

                case 0: /* child */
                        close(fd[1]);
                        child = getpid();
                        if (read(fd[0], &pidout, sizeof(pidout)) < 0) {
                                perror("read");
                                exit(EXIT_FAILURE);
                        }
                        printf("Node: %i My pid is: %i My parent's pid is: %i\n", i, child, pidout);
                        close(fd[0]);
                        exit(EXIT_SUCCESS);

                default: /* parent */
                        ppid = getpid();
                        if (write(fd[1], &ppid, sizeof(ppid)) < 0) {
                                perror("write");
                                exit(EXIT_FAILURE);
                        }
                        if (i == 0)
                                printf("\nParent, My pid is: %i\n\n", ppid);
                }
        }

        for (int i = 0; i < n; i++)
                wait(NULL);

        /* close pipe in the parent at the end, to avoid bad file descriptors
         * in the forked children */
        close(fd[0]);
        close(fd[1]);
}