【问题标题】:Pipes from process to dynamic list of grandchildren processes从进程到孙进程动态列表的管道
【发布时间】:2020-07-21 08:37:04
【问题描述】:

我有一个进程 P,它必须跟踪从 P1Pn 的进程列表。

程序使用 P1 进行初始化,列表的头部已经创建。在特定事件上(不在问题范围内),P 必须通过管道写入列表中的当前最后一个进程 Pi 和这个进程,在收到消息,不得不分叉。新创建的 Pi+1 必须作为新的最后一个进程添加到由 P 管理的链表中。

我尝试实现的方式如下:

  • P 初始化一个空链表List,创建一个管道childs_to_p 来接收来自所有后代的消息,创建一个管道fd em>,分叉 P1 并将其附加到 List
  • P1 开始在 fd 上监听。
  • 如果P要添加另一个进程,它会创建一个新管道fd2,并通过fdP1(第一次迭代的 List 中的最后一个进程)fd2 的读取端(P2 必须监听的读取端来自)。
  • P1fd 读取,分叉并告诉新创建的 P2 fd2的读取端>。然后继续听fd上的指令。

  • P2 通过管道 childs_to_p 将其 pid 发送到 P

  • PP2'pid 添加到 List。
  • 重复 n 次。

请记住,我是 C 语言的新手,但这是我目前实现的。

这是P的初始化:

void setupProcessManager(int we, int re, int msg_size) {
     write_to_m = we;             // Setup of pipe to external
     read_from_m = re;            // process that gives instructions
     message_size = msg_size;     // to P.

     initList(&l);                // Linked list initialization

     open_childs_to_pm_pipe();    // Open pipe to read from processes in list

     addp1();                     // Initialize P1

     pthread_t t;               
     pthread_create(&t, NULL, PMListen, NULL);   // P starts listening from external process


     pthread_join(t,NULL);        // Wait for thread termination

     close(write_to_m);           // Close pipe to external process
     close(read_from_m);          // Close pipe from external process
}

初始化P1的函数:

void addp1() {
     int to_p1[2];           
     pipe(to_p1);              // Create new pipe to P1
     pid_t pid = fork();       // Fork P1
     if(pid == 0) {
         PListen(to_p1[0]);    // P1 starts listening
         exit(0);
     } else if(pid>0) {
         insertEnd(&l, pid, to_p1[1], to_p1[0]);  // P appends P1 to list 
     }
 }

P 执行的功能(仅考虑 switch 中的 'A' 情况):

void *PMListen() {
     int message;
     int listen = 1;
     while(listen) {
         read(read_from_m,&message,message_size);
         switch(message) {

             // OTHER CASES

             case 'A':   
                 int new_pipe[2];   // P creates a new pipe
                 pipe(new_pipe);
                 write(getLastProcessWritingEnd(&l), &new_pipe[0], message_size);  // P writes reading end of new pipe to P1
                 pid_t pid;
                 read(childs_to_pm[0], &pid,sizeof(pid_t)); // P reads P2 pid from childs_to_p
                 insertEnd(&l, pid, new_pipe[1], new_pipe[0]); // P appends P2 to list
                 break;

         }
     }

 }

最后是列表中的进程执行的任务:

void PListen(int read_from_here) {
     int message;
     int listen = 1;
     while(read(read_from_here,&message,message_size) != 0) {
         switch(message) {
             default:                  // Pi reads integer (reading end of pipe P-Pi+1)
                 pid_t pid = fork();   // Fork Pi+1
                 if(pid == 0) {
                     pid_t mypid = getpid();
                     write(childs_to_pm[1], &mypid, sizeof(int)); // Pi+1 writes its pid to P through childs_to_p
                     PListen(message); // Pi+1 starts listening on its pipe from P
                     exit(0);
                 }
                 break;
         }
     }

 }

