Трубы от процесса к динамике c список процессов внуков - PullRequest
0 голосов
/ 08 апреля 2020

У меня есть процесс P , который должен отслеживать список процессов P1 до P n.

Программа инициализируется с P1 , заголовком списка, уже созданным. В конкретном событии c (не входящем в объем вопроса) P должен записать через канал в текущий последний процесс P i в списке и этот процесс, после получения сообщения, должен раскошелиться. Недавно созданный P i + 1 должен быть добавлен в связанный список, управляемый P , как новый последний процесс.

Способ, которым я пытаюсь реализовать это следующее:

  • P инициализирует пустой связанный список List , создает канал childs_to_p для получения сообщений от всех потомки, создает канал fd , разветвляется P1 и добавляет его к List .
  • P1 начинает слушать fd .
  • Если P хочет добавить другой процесс, он создает новый канал fd2 , а через fd it передает P1 (последний процесс в List для этой первой итерации) конец чтения fd2 (конец чтения, который будет P2 должен слушать).
  • P1 читает из fd , разветвляется и сообщает вновь созданному P2 конец чтения FD2 . Затем продолжает прислушиваться к инструкциям по fd .

  • P2 отправляет pid на P через канал childs_to_p .

  • P добавляет P2 ' pid в список.
  • Повтор 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 (рассмотрим только «А» в коммутаторе):

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. Мне нужна помощь с алгоритмом, а не с красотой кода.

1 Ответ

0 голосов
/ 09 апреля 2020

В комментариях много говорится о том, почему я думаю, что проект, пытающийся передать файловые дескрипторы для вновь созданных каналов из начального процесса 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 ожидает для сообщения от P в канале «to children»;
  7. P записывает сообщение в канал «to children»;
  8. P1 читает сообщение;
  9. P1 forks дочерний P2;
  10. P1 записывает PID P2 на канал «от детей» и закрывает его, а также закрывает канал «для детей» (он не будет использовать их снова);
  11. P считывает PID P2 из канала «от детей»;
  12. P возвращается к ожиданию для другого события (строка ввода)

С шага (4) все продолжается с новым дочерним элементом, последним в списке, реагирующим на каждое событие (сообщение от P). Каждое поколение дочерних процессов может завершить работу после закрытия каналов (или может завершиться, чтобы закрыть каналы).

Только два канала создаются начальным процессом, и соответствующие файловые дескрипторы наследуются следующее поколение потомков по очереди.

Вот рабочий код, реализующий эту схему. Исходный дочерний элемент, P1, может устроить так, чтобы его стандартный ввод был концом чтения канала «дочерним элементам», а его стандартный вывод - концом записи канала «дочерних элементов». Это позволяет детям использовать getline() для чтения сообщений из начального процесса P. Они фактически записывают двоичные данные в канал с помощью fwrite(); было бы целесообразно превратить его в строковые данные, но в этом нет необходимости (и это немного усложнит исходный родительский процесс).

В нем используется некоторый код сообщения об ошибках, который доступен в моем * Репозиторий 1040 * SOQ (вопросы о переполнении стека) на GitHub в виде файлов stderr.c и stderr.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
$

Это, кажется, охватывает суть того, что требуется. Дочернее поколение N th передает исходному родительскому процессу PID только что запущенного дочернего процесса, а поколение (N + 1) th реагирует на следующий запрос В этом коде родитель не хранит список детей, о которых ему говорят, но он сообщает о каждом из этих детей, как ему об этом сказано (сохранение информации не составит труда). Как только ребенок N th поколения сообщил P, он прекращает работу. Это может быть пересмотрено для выполнения другой работы.

...