В некоторой степени перефразирование пунктов, сделанных в комментарии к вопросу, не исключая сеанс чата , содержит ответ в нескольких частях.
Терминология
Этокак правило, лучше всего зарезервировать термин «каналы» для типа канала, созданного системным вызовом pipe()
(или функцией, заключающей в себе системный вызов), используя термин FIFO для «именованных каналов», созданныхmkfifo()
системный вызов.Обратите внимание, что вы не можете открывать каналы с помощью системного вызова open()
;вы не можете открыть FIFO, кроме как с помощью open()
или его второстепенного варианта openat()
.
Кроме того, см. Является ли хорошей идеей вводить указатели на определения типа? , на который краткий ответвообще "нет".Это вызвало некоторую путаницу в вашем коде, где у вас есть:
if ((ret = write(parent, &orderA, sizeof(Order))) > 0)
if ((ret = read(parent, &order_A, sizeof(Order))) > 0)
В обоих случаях переменная является указателем на Order
(a node
) и дополнительной косвенной ссылкой на &
неверно, потому что sizeof(Order *) != sizeof(Order)
- и вы отправляете неверные данные по конвейеру и читаете в неправильном месте.Вероятность путаницы была бы меньше, если бы у вас было typedef struct Order Order;
, а переменные имели тип Order *
.
Версия 1
В какой-то момент обсуждения я отправил следующее (с ошибками)код.Удивительно, но это сработало более или менее.Но это было в основном случайно, не в последнюю очередь из-за показанных проблем чтения / записи.
Этот код использует библиотечную функцию, доступную в моем репозитории SOQ (вопросы о переполнении стека) на GitHub в виде файловstderr.c
и stderr.h
в подкаталоге src / libsoq .
Дефектный код - не используйте
/* SO 5396-9266 */
#include "posixver.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "stderr.h"
#define MAX_PROCESS 10
const char *defaultPipe = "/tmp/child";
static int orderNum = 0;
static int workerNum = 0;
static pid_t shutDown[MAX_PROCESS];
static char pipes[MAX_PROCESS][100];
typedef struct Order
{
int id;
char created[256];
char fullName[256];
char email[256];
char phone[256];
char status;
int performance;
int days;
struct Order *next;
} Order;
typedef struct Order *node;
void startJob(void);
void sendPriorityJobs(node priorityHead);
void handler(int signo, siginfo_t *info, void *context);
void createWorker(node orderA, int workerID);
node createNode(void);
node createOrder(char *fullName, char *email, char *phone, char *created, int performance);
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
err_setlogopts(ERR_PID|ERR_MILLI);
struct sigaction sa;
sa.sa_handler = (void *)handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction(SIGUSR2, &sa, NULL);
node test1 = createOrder("Jane Doe", "test1@gmail.com", "12345678", "2018-12-25 8:00", 1000);
node test2 = createOrder("John Doe", "test2@gmail.com", "87654321", "2018-12-25 9:00", 1001);
test1->next = test2;
printf("Parent pid: %d\n", getpid());
/*This is where we send the task */
sendPriorityJobs(test1);
for (int i = 0; i < workerNum; i++)
{
/*int p = open(pipes[i], O_RDONLY);
read(p, msgFromWorker, sizeof(msgFromWorker));
sleep(1);
printf("%s\n", msgFromWorker);
close(p);*/
int status;
int corpse = waitpid(shutDown[i], &status, 0);
if (corpse < 0)
err_sysrem("child %d - no status available: ", shutDown[i]);
else
err_remark("child %d (corpse %d) exited with status 0x%.4X\n", shutDown[i], corpse, status);
}
err_remark("All done!\n");
return 0;
}
static void dump_order(const char *tag, const node order)
{
err_remark("%s (%p):\n", tag, (void *)order);
err_remark("Order: %d, %s, %s, %s, %s, %d\n", order->id, order->fullName,
order->email, order->phone, order->created, order->performance);
}
void createWorker(node orderA, int workerID)
{
int parent; // child;
pid_t worker;
char strID[12];
sprintf(strID, "%d", workerID);
char pipe[100];
strcpy(pipe, defaultPipe);
strcat(pipe, strID);
if (mkfifo(pipe, S_IRUSR | S_IWUSR) != 0)
err_syserr("failed to create FIFO '%s': ", pipe);
err_remark("FIFO %s created\n", pipe);
worker = fork();
if (worker < 0)
err_syserr("failed to fork: ");
if (worker == 0)
{
err_remark("worker at play!\n");
//this is a temporarily variable for the received structure.(order)
node order_A = createNode();
parent = open(pipe, O_RDONLY);
if (parent < 0)
err_syserr("failed to open FIFO '%s' for reading: ", pipe);
int ret;
if ((ret = read(parent, order_A, sizeof(Order))) > 0)
{
printf("[Child %d]: started work on %d. order.\n", getpid(), order_A->id);
//printf("ret: %d\n", ret);
//printf("%d,%s,%s,%s,%s,%d\n", order_A->id,order_A->fullName,order_A->email,order_A->phone,order_A->created,order_A->performance);
dump_order("Read by child:", order_A);
}
startJob();
char endMessage[256];
sprintf(endMessage, "[Child %d]: ended his daily task.", getpid());
/*Sending the done message via pipe. This is questionable part,
how to do this properly. */
/*child = open(pipe, O_WRONLY);
write(child, &endMessage, strlen(endMessage) + 1);*/
err_remark("Message to parent: %s\n", endMessage);
free(order_A);
exit(0);
}
else
{
//Save the child's pid
shutDown[workerID] = worker;
//Save the child's pipe name.
strcpy(pipes[workerID], pipe);
parent = open(pipe, O_WRONLY);
int ret;
if ((ret = write(parent, &orderA, sizeof(Order))) > 0) // BUG!
{
printf("[Parent]: sending %d. order!\n", orderA->id);
dump_order("Parent sends", orderA);
//printf("ret: %d\n", ret);
}
else
err_syserr("faileds to writ to child %d\n", (int)worker); // Ick!
close(parent);
}
}
void startJob(void)
{
pid_t parentPID = getppid();
sleep(1);
printf("[Child %d]: is done, sending signal.\n", getpid());
if (kill(parentPID, SIGUSR2) != 0)
err_syserr("failed to signal parent process %d\n", (int)parentPID);
else
err_remark("signalled parent process %d with SIGUSR2\n", (int)parentPID);
}
void sendPriorityJobs(node priorityHead)
{
node current = priorityHead;
while (current != NULL)
{
createWorker(current, workerNum);
workerNum++;
current = current->next;
}
err_remark("All priority jobs sent\n");
}
node createNode(void)
{
node tmp;
tmp = (node)malloc(sizeof(struct Order));
tmp->next = NULL;
return tmp;
}
node createOrder(char *fullName, char *email, char *phone, char *created, int performance)
{
node newOrder;
newOrder = createNode();
strcpy(newOrder->fullName, fullName);
strcpy(newOrder->email, email);
strcpy(newOrder->phone, phone);
strcpy(newOrder->created, created);
newOrder->performance = performance;
newOrder->status = 'N';
newOrder->id = orderNum + 1;
orderNum++;
return newOrder;
}
void handler(int signo, siginfo_t *info, void *context)
{
(void)context; // Unused
char msg[256];
time_t t;
time(&t);
sprintf(msg, "[Parent]: i got the signal(%d) from [Child %d] time: %s", signo, info->si_pid, ctime(&t));
err_remark("%s: %d from %d\n", __func__, signo, info->si_pid);
int nbytes = strlen(msg) + 1;
int obytes;
if ((obytes = write(1, msg, nbytes)) != nbytes)
err_syserr("short write %d bytes (%d expected): ", obytes, nbytes);
err_remark("return from %s\n", __func__);
}
Несмотря на свои недостатки и степень, в которой он игнорирует рекомендации по Как избежать использования printf()
в обработчике сигналов , это вроде работает.К счастью, ребенок ничего не пытается сделать с данными, которые он должен прочитать от родителя.
Пример выполнения
Parent pid: 89291
signal67: 2018-12-30 15:54:17.298 - pid=89291: FIFO /tmp/child0 created
signal67: 2018-12-30 15:54:17.299 - pid=89292: worker at play!
[Parent]: sending 1. order!
[Child 89292]: started work on 2088763392. order.
signal67: 2018-12-30 15:54:17.299 - pid=89291: Parent sends (0x7fba7c800000):
signal67: 2018-12-30 15:54:17.299 - pid=89291: Order: 1, Jane Doe, test1@gmail.com, 12345678, 2018-12-25 8:00, 1000
signal67: 2018-12-30 15:54:17.299 - pid=89291: FIFO /tmp/child1 created
signal67: 2018-12-30 15:54:17.299 - pid=89292: Read by child: (0x7fba7d001800):
signal67: 2018-12-30 15:54:17.300 - pid=89292: Order: 2088763392, , ?, ?, ?, -413540392
signal67: 2018-12-30 15:54:17.300 - pid=89293: worker at play!
[Parent]: sending 2. order!
signal67: 2018-12-30 15:54:17.300 - pid=89291: Parent sends (0x7fba7c801000):
signal67: 2018-12-30 15:54:17.300 - pid=89291: Order: 2, John Doe, test2@gmail.com, 87654321, 2018-12-25 9:00, 1001
[Child 89293]: started work on 2088767488. order.
signal67: 2018-12-30 15:54:17.300 - pid=89291: All priority jobs sent
signal67: 2018-12-30 15:54:17.300 - pid=89293: Read by child: (0x7fba7d001800):
signal67: 2018-12-30 15:54:17.300 - pid=89293: Order: 2088767488, , ?, ?, ?, -413540392
[Child 89292]: is done, sending signal.
signal67: 2018-12-30 15:54:18.301 - pid=89291: handler: 31 from 89292
[Parent]: i got the signal(31) from [Child 89292] time: Sun Dec 30 15:54:18 2018
signal67: 2018-12-30 15:54:18.301 - pid=89291: return from handler
signal67: 2018-12-30 15:54:18.300 - pid=89292: signalled parent process 89291 with SIGUSR2
[Child 89293]: is done, sending signal.
signal67: 2018-12-30 15:54:18.301 - pid=89291: handler: 31 from 89293
[Parent]: i got the signal(31) from [Child 89293] time: Sun Dec 30 15:54:18 2018
signal67: 2018-12-30 15:54:18.301 - pid=89291: return from handler
signal67: 2018-12-30 15:54:18.301 - pid=89292: Message to parent: [Child 89292]: ended his daily task.
signal67: 2018-12-30 15:54:18.301 - pid=89293: signalled parent process 89291 with SIGUSR2
signal67: 2018-12-30 15:54:18.301 - pid=89293: Message to parent: [Child 89293]: ended his daily task.
signal67: 2018-12-30 15:54:18.302 - pid=89291: child 89292 (corpse 89292) exited with status 0x0000
signal67: 2018-12-30 15:54:18.302 - pid=89291: child 89293 (corpse 89293) exited with status 0x0000
signal67: 2018-12-30 15:54:18.302 - pid=89291: All done!
Вы можете увидеть мусорв дамп информации полученной от родителя.Однако это показывает, что флаг SA_RESTART
важен в вызове sigaction()
.Без этого функция waitpid()
возвращает ошибки, которые эта программа отлавливает и сообщает.
Версия 2
Это в основном рабочий код, который на самом деле использует sigsuspend()
Цикл примерно такой же, как в предыдущем воплощении вашего кода.Однако это несколько иное.В частности, это возвращает информацию от детей.Это делается с использованием того же FIFO, который использовался для передачи информации от родителя к детям.Родитель сначала открывает FIFO для записи, затем пишет сообщение дочернему элементу, а затем закрывает FIFO.Тем временем ребенок открывает FIFO для чтения, читает сообщение, а затем закрывает FIFO.Когда все дочерние элементы дали сигнал родителю (используя цикл sigsuspend
), он считывает ответы, тогда как дочерние элементы открывают FIFO для записи, записи и закрытия FIFO, в то время как родительский элемент открывает FIFO для чтения, читает ответи снова закрывает FIFO.Только после этого родитель зацикливается на ожидании смерти своих детей (многопроцессная работа в Unix-подобных системах - болезненное дело, как и с мертвыми детьми, зомби и всеми остальными).
Этот кодтакже удаляет FIFO как до, так и после его запуска.Имена FIFO в каталоге /tmp
легко вывести, и, следовательно, легко сломать программу (например, создать каталог /tmp/child.0
).Лучшее решение будет использовать mkstemp()
или аналогичную функцию для создания имен FIFO (отмечая, что mkstemp()
фактически создает файл, а не FIFO; не существует прямого механизма для создания уникальнос именем FIFO, о котором я знаю).
Используемый код, но все еще не полированный
/* SO 5396-9266 */
#include "posixver.h"
#include "stderr.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#define MAX_PROCESS 10
static const char defaultPipe[] = "/tmp/child.";
static int orderNum = 0;
static int workerNum = 0;
static pid_t shutDown[MAX_PROCESS];
static char pipes[MAX_PROCESS][100];
struct SigCaught
{
int sig_number;
struct timespec sig_tstamp;
int sig_sender;
void *sig_contxt;
};
static struct SigCaught sig_list[MAX_PROCESS];
static int sig_cur = 0;
static int sig_prt = 0;
typedef struct Order
{
int id;
char created[256];
char fullName[256];
char email[256];
char phone[256];
char status;
int performance;
int days;
struct Order *next;
} Order;
typedef struct Order *node;
static void startJob(void);
static void sendPriorityJobs(node priorityHead);
static void sig_handler(int signo, siginfo_t *info, void *context);
static void sig_printer(void);
static void createWorker(node orderA, int workerID);
static node createNode(void);
static node createOrder(char *fullName, char *email, char *phone, char *created, int performance);
static void get_response(void);
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
err_setlogopts(ERR_PID|ERR_MILLI);
struct sigaction sa;
sa.sa_handler = (void *)sig_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction(SIGUSR2, &sa, NULL);
node test1 = createOrder("Jane Doe", "test1@gmail.com", "12345678", "2018-12-25 8:00", 1000);
node test2 = createOrder("John Doe", "test2@gmail.com", "87654321", "2018-12-25 9:00", 1001);
test1->next = test2;
printf("Parent pid: %d\n", getpid());
/* This is where we send the task */
sendPriorityJobs(test1);
for (int i = 0; i < workerNum; i++)
{
sigset_t es;
sigemptyset(&es);
err_remark("suspending...\n");
sigsuspend(&es);
err_remark("awake again.\n");
}
sig_printer();
get_response();
/* Wait for child processes to signal, write, and exit */
for (int i = 0; i < workerNum; i++)
{
int status;
int corpse = waitpid(shutDown[i], &status, 0);
if (corpse > 0)
err_remark("child %d (corpse %d) exited with status 0x%.4X\n", shutDown[i], corpse, status);
else
err_sysrem("child %d - no status available: ", shutDown[i]);
}
err_remark("All done!\n");
/* Clean up FIFOs */
for (int i = 0; i < workerNum; i++)
unlink(pipes[i]);
return 0;
}
static void dump_order(const char *tag, const node order)
{
err_remark("%s (%p):\n", tag, (void *)order);
err_remark("Order: %d, %s, %s, %s, %s, %d\n", order->id, order->fullName,
order->email, order->phone, order->created, order->performance);
}
void createWorker(node orderA, int workerID)
{
int parent;
pid_t worker;
char strID[12];
sprintf(strID, "%d", workerID);
char pipe[100];
strcpy(pipe, defaultPipe);
strcat(pipe, strID);
if (unlink(pipe) != 0 && errno != ENOENT)
err_syserr("failed to remove FIFO '%s': ", pipe);
if (mkfifo(pipe, S_IRUSR | S_IWUSR) != 0)
err_syserr("failed to create FIFO '%s': ", pipe);
err_remark("FIFO %s created\n", pipe);
worker = fork();
if (worker < 0)
err_syserr("failed to fork: ");
if (worker == 0)
{
err_remark("worker at play!\n");
node order_A = createNode();
parent = open(pipe, O_RDONLY);
if (parent < 0)
err_syserr("failed to open FIFO '%s' for reading: ", pipe);
int ret;
if ((ret = read(parent, order_A, sizeof(Order))) != sizeof(Order))
err_syserr("short read of %d bytes (%zu expected) from parent: ", ret, sizeof(Order));
printf("[Child %d]: started work on %d. order.\n", getpid(), order_A->id);
dump_order("Read by child:", order_A);
close(parent);
startJob(); /* Signal to parent */
char endMessage[256];
sprintf(endMessage, "[Child %d]: ended his daily task.", getpid());
parent = open(pipe, O_WRONLY);
if (parent < 0)
err_syserr("failed to open FIFO '%s' for writing: ", pipe);
err_remark("successfully reopened FIFO '%s' for writing\n", pipe);
int len = strlen(endMessage);
if (write(parent, endMessage, len) != len)
err_syserr("faied to write message of %d bytes to parent: ", len);
close(parent);
err_remark("Message sent to parent: %s\n", endMessage);
free(order_A);
exit(0);
}
else
{
shutDown[workerID] = worker;
strcpy(pipes[workerID], pipe);
workerID++;
parent = open(pipe, O_WRONLY);
int ret;
if ((ret = write(parent, orderA, sizeof(Order))) == sizeof(Order))
{
printf("[Parent]: sending %d. order to child %d!\n", orderA->id, (int)worker);
dump_order("Parent sends", orderA);
}
else
err_syserr("failed to write %zu bytes to child %d\n", sizeof(Order), (int)worker);
close(parent);
}
}
static void read_response(int worker)
{
err_remark("Starting to read response from worker %d\n", worker);
int fd = open(pipes[worker], O_RDONLY);
if (fd < 0)
err_syserr("failed to open FIFO '%s' for reading\n", pipes[worker]);
err_remark("successfully opened FIFO '%s' for reading\n", pipes[worker]);
int nbytes;
char buffer[1024];
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
err_remark("MSG %i (%d): %.*s\n", worker, shutDown[worker], nbytes, buffer);
fflush(stdout);
close(fd);
err_remark("Finished reading response from worker %d\n", worker);
}
/* There's probably a better way to do this! */
static void get_response(void)
{
for (int i = 0; i < workerNum; i++)
{
for (int j = 0; j < sig_cur; j++)
{
if (shutDown[i] == sig_list[j].sig_sender)
{
read_response(i);
sig_list[j].sig_sender = 0; /* Don't try again */
}
}
}
}
void startJob(void)
{
pid_t parentPID = getppid();
sleep(1);
printf("[Child %d]: is done, sending signal.\n", getpid());
if (kill(parentPID, SIGUSR2) != 0)
err_syserr("failed to signal parent process %d\n", (int)parentPID);
else
err_remark("signalled parent process %d with SIGUSR2\n", (int)parentPID);
}
void sendPriorityJobs(node priorityHead)
{
node current = priorityHead;
while (current != NULL)
{
createWorker(current, workerNum);
workerNum++;
current = current->next;
}
err_remark("All priority jobs sent\n");
}
node createNode(void)
{
node tmp;
tmp = (node)malloc(sizeof(struct Order));
tmp->next = NULL;
return tmp;
}
node createOrder(char *fullName, char *email, char *phone, char *created, int performance)
{
node newOrder;
newOrder = createNode();
strcpy(newOrder->fullName, fullName);
strcpy(newOrder->email, email);
strcpy(newOrder->phone, phone);
strcpy(newOrder->created, created);
newOrder->performance = performance;
newOrder->status = 'N';
newOrder->id = orderNum + 1;
orderNum++;
return newOrder;
}
void sig_handler(int signo, siginfo_t *info, void *context)
{
sig_list[sig_cur].sig_number = signo;
clock_gettime(CLOCK_REALTIME, &sig_list[sig_cur].sig_tstamp);
sig_list[sig_cur].sig_sender = info->si_pid;
sig_list[sig_cur].sig_contxt = context;
static const char sig_message[] = "return from signal handler\n";
write(STDERR_FILENO, sig_message, sizeof(sig_message) - 1);
sig_cur++;
}
static void print_siginfo(struct SigCaught *info)
{
struct tm *lt = localtime(&info->sig_tstamp.tv_sec);
char buffer[32];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", lt);
err_remark("%s.%.3ld: signal %d received from PID %d\n", buffer,
info->sig_tstamp.tv_nsec / 1000000, info->sig_number,
info->sig_sender);
}
static void sig_printer(void)
{
while (sig_prt < sig_cur)
print_siginfo(&sig_list[sig_prt++]);
}
Пробный прогон
Parent pid: 89404
signal41: 2018-12-30 16:02:57.458 - pid=89404: FIFO /tmp/child.0 created
signal41: 2018-12-30 16:02:57.459 - pid=89405: worker at play!
[Child 89405]: started work on 1. order.
[Parent]: sending 1. order to child 89405!
signal41: 2018-12-30 16:02:57.459 - pid=89404: Parent sends (0x7fc14a800000):
signal41: 2018-12-30 16:02:57.459 - pid=89404: Order: 1, Jane Doe, test1@gmail.com, 12345678, 2018-12-25 8:00, 1000
signal41: 2018-12-30 16:02:57.460 - pid=89404: FIFO /tmp/child.1 created
signal41: 2018-12-30 16:02:57.459 - pid=89405: Read by child: (0x7fc14b001800):
signal41: 2018-12-30 16:02:57.460 - pid=89405: Order: 1, Jane Doe, test1@gmail.com, 12345678, 2018-12-25 8:00, 1000
signal41: 2018-12-30 16:02:57.460 - pid=89406: worker at play!
[Child 89406]: started work on 2. order.
[Parent]: sending 2. order to child 89406!
signal41: 2018-12-30 16:02:57.461 - pid=89404: Parent sends (0x7fc14a801000):
signal41: 2018-12-30 16:02:57.461 - pid=89404: Order: 2, John Doe, test2@gmail.com, 87654321, 2018-12-25 9:00, 1001
signal41: 2018-12-30 16:02:57.461 - pid=89404: All priority jobs sent
signal41: 2018-12-30 16:02:57.461 - pid=89404: suspending...
signal41: 2018-12-30 16:02:57.461 - pid=89406: Read by child: (0x7fc14b80a200):
signal41: 2018-12-30 16:02:57.461 - pid=89406: Order: 2, John Doe, test2@gmail.com, 87654321, 2018-12-25 9:00, 1001
[Child 89405]: is done, sending signal.
return from signal handler
signal41: 2018-12-30 16:02:58.461 - pid=89404: awake again.
signal41: 2018-12-30 16:02:58.461 - pid=89404: suspending...
signal41: 2018-12-30 16:02:58.461 - pid=89405: signalled parent process 89404 with SIGUSR2
[Child 89406]: is done, sending signal.
return from signal handler
signal41: 2018-12-30 16:02:58.462 - pid=89404: awake again.
signal41: 2018-12-30 16:02:58.462 - pid=89404: 2018-12-30 16:02:58.461: signal 31 received from PID 89405
signal41: 2018-12-30 16:02:58.462 - pid=89404: 2018-12-30 16:02:58.462: signal 31 received from PID 89406
signal41: 2018-12-30 16:02:58.462 - pid=89404: Starting to read response from worker 0
signal41: 2018-12-30 16:02:58.462 - pid=89404: successfully opened FIFO '/tmp/child.0' for reading
signal41: 2018-12-30 16:02:58.462 - pid=89406: signalled parent process 89404 with SIGUSR2
signal41: 2018-12-30 16:02:58.462 - pid=89405: successfully reopened FIFO '/tmp/child.0' for writing
signal41: 2018-12-30 16:02:58.463 - pid=89404: MSG 0 (89405): [Child 89405]: ended his daily task.
signal41: 2018-12-30 16:02:58.463 - pid=89405: Message sent to parent: [Child 89405]: ended his daily task.
signal41: 2018-12-30 16:02:58.463 - pid=89404: Finished reading response from worker 0
signal41: 2018-12-30 16:02:58.463 - pid=89404: Starting to read response from worker 1
signal41: 2018-12-30 16:02:58.463 - pid=89404: successfully opened FIFO '/tmp/child.1' for reading
signal41: 2018-12-30 16:02:58.463 - pid=89406: successfully reopened FIFO '/tmp/child.1' for writing
signal41: 2018-12-30 16:02:58.463 - pid=89404: MSG 1 (89406): [Child 89406]: ended his daily task.
signal41: 2018-12-30 16:02:58.464 - pid=89404: Finished reading response from worker 1
signal41: 2018-12-30 16:02:58.464 - pid=89404: child 89405 (corpse 89405) exited with status 0x0000
signal41: 2018-12-30 16:02:58.463 - pid=89406: Message sent to parent: [Child 89406]: ended his daily task.
signal41: 2018-12-30 16:02:58.464 - pid=89404: child 89406 (corpse 89406) exited with status 0x0000
signal41: 2018-12-30 16:02:58.464 - pid=89404: All done!