一切都按预期进行。 P1 被创建并添加到链表中。当 P 尝试添加 P2 时,它会这样做,但函数 PListen 会一直循环,永远分叉。

我知道代码写得不好,但正如我提到的,我不熟悉 C。我需要算法方面的帮助,而不是代码的美感。

【问题讨论】:

  • AFAIK,您不能通过管道发送文件描述符。可以pass file descriptors over sockets,但不能通过管道。所以,我认为这个设计存在一些问题。共享管道的进程必须有一个在创建进程之前创建管道的共同祖先(或者其中一个必须是创建管道的祖先)。
  • 我认为您将需要创建一个 MCVE (Minimal, Complete, Verifiable Example)(或 MRE 或 SO 现在使用的任何名称)或一个 SSCCE (Short, Self-Contained, Correct Example)。很难看出一切是如何工作的,特别是因为有很多全局变量我们看不到定义。
  • PMListen() 应该是一个线程函数,但它的签名不正确。这可能不是致命的,但你不应该冒险。将线程与分叉混合通常不是一个好主意。无论如何,你需要谨慎。
  • 如果您只能使用未命名的管道,那么祝您好运,正如临时评论所暗示的那样。那我看看你是怎么解决问题的。 Linux 上可能有一些我不熟悉的可能性,但我认为你前面的路很坎坷。
  • @JonathanLeffler 事实上,对于这个程序,我应该只使用未命名的管道,所以这就是我决定进行这个实现和设计的原因。在我的情况下,P 确实是创建管道的祖先

标签: c pipe fork


【解决方案1】:

在 cmets 中进行了广泛的讨论,为什么我认为尝试将新创建的管道的文件描述符从初始进程 P 传递到第 N 代子进程的设计不会像描述的那样工作。简而言之,一旦创建了子进程 P1,它的父进程 P 就无法创建新的文件描述符(例如,通过调用 pipe())来传递给子进程。

我概述了一种替代设计,它可以——而且确实——有效:

  1. P 创建两个管道,一个用于写入子级,一个用于读取子级;
  2. P 派生第一个孩子 P1;
  3. P 关闭“to children”管道的读取端和“from children”管道的写入端;
  4. P 等待事件——例如从标准输入读取一行,并在到达时立即执行;
  5. P1 关闭“to children”管道的写入端和“from children”管道的读取端;
  6. P1 在“to children”管道上等待来自 P 的消息;
  7. P 在“致儿童”管道上写了一条消息;
  8. P1 读取消息;
  9. P1 派生出一个子 P2;
  10. P1 将 P2 的 PID 写入 'from children' 管道并关闭它,同时关闭 'to children' 管道(它不会再次使用它们);
  11. P 从 'from children' 管道中读取 P2 的 PID;
  12. P 回到等待另一个事件(输入行)

从第 (4) 步开始,列表中最新的一个新孩子对每个事件(来自 P 的消息)做出反应。每一代子进程都可以在关闭管道后退出(或者为了关闭管道而退出)。

只有两个管道由初始进程创建,相关文件描述符依次由下一代child继承。

这是实现此方案的工作代码。原始子节点 P1 可以将其标准输入设置为“to children”管道的读取端,并将其标准输出设置为“from children”管道的写入端。这允许孩子们使用getline() 从初始进程P 中读取消息。他们实际上使用fwrite() 在管道上写入二进制数据;把它做成字符串数据是可行的,但不是必须的(而且会使原来的父进程复杂一点)。

它使用了一些错误报告代码,这些代码在我的 GitHub 上的SOQ(堆栈溢出问题)存储库中作为文件stderr.cstderr.h 位于src/libsoq 子目录中。特别是,它设置了错误选项来报告每个进程的 PID 报告(在处理多个进程时非常有用)。

源文件pipe73.c

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

static void be_parental(int to_kids[2], int fr_kids[2]);
static void be_childish(int to_kids[2], int fr_kids[2]);

