В комментариях много говорится о том, почему я думаю, что проект, пытающийся передать файловые дескрипторы для вновь созданных каналов из начального процесса P в дочерние процессы N-го поколения, не будет работать, как описано. Короче говоря, после того, как дочерний процесс P1 был создан, его родительский элемент P не может создавать новые файловые дескрипторы (например, путем вызова pipe()
), которые могут быть переданы дочернему процессу.
Я обрисовал альтернативный дизайн, который мог бы - и работает -:
- P создает две трубы, одну для записи детям, одну для чтения от детей;
- P разветвляется первый дочерний элемент, P1;
- P закрывает конец чтения канала 'to children' и конец записи канала 'from children';
- P ожидает события - например, читает строку из стандартного ввода и запускается в действие, когда он прибывает;
- P1 закрывает конец записи канала 'to children' и конец чтения канала 'from children';
- P1 ожидает для сообщения от P в канале «to children»;
- P записывает сообщение в канал «to children»;
- P1 читает сообщение;
- P1 forks дочерний P2;
- P1 записывает PID P2 на канал «от детей» и закрывает его, а также закрывает канал «для детей» (он не будет использовать их снова);
- P считывает PID P2 из канала «от детей»;
- 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, он прекращает работу. Это может быть пересмотрено для выполнения другой работы.