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

Unix 为什么不能重用管道与多个子进程通信?,unix,pipe,Unix,Pipe,如果我有一个具有n个子进程的父进程,为什么我不能使用一个管道将父进程的pid发送到所有子进程?本节简要介绍了这一点, 但我觉得解释得不太清楚 这是我的密码: #include <stdio.h> #include <unistd.h> #include <stdlib.h> void main(void){ int n; printf("How many child processes would you like to cre

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

这是我的密码:

#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专家

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

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


编辑:在创建所有子级之前,还存在提前关闭管道读取端的问题。请参阅@MarcoLucidi的答案。

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

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

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


编辑:在创建所有子级之前,还存在提前关闭管道读取端的问题。请参阅@MarcoLucidi的答案。

要阅读的相关文档包括Linux、、和

当失败时,您可能希望使用并理解它失败的原因。大多数其他系统调用也可能失败,您需要阅读其文档,并处理它们的失败案例

你当然需要阅读 对于非Linux的Unix系统,例如,您应该参考其文档

如果您的C编译器是,我建议您阅读其文档,然后在编译时使用gcc-Wall-Wextra-g,并学习使用调试器

如果您不熟悉C语言,请阅读本书并参考网站,如website

查看以C语言编写的现有项目,例如在上或上,应该是鼓舞人心的


您可能对或等项目感兴趣。

要阅读的相关文档包括Linux、、

当失败时,您可能希望使用并理解它失败的原因。大多数其他系统调用也可能失败,您需要阅读其文档,并处理它们的失败案例

你当然需要阅读 对于非Linux的Unix系统,例如,您应该参考其文档

如果您的C编译器是,我建议您阅读其文档,然后在编译时使用gcc-Wall-Wextra-g,并学习使用调试器

如果您不熟悉C语言,请阅读本书并参考网站,如website

查看以C语言编写的现有项目,例如在上或上,应该是鼓舞人心的


您可能对或之类的项目感兴趣。

我认为这里的问题是您过早地关闭了父级中管道的读取端,而当其他分叉的孩子尝试从中读取时,他们会得到EBADF。您应该始终检查读取和写入的返回值

如果移动closefd[0];最后,在等待调用之后,代码应该按预期工作

下面是代码的工作重写,以说明我的意思:

#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]);
}

我认为这里的问题是,您过早地关闭了父级中管道的读取端,当其他分叉的孩子尝试从中读取时,他们会得到EBADF。您应该始终检查读取和写入的返回值

如果移动closefd[0];最后,在等待调用之后,代码应该按预期工作

下面是代码的工作重写,以说明我的意思:

#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]);
}

我想我明白你的意思。然而,我相信在我的代码中,父级在每次迭代时都将数据写入管道。不是这样吗?@iaskdumbstuff:没有人能保证每个进程都能获得所需的书面信息;这很可能是一场灾难
单个进程将获得全部,或者几个进程将获得一些,等等。无论如何,始终检查您的系统和库调用返回值是否存在错误!我懂了。但是如果是这样的话,我不希望看到不同的子进程在不同的执行中接收父进程的pid吗?当我执行时,第一个子级接收数据,但所有其他子级接收0。我应该提到,我第一次使用管道后,管道断了。我会在帖子中添加和编辑这些内容。@iaskdumbstuff:Marco有你的答案。我想我理解你的意思。然而,我相信在我的代码中,父级在每次迭代时都将数据写入管道。不是这样吗?@iaskdumbstuff:没有人能保证每个进程都能获得所需的书面信息;很可能一个进程将获得全部,或者几个进程将获得一些,等等。无论如何,始终检查您的系统和库调用返回值是否存在错误!我懂了。但是如果是这样的话,我不希望看到不同的子进程在不同的执行中接收父进程的pid吗?当我执行时,第一个子级接收数据,但所有其他子级接收0。我应该提到,我第一次使用管道后,管道断了。我将在帖子中添加编辑。@iaskdumbstuff:Marco有你的答案。OP正在关闭父对象中管道的读取端,而子对象中管道的写入端,所以可能不是这样。@einpoklum在下一次迭代中,在第一次关闭后,它将在子对象中获得错误的读取文件描述符。当然了!这是有道理的。为了确保这一点是正确的:我不能使用相同的逻辑在只有一个管道的子节点之间进行通信,对吗?在这种情况下,我必须创建n-1个管道?谢谢你的帮助@iaskdumbstuff是的,可能每个子级有一个管道是最好/更简单的解决方案,并且将使您免于在使用具有多个子级的单个管道时出现的同步问题。OP关闭父级管道的读取端,子级管道的写入端,所以可能不是这样。@einpoklum再次分叉子级时,在下一次迭代中,在第一次关闭之后,它将在childrenAh中获得错误的读取文件描述符!这是有道理的。为了确保这一点是正确的:我不能使用相同的逻辑在只有一个管道的子节点之间进行通信,对吗?在这种情况下,我必须创建n-1个管道?谢谢你的帮助@iaskdumbstuff是的,可能每个孩子有一个管道是最好的/更简单的解决方案,并且将使您从使用单个管道和多个孩子时出现的同步问题中解脱出来。我喜欢使用exitEXIT_失败和exitEXIT_成功来避免幻数。我也不喜欢你用4作为一个神奇的数字——它不是应该是sizeofint吗?我喜欢用exitEXIT_失败和exitEXIT_成功来避免神奇的数字。我也不喜欢你用4作为一个神奇的数字——它不是应该是sizeofint吗?