int main(int argc, char **argv)
{
    if (argc > 0)
        err_setarg0(argv[0]);
    err_setlogopts(ERR_PID|ERR_MILLI);
    err_remark("Parent at work\n");

    int to_kids[2];
    int fr_kids[2];

    if (pipe(to_kids) < 0 || pipe(fr_kids) < 0)
        err_syserr("failed to create pipes: ");

    int pid = fork();
    if (pid < 0)
        err_syserr("failed to fork: ");
    else if (pid == 0)
        be_childish(to_kids, fr_kids);
    else
        be_parental(to_kids, fr_kids);

    /*NOTREACHED*/
    err_error("Oops!\n");
    return EXIT_FAILURE;
}

static void be_childish(int to_kids[2], int fr_kids[2])
{
    int child = 0;
    if (dup2(to_kids[0], STDIN_FILENO) < 0 ||
        dup2(fr_kids[1], STDOUT_FILENO) < 0)
        err_syserr("failed to duplicate pipes to standard input/output: ");

    close(to_kids[0]);
    close(to_kids[1]);
    close(fr_kids[0]);
    close(fr_kids[1]);

    err_remark("child P%d at work\n", ++child);

    char *buffer = 0;
    size_t buflen = 0;
    ssize_t msglen;
    while ((msglen = getline(&buffer, &buflen, stdin)) != -1)
    {
        err_remark("Read: [%.*s]\n", (int)msglen - 1, buffer);
        int pid = fork();
        if (pid < 0)
            err_syserr("failed to fork: ");
        else if (pid != 0)
        {
            size_t nbytes = fwrite(&pid, sizeof(char), sizeof(pid), stdout);
            if (nbytes != sizeof(pid))
            {
                if (nbytes == 0)
                    err_syserr("write to pipe failed: ");
                else
                    err_error("short write of %zu bytes instead of %zu expected\n", nbytes, sizeof(pid));
            }
            err_remark("forked PID %d\n", pid);
            err_remark("child P%d finished\n", child);
            exit(0);
        }
        else
            err_remark("child P%d at work\n", ++child);
    }

    free(buffer);
    err_remark("EOF\n");
    err_remark("child P%d finished\n", child);
    exit(0);
}

static void be_parental(int to_kids[2], int fr_kids[2])
{
    close(to_kids[0]);
    close(fr_kids[1]);
    char  *buffer = 0;
    size_t buflen = 0;
    ssize_t msglen;
    int pid;

    err_remark("parent at work\n");

    while ((msglen = getline(&buffer, &buflen, stdin)) != -1)
    {
        err_remark("message: %s", buffer);  /* buffer includes a newline - usually */
        ssize_t nbytes = write(to_kids[1], buffer, msglen);
        if (nbytes < 0)
            err_syserr("write to pipe failed: ");
        else if (nbytes != msglen)
            err_error("short write of %zu bytes instead of %zu expected\n", (size_t) nbytes, (size_t)msglen);
        nbytes = read(fr_kids[0], &pid, sizeof(pid));
        if (nbytes < 0)
            err_syserr("read from pipe failed: ");
        else if ((size_t)nbytes != sizeof(pid))
            err_error("short read of %zu bytes instead of %zu expected\n", (size_t) nbytes, sizeof(pid));
        err_remark("PID %d started\n", pid);
    }

    err_remark("EOF\n");

    close(to_kids[1]);
    close(fr_kids[0]);

    free(buffer);
    err_remark("Finished\n");

    exit(0);
}

pipe73 的示例运行

键入的输入是“abracadabra”、“hocus-pocus”、“zippedy-doo-dah”、“绝对零”和“沸点”;之后,我指出 EOF(键入 Control-D)。

