【问题标题】:Ring of processes using fork() and pipes使用 fork() 和管道的进程环
【发布时间】:2014-05-12 14:32:18
【问题描述】:

一个小时以来,我一直在为这个问题绞尽脑汁。我必须创建一个包含 n 个进程的进程环(数量通过 cmd 作为参数传递)。父进程将他的 PID 发送给他的第一个子进程,这个进程将他的父进程的 PID 加上他自己的 PID 发送给他的下一个兄弟,直到我们创建了 n 个子进程。之后,父进程获取所有子进程的PID。

假设父进程的 PID 为 3400,我们创建了两个子进程,所以环是由三个进程组成的

3400 + 3401(第一个孩子的 PID) + 3402(第二个孩子的 PID) = 10203

父进程应该得到这个 10203。

我考虑过一个“for”循环,其中子进程仅使用一个管道将其兄弟 PID 的加法从兄弟发送到兄弟。尽管如此,我还没有找到解决方案。

【问题讨论】:

    标签: c linux process pipe


    【解决方案1】:

    鉴于任务是使用fork()pipe(),您可能需要使用如下算法:

    • 父级为其创建管道以写入第一个st 子级。
    • 父级保持对第一个st子级的管道写入端打开。
    • 父级保持从第 Nth 个子级打开管道的读取端。
    • 对于每个孩子 n = 1..N,Parent 为 nth 个孩子创建输出管道以与 n+1th 对话。
    • 父分叉 nth 个子节点。
    • nth 子级关闭其输入管道的写入端和其输出管道的读取端。
    • nth 子进程从输入管道读取 PID 的总和,将自己的 pid 添加到总和中,然后将其写入输出管道,然后退出。
    • 同时,Parent 将输入管道的两端关闭到第 nth(除了必须保持打开的描述符),并循环返回以创建第 n+1th sup> 孩子的管道,然后是孩子。
    • 当所有子级启动后,父级将其 PID 写入第一个st 子级并关闭该管道的写入端。
    • 然后它从第 Nth 个子节点读取响应,关闭管道的读取端,并打印结果。

    如何验证总和不太明显,除非每个子节点也将其 PID 写入标准输出,或者父节点(知道所有子节点 PID)计算答案以验证它。


    由于完全没有错误检查而获得零分:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char **argv)
    {
        int N = (argc > 1) ? atoi(argv[1]) : 10;
        int c1_wr;
        int cN_rd;
        int p1[2];
        int p2[2];
        int pid_chk = getpid();
    
        if (N <= 0 || N >= 100)
            N = 10;
    
        pipe(p1);
        c1_wr = dup(p1[1]);
        printf("%d children\n", N);
        printf("Parent   = %d\n", pid_chk);
    
        for (int n = 0; n < N; n++)
        {
            int pid;
            pipe(p2);
            fflush(stdout);
            if ((pid = fork()) == 0)
            {
                close(p1[1]);
                close(p2[0]);
                int pid_sum;
                read(p1[0], &pid_sum, sizeof(pid_sum));
                pid_sum += getpid();
                write(p2[1], &pid_sum, sizeof(pid_sum));
                close(p1[0]);
                close(p2[1]);
                exit(0);
            }
            printf("Child %2d = %d\n", n+1, pid);
            pid_chk += pid;
            close(p1[0]);
            close(p1[1]);
            p1[0] = p2[0];
            p1[1] = p2[1];
        }
    
        cN_rd = p2[0];
        close(p2[1]);
    
        int pid_sum = getpid();
        write(c1_wr, &pid_sum, sizeof(pid_sum));
        close(c1_wr);
        read(cN_rd, &pid_sum, sizeof(pid_sum));
        close(cN_rd);
        printf("PID sum = %d\n", pid_sum);
        printf("PID chk = %d\n", pid_chk);
    
        return 0;
    }
    

    示例运行:

    10 children
    Parent   = 49686
    Child  1 = 49688
    Child  2 = 49689
    Child  3 = 49690
    Child  4 = 49691
    Child  5 = 49692
    Child  6 = 49693
    Child  7 = 49694
    Child  8 = 49695
    Child  9 = 49696
    Child 10 = 49697
    PID sum = 546611
    PID chk = 546611
    

    fflush(stdout); 的用途在 (a) 省略它并且 (b) 通过管道运行输出时变得清晰。很有必要。

    【讨论】:

    • 好一个!我创建了一个示例,其中我只使用一个管道,我也可以进行添加。那是对的吗?哪些优点提供了使用两个管道而不是一个的事实?
    • 一对多:使用多管道解决方案,您可以保证以正确的顺序对传输的数据进行排序;使用单管道解决方案,您对 o/s 调度程序有点心血来潮,了解孩子读取数据的顺序。您可能足够安全,但是如果您在读取进程时打印出进程的 PID管道中的值,它们可能不是单调递增的顺序。
    【解决方案2】:

    如果您可以控制子进程的行为,并且父进程在启动子进程之前没有重要/难以重新创建的状态,那么您可以简化事情。

    您可以让父级递归地启动分离的子级,终止自身,并让最后一个子级重新启动父级,而不是 IPC:

    1. 调用parent n,其中n 是要生成的孩子的数量。
    2. parent:
      • 调用child arg1-1 0并终止,
      • 如果使用 "0" 调用 arg1,它会将自己的 PID 添加到 arg2,然后就完成了,即使就进程树而言,它并不是任何东西的真正“父级”。
    3. child:
      • 调用child arg-1 arg2+pid,其中pid 是孩子自己的PID。
      • 如果用"0" 调用arg1,它会调用parent 0 arg2+pid

    【讨论】:

      【解决方案3】:

      您需要某种进程间通信 (IPC)。 IPC常用的方法有:

      • 环回套接字

      • 共享内存

      您可以在wikipedia IPC article 中找到所有其他方法。

      【讨论】:

      • 任务是使用管道作为IPC的方法。我不明白这如何帮助回答这个问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-07
      • 1970-01-01
      • 2015-12-25
      • 2014-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多