【问题标题】:Why can't I reuse a pipe to communicate with multiple child processes?为什么我不能重用管道与多个子进程通信?
【发布时间】:2020-09-19 08:58:41
【问题描述】:

如果我的父进程有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 管道:每个通信一个。但是,我不明白为什么这段代码不起作用。任何澄清将不胜感激。谢谢!

【问题讨论】:

  • 我喜欢使用exit(EXIT_FAILURE)exit(EXIT_SUCCESS) 来避免“幻数”。我也不喜欢你如何使用 4 作为幻数 - 它不应该是 sizeof(int) 吗?

标签: unix pipe


【解决方案1】:

我认为这里的问题是您close() 父级管道的读取端太早了,当其他分叉子级尝试从中读取时,他们得到EBADF。您应该始终检查 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]);
}

【讨论】:

  • OP 正在关闭父管道的读取端和子管道的写入端,所以可能不是这样。
  • @einpoklum 当它再次分叉子代时,在下一次迭代中,第一次关闭后,它将在子代中读取错误的文件描述符
  • 啊当然!这就说得通了。只是为了确保我做对了:我不能使用相同的逻辑在只有一个管道的子节点之间进行通信,对吗?在这种情况下,我将不得不创建 n-1 个管道?感谢您的帮助!
  • @iaskdumbstuff 是的,可能每个孩子有一个管道是最好/更简单的解决方案,并且可以让您摆脱在使用具有多个孩子的单个管道时出现的同步问题。
【解决方案2】:

(警告:我不是管道和 Unix IPC 专家。)

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

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

编辑:在创建所有子项之前,您还存在过早关闭管道读取端的问题。请参阅@MarcoLucidi 的回答。

【讨论】:

  • 我想我明白你在说什么。但是,我相信在我的代码中,父级在每次迭代时都将数据写入管道。不是这样吗?
  • @iaskdumbstuff : 没有人保证每个进程都会获得所需的书面信息;很可能一个进程会得到所有这些,或者几个进程会得到一些,等等。无论如何,总是总是检查你的系统和库调用返回值是否有错误!
  • 我明白了。但如果是这种情况,我是否不希望看到不同的子进程在不同的执行过程中接收父进程的 pid?当我执行时,第一个孩子收到数据,但所有其他孩子收到 0。我应该提到第一次使用管道后我的管道坏了。我将在帖子中添加编辑。
  • @iaskdumbstuff:Marco 有你的答案。
【解决方案3】:

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

fork(2) 失败时,您可能想使用errno(3)perror(3) 来了解它失败的原因。大多数其他系统调用也可能失败,您需要阅读其文档并处理它们的失败案例。

你当然需要阅读Advanced Linux Programming

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

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

如果你对C不熟悉,请阅读Modern C这本书,参考this C reference网站等网站。

查看现有用 C 编码的 open source 项目(例如 github.comgitlab.com)应该是鼓舞人心的。

您可能对Frama-CClang static analyzer 等项目感兴趣。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 2011-12-22
    相关资源
    最近更新 更多