$ pipe73
pipe73: 2020-04-08 16:17:59.531 - pid=14441: Parent at work
pipe73: 2020-04-08 16:17:59.532 - pid=14441: parent at work
pipe73: 2020-04-08 16:17:59.532 - pid=14442: child P1 at work
abracadabra
pipe73: 2020-04-08 16:18:03.095 - pid=14441: message: abracadabra
pipe73: 2020-04-08 16:18:03.095 - pid=14442: Read: [abracadabra]
pipe73: 2020-04-08 16:18:03.096 - pid=14441: PID 14443 started
pipe73: 2020-04-08 16:18:03.096 - pid=14442: forked PID 14443
pipe73: 2020-04-08 16:18:03.096 - pid=14443: child P2 at work
pipe73: 2020-04-08 16:18:03.097 - pid=14442: child P1 finished
hocus-pocus
pipe73: 2020-04-08 16:18:08.989 - pid=14441: message: hocus-pocus
pipe73: 2020-04-08 16:18:08.989 - pid=14443: Read: [hocus-pocus]
pipe73: 2020-04-08 16:18:08.990 - pid=14441: PID 14444 started
pipe73: 2020-04-08 16:18:08.990 - pid=14443: forked PID 14444
pipe73: 2020-04-08 16:18:08.990 - pid=14444: child P3 at work
pipe73: 2020-04-08 16:18:08.990 - pid=14443: child P2 finished
zippedy-doo-dah
pipe73: 2020-04-08 16:18:14.711 - pid=14441: message: zippedy-doo-dah
pipe73: 2020-04-08 16:18:14.711 - pid=14444: Read: [zippedy-doo-dah]
pipe73: 2020-04-08 16:18:14.712 - pid=14441: PID 14445 started
pipe73: 2020-04-08 16:18:14.711 - pid=14444: forked PID 14445
pipe73: 2020-04-08 16:18:14.712 - pid=14445: child P4 at work
pipe73: 2020-04-08 16:18:14.712 - pid=14444: child P3 finished
absolute zero
pipe73: 2020-04-08 16:18:19.169 - pid=14441: message: absolute zero
pipe73: 2020-04-08 16:18:19.169 - pid=14445: Read: [absolute zero]
pipe73: 2020-04-08 16:18:19.170 - pid=14441: PID 14446 started
pipe73: 2020-04-08 16:18:19.170 - pid=14445: forked PID 14446
pipe73: 2020-04-08 16:18:19.170 - pid=14445: child P4 finished
pipe73: 2020-04-08 16:18:19.170 - pid=14446: child P5 at work
boiling point
pipe73: 2020-04-08 16:18:26.772 - pid=14441: message: boiling point
pipe73: 2020-04-08 16:18:26.772 - pid=14446: Read: [boiling point]
pipe73: 2020-04-08 16:18:26.773 - pid=14441: PID 14447 started
pipe73: 2020-04-08 16:18:26.773 - pid=14447: child P6 at work
pipe73: 2020-04-08 16:18:26.773 - pid=14446: forked PID 14447
pipe73: 2020-04-08 16:18:26.774 - pid=14446: child P5 finished
pipe73: 2020-04-08 16:18:29.374 - pid=14441: EOF
pipe73: 2020-04-08 16:18:29.374 - pid=14441: Finished
pipe73: 2020-04-08 16:18:29.374 - pid=14447: EOF
pipe73: 2020-04-08 16:18:29.375 - pid=14447: child P6 finished
$

这似乎涵盖了所需内容的要点。第 Nth 代子进程将它刚刚启动的子进程的 PID 传递给原始父进程,第 (N+1)th 代是响应的那个到下一个请求。在这段代码中,父母不会保留被告知的孩子的列表,但它会报告每个被告知的孩子(保存信息并不难)。一旦 Nth 代子代向 P 报告,它就会终止。也可以修改它以进行其他工作。

【讨论】:

  • 这对我的任务来说是一个完美的解决方案。我将不得不稍微修改一下以满足项目的确切要求,但它完全符合我的要求。感谢您在寻找答案方面的承诺,尽管这项任务既不容易也没有解释清楚。向你致敬!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多