Reputation: 53
I have a process P which has to keep track of a list of processes P1 to Pn.
The program is initialized with P1, the head of the list, already created. On a specific event (not in the scope of the question), P has to write trough a pipe to the current last process Pi in the list and this process, upon receiving the message, has to fork. The newly created Pi+1 has to be added to a linked list managed by P as the new last process.
The way I'm trying to implement this is the following:
P1 reads from fd, forks and tells the newly created P2 the reading end of fd2. Then continues to listen for instructions on fd.
P2 sends its pid to P through the pipe childs_to_p.
Keep in mind that I'm new to C language, but here's what I implemented so far.
This is the initialization of 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
}
The function to initialize 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
}
}
The function performed by P (consider only the 'A' case in the switch):
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;
}
}
}
And finally the task performed by the processes in the list:
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;
}
}
}
Everything works as intended. P1 is created and added to the linked list. When P tries to add P2 it does so but then the function PListen keeps looping, forking forever.
I know the code is not well written, but as I mentioned I'm not familiar with C. I need help with the algorithm rather than with the beauty of the code.
Upvotes: 0
Views: 421
Reputation: 754610
There is extensive discussion in the comments of why I think the design trying to pass file descriptors for newly created pipes from the initial process P to the Nth-generation child processes won't work as described. In short, once a child process P1 has been created, there is no way for its parent, P, to create new file descriptors (e.g. by a call to pipe()
) that can be relayed to the child process.
I outlined an alternative design that could — and does – work:
Things continue from step (4) with a new child, the latest in the list, reacting to each event (message from P). Each generation of child process could exit after it closes the pipes (or it could exit in order to close the pipes).
The only two pipes are created by the initial process, and the relevant file descriptors are inherited by the next generation of child in turn.
Here is working code that implements this scheme. The original child, P1, can arrange for its standard input to be the read end of the 'to children' pipe and its standard output to be the write end of the 'from children' pipe. This allows the children to use getline()
to read messages from the initial process, P. They actually write binary data on the pipe with fwrite()
; it would be feasible to make it into string data, but it isn't necessary (and it would make the original parent process a little more complicated).
It uses some error reporting code which is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c
and stderr.h
in the src/libsoq sub-directory. In particular, it sets the error options to report the PID of each process reporting (very helpful when dealing with multiple processes as here).
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
The typed inputs are 'abracadabra', 'hocus-pocus', 'zippedy-doo-dah', 'absolute zero' and 'boiling point'; after that, I indicated EOF (typed 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
$
That seems to cover the gist of what's required. The Nth-generation child relays to the original parent process the PID of the child it just launched, and the (N+1)th-generation is the one that responds to the next request. In this code, the parent doesn't keep a list of the children it is told about, but it does report on each of those children as it is told about it (saving the information would not be hard). Once an Nth-generation child has reported to P, it terminates. It could be revised to do other work as well.
Upvotes: 1