I implement a Ring of N generation like this.
(process 0)--pipe4--(process 3)
| |
| |
pipe 1 pipe 3
| |
| |
(process 1)--pipe2--(process 2)
So, the Process 0 is the father of process 1, the process 1 the father of process 2 etc...
The process 0 send a random number to process 1 via pipe1, process 1 generate an other random number, and send max between these two numbers to process 2 via pipe 2 etc...
The proess 0 is reading on the pipe4, so he is blocked, waiting for the last message, then show the winner.
What i want to do, is that the process 0 send the winner to all the process now.
I try to re-read the pipe on each process to make them lock until the answer is propagated but a re read in the same process for the same pipe => no block.
So i try to create an other pipe between each process for the communication of the winner. So after i send the random number, i read on the pipeWinner, but this not block the process too!!
i'm really confuse, i don"t know how to do it.
Here is my code:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
struct Node {
int node;
int val;
int pid;
};
int main(int argc, const char * argv[]) {
//Generation correspond to the number of generation the user want to create
int nbGeneration = atoi(argv[1]);
int indexGeneration=1;
//This value store the new random created on each generation
int newValue=0;
int pid=0;
//Array of pipe
int tube[nbGeneration][2];
int tubeAnswer[nbGeneration-1][2];
//Our seed for the rand
int seed=time(NULL);
srand(seed);
//The struct for the first process
//Value initialize here
struct Node processInitiale;
processInitiale.node=0;
processInitiale.val =rand();
processInitiale.pid =getpid();
//The struct for the childs process, this struct store the winner too
struct Node processWinner;
processWinner.node=0;
processWinner.val =0;
processWinner.pid =0;
//Pipe the nbGeneration pipe for the inital process reading
pipe(tube[nbGeneration]);
//Create N generation
for(indexGeneration = 1 ; indexGeneration < nbGeneration; ++indexGeneration )
{
pipe(tube[indexGeneration]);
pipe(tubeAnswer[indexGeneration]);
pid = fork();
if(pid==-1)
{
printf("%s\n", "Erreur lors de la création du processus");
}
if(pid!=0)
{
if(indexGeneration==1) // Initial process - Write first value to the first child
{
close(tube[indexGeneration][0]);
write(tube[indexGeneration][1],&processInitiale,sizeof(processInitiale));
close(tube[indexGeneration][1]);
//This read is "bloquant", we gonna wait for the last children
close(tube[nbGeneration][1]);
read(tube[nbGeneration][0],&processWinner,sizeof(processWinner));
close(tube[nbGeneration][0]);
//Show result
printf("%s%d%s%d%s%d\n", "Processus PID: ",processInitiale.pid, " node ", processInitiale.node ," value: ", processInitiale.val);
printf("%s%d%s%d%s%d%s%d\n","Node: ",processInitiale.node," Winner is: ",processWinner.val," NODE: ",processWinner.node," PID: ",processWinner.pid );
}
break;
}
else if(pid==0) //If it's the children, let's read the precedent pipe, then write into the next pipe for the next children
{
//Reading in the precedent pipe (The first one was writtend by the initial process - so this one is lock in the case children execute before first father)
close(tube[indexGeneration][1]);
read(tube[indexGeneration][0],&processWinner,sizeof(processWinner));
close(tube[indexGeneration][0]);
//Creation on the new random value to compare
newValue = rand();
printf("%s%ld%s%d%s%d\n", "Processus PID: ",(long)getpid(), " node ", indexGeneration ," value: ", newValue);
//Compare, if more bigger, change the struct
if(newValue>processWinner.val)
{
processWinner.node= indexGeneration;
processWinner.val = newValue;
processWinner.pid = getpid();
}
//Write data into the next pipe for the next children
//The last children gonna write for the inital process at least
close(tube[indexGeneration+1][0]);
write(tube[indexGeneration+1][1],&processWinner,sizeof(processWinner));
close(tube[indexGeneration+1][1]);
//Trying to read in the pipeAnswer but this one didn't block my process.
close(tubeAnswer[indexGeneration][0]);
read(tubeAnswer[indexGeneration][1],&processWinner,sizeof(processWinner));
close(tubeAnswer[indexGeneration][1]);
}
}
}
This is the code I ended up producing for this. I originally worked on it back in 2017, but recently another question appeared that had similar issues with a circle of pipes.
Source code: pipe97.c — compiled to executable pipe97.
/* SO 4626-6329 */
/*
** (process 0)--pipe0--(process 3)
** | |
** | |
** pipe1 pipe3
** | |
** | |
** (process 1)--pipe2--(process 2)
**
** So, the Process 0 is the parent of process 1; process 1 the
** parent of process 2; etc...
**
** Process 0 sends a random number to process 1 via pipe1; process 1
** generate an other random number, and send max between these two
** numbers to process 2 via pipe 2; etc...
**
** Process 0 is reading on pipe4, so it is blocked, waiting for
** the last message, then show the winner.
**
** What I want to do, is that the process 0 send the winner to all the
** process now.
**
** ********************************************************************
**
** Designate the 4 processes P0..P3.
** Designate the 4 pipes (channels) C0..C3.
**
** C0 must be created by P0 before creating P1.
** C1 must be created by P0 before creating P1.
** C2 must be created by P0 before creating P1 (because P1 needs to write to it).
** C3 must be created by P0 before creating P2 (because P2 needs to write to it).
** P0 will write to C1 and read from C0.
** P1 will read from C1 and write to C2.
** P2 will read from C2 and write to C3.
** P3 will read from C3 and write to C0.
**
** Hence Pn always reads from Cn and writes to C((n+1)%4).
** P0 writes then reads; the other processes read then write.
** Note that P1 and P2 should close C0 after forking the next child.
** PN (for N > 1) should close C1..C(N-1) too (it should be sufficient
** to close C(N-1) since P(N-1) closed the prior channels).
**
** Although the code does not rely on detecting EOF, the pipes will be
** closed, even though it is not crucial to the correct functioning of
** this program.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
struct Node
{
int node;
int val;
int pid;
};
static void no_children(void)
{
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
printf("PID %5d: child %5d exited 0x%.4X\n", (int)getpid(), corpse, status);
}
static void check_fds(void)
{
struct stat sb;
char buffer[128];
char *bufptr = buffer;
int n = snprintf(bufptr, sizeof(buffer), "PID %5d: ", (int)getpid());
bufptr += n;
for (int i = 0; i < 20; i++)
{
if (fstat(i, &sb) != 0)
*bufptr++ = '-';
else
*bufptr++ = 'o';
}
*bufptr++ = '\0';
printf("%s\n", buffer);
fflush(stdout);
}
static void pr_result(const char *tag, const struct Node *result)
{
char buffer[16];
snprintf(buffer, sizeof(buffer), "%.12s:", tag);
printf("%-13s PID = %5d, PPID = %5d, Node = %d (PID %5d), Value = %5d\n", buffer,
(int)getpid(), (int)getppid(), result->node, result->pid, result->val);
fflush(stdout);
}
static inline int new_bid(void)
{
return rand() % 10000;
}
static inline void pr_channel(int i, int c[2])
{
printf("%5d: C%d(%d,%d)\n", (int)getpid(), i, c[0], c[1]);
fflush(stdout);
}
static inline void mk_channel(int i, int c[2])
{
if (pipe(c) != 0)
{
fprintf(stderr, "%5d: failed to create pipe %d, (%d: %s)\n",
(int)getpid(), i, errno, strerror(errno));
exit(EXIT_FAILURE);
}
pr_channel(i, c);
}
static _Noreturn void be_parental(int i_pipe[2], int o_pipe[2])
{
int pid = getpid();
struct Node proc0 = { 0, new_bid(), pid };
struct Node winner = { 0, 0, 0 };
printf("PID %5d (PPID %5d): parent at work\n", pid, (int)getppid());
printf("FD {%d, %d, %d, %d}\n", i_pipe[0], i_pipe[1], o_pipe[0], o_pipe[1]);
check_fds();
pr_result("Opening bid", &proc0);
close(o_pipe[0]);
printf("Writing opening bid to fd %d\n", o_pipe[1]);
if (write(o_pipe[1], &proc0, sizeof(proc0)) != sizeof(proc0))
{
fprintf(stderr, "%5d: failed to write entry to fd %d (%d: %s)\n",
pid, o_pipe[1], errno, strerror(errno));
exit(EXIT_FAILURE);
}
close(i_pipe[1]);
if (read(i_pipe[0], &winner, sizeof(winner)) != sizeof(winner))
{
fprintf(stderr, "%5d: failed to read entry from fd %d (%d: %s)\n",
pid, i_pipe[0], errno, strerror(errno));
exit(EXIT_FAILURE);
}
//close(i_pipe[0]);
pr_result("Winning node", &winner);
if (write(o_pipe[1], &winner, sizeof(winner)) != sizeof(winner))
{
fprintf(stderr, "%5d: failed to write entry to fd %d (%d: %s)\n",
pid, o_pipe[1], errno, strerror(errno));
exit(EXIT_FAILURE);
}
close(o_pipe[1]);
/* Read final result once more */
if (read(i_pipe[0], &winner, sizeof(winner)) != sizeof(winner))
{
fprintf(stderr, "%5d: failed to read entry from fd %d (%d: %s)\n",
pid, i_pipe[0], errno, strerror(errno));
exit(EXIT_FAILURE);
}
close(i_pipe[0]);
pr_result("Recirculated", &winner);
no_children();
check_fds();
exit(EXIT_SUCCESS);
}
static _Noreturn void be_childish(int i, int i_pipe[2], int o_pipe[2])
{
struct Node winner;
int pid = getpid();
printf("PID %5d (PPID %5d) i = %d: child at work\n", pid, (int)getppid(), i);
printf("FD {%d, %d, %d, %d}\n", i_pipe[0], i_pipe[1], o_pipe[0], o_pipe[1]);
check_fds();
srand(pid);
close(i_pipe[1]);
if (read(i_pipe[0], &winner, sizeof(winner)) != sizeof(winner))
{
fprintf(stderr, "%5d: failed to read entry from fd %d (%d: %s)\n",
pid, i_pipe[0], errno, strerror(errno));
exit(EXIT_FAILURE);
}
pr_result("Incoming bid", &winner);
int newValue = new_bid();
printf("PID: %5d, new offer %5d\n", pid, newValue);
if (newValue > winner.val)
{
winner.node = i;
winner.val = newValue;
winner.pid = pid;
}
close(o_pipe[0]);
pr_result("Outgoing bid", &winner);
if (write(o_pipe[1], &winner, sizeof(winner)) != sizeof(winner))
{
fprintf(stderr, "%5d: failed to write entry to fd %d (%d: %s)\n",
pid, o_pipe[1], errno, strerror(errno));
exit(EXIT_FAILURE);
}
if (read(i_pipe[0], &winner, sizeof(winner)) != sizeof(winner))
exit(EXIT_FAILURE);
pr_result("Final winner", &winner);
close(i_pipe[0]);
/* Relay final result to next process */
if (write(o_pipe[1], &winner, sizeof(winner)) != sizeof(winner))
{
fprintf(stderr, "%5d: failed to write entry to fd %d (%d: %s)\n",
pid, o_pipe[1], errno, strerror(errno));
exit(EXIT_FAILURE);
}
close(o_pipe[1]);
/* There should be no children */
no_children();
check_fds();
exit(i);
}
int main(int argc, const char *argv[])
{
int numproc = 4;
if (argc > 1)
numproc = atoi(argv[1]);
if (numproc < 2 || numproc > 10)
{
fprintf(stderr, "Number of processes %d is out of the range 2..10\n", numproc);
exit(EXIT_FAILURE);
}
int seed = time(NULL);
printf("%5d: seed: %d\n", (int)getpid(), seed);
srand(seed);
int tube[numproc][2];
mk_channel(0, tube[0]);
mk_channel(1, tube[1]);
for (int i = 1; i < numproc; ++i)
{
if (i + 1 < numproc)
mk_channel(i + 1, tube[i + 1]);
int pid = fork();
if (pid == -1)
{
fprintf(stderr, "fork error for child %d (%d: %s)\n", i, errno, strerror(errno));
exit(1);
}
else if (pid == 0)
{
for (int c = 0; c < i; c++)
{
if ((i + 1) % numproc != c)
{
printf("%5d: closing %2d and %2d\n", (int)getpid(), tube[c][0], tube[c][1]);
close(tube[c][0]);
close(tube[c][1]);
}
}
be_childish(i, tube[i], tube[(i+1) % numproc]);
}
}
for (int c = 2; c < numproc; c++)
{
printf("%5d: closing %2d and %2d\n", (int)getpid(), tube[c][0], tube[c][1]);
close(tube[c][0]);
close(tube[c][1]);
}
be_parental(tube[0], tube[1]);
return 0;
}
Example run (6 processes requested):
$ pipe97 6
53200: seed: 1618329579
53200: C0(3,4)
53200: C1(5,6)
53200: C2(7,8)
53200: C3(9,10)
53201: closing 3 and 4
53200: C4(11,12)
PID 53201 (PPID 53200) i = 1: child at work
FD {5, 6, 7, 8}
PID 53201: ooo--oooo-----------
53202: closing 3 and 4
53200: C5(13,14)
53202: closing 5 and 6
PID 53202 (PPID 53200) i = 2: child at work
FD {7, 8, 9, 10}
PID 53202: ooo----oooo---------
53203: closing 3 and 4
53203: closing 5 and 6
53203: closing 7 and 8
PID 53203 (PPID 53200) i = 3: child at work
FD {9, 10, 11, 12}
PID 53203: ooo------oooo-------
53204: closing 3 and 4
53204: closing 5 and 6
53200: closing 7 and 8
53204: closing 7 and 8
53204: closing 9 and 10
53200: closing 9 and 10
PID 53204 (PPID 53200) i = 4: child at work
53200: closing 11 and 12
FD {11, 12, 13, 14}
53200: closing 13 and 14
PID 53200 (PPID 828): parent at work
FD {3, 4, 5, 6}
PID 53204: ooo--------oooo-----
PID 53200: ooooooo-------------
Opening bid: PID = 53200, PPID = 828, Node = 0 (PID 53200), Value = 4998
Writing opening bid to fd 6
Incoming bid: PID = 53201, PPID = 53200, Node = 0 (PID 53200), Value = 4998
PID: 53201, new offer 9207
Outgoing bid: PID = 53201, PPID = 53200, Node = 1 (PID 53201), Value = 9207
Incoming bid: PID = 53202, PPID = 53200, Node = 1 (PID 53201), Value = 9207
PID: 53202, new offer 6014
Outgoing bid: PID = 53202, PPID = 53200, Node = 1 (PID 53201), Value = 9207
Incoming bid: PID = 53203, PPID = 53200, Node = 1 (PID 53201), Value = 9207
53205: closing 5 and 6
PID: 53203, new offer 2821
Outgoing bid: PID = 53203, PPID = 53200, Node = 1 (PID 53201), Value = 9207
Incoming bid: PID = 53204, PPID = 53200, Node = 1 (PID 53201), Value = 9207
PID: 53204, new offer 9628
Outgoing bid: PID = 53204, PPID = 53200, Node = 4 (PID 53204), Value = 9628
53205: closing 7 and 8
53205: closing 9 and 10
53205: closing 11 and 12
PID 53205 (PPID 53200) i = 5: child at work
FD {13, 14, 3, 4}
PID 53205: ooooo--------oo-----
Incoming bid: PID = 53205, PPID = 53200, Node = 4 (PID 53204), Value = 9628
PID: 53205, new offer 6435
Outgoing bid: PID = 53205, PPID = 53200, Node = 4 (PID 53204), Value = 9628
Winning node: PID = 53200, PPID = 828, Node = 4 (PID 53204), Value = 9628
Final winner: PID = 53201, PPID = 53200, Node = 4 (PID 53204), Value = 9628
Final winner: PID = 53202, PPID = 53200, Node = 4 (PID 53204), Value = 9628
PID 53201: ooo-----------------
Final winner: PID = 53203, PPID = 53200, Node = 4 (PID 53204), Value = 9628
PID 53202: ooo-----------------
PID 53203: ooo-----------------
Final winner: PID = 53204, PPID = 53200, Node = 4 (PID 53204), Value = 9628
Final winner: PID = 53205, PPID = 53200, Node = 4 (PID 53204), Value = 9628
PID 53204: ooo-----------------
PID 53205: ooo-----------------
Recirculated: PID = 53200, PPID = 828, Node = 4 (PID 53204), Value = 9628
PID 53200: child 53201 exited 0x0100
PID 53200: child 53203 exited 0x0300
PID 53200: child 53202 exited 0x0200
PID 53200: child 53204 exited 0x0400
PID 53200: child 53205 exited 0x0500
PID 53200: ooo-----------------
$
Note that the code is careful to close unnecessary file descriptors before calling the be_childish() or be_parental() functions. The program works OK without those loops.
I want to solve a certain problem and i can't figure it out how to do it properly. I have to create N processes in chain and after i finish making all of them, the initial process will random a number and write it in the pipe, the other processes read from the pipe the number, they randomize a number and substract the result from the number read, write it back to the pipe and so on. Here is what i've tried
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#define P_READ 0
#define P_WRITE 1
void forker(int nprocesses, int** pipes, int fd[2])
{
pid_t pid;
int buf;
if(nprocesses > 0)
{
if ((pid = fork()) < 0)
{
perror("fork");
}
else if (pid == 0)
{
//Child
printf("Child %d created PID : %d , PPID : %d\n", nprocesses-1, getpid(), getppid());
int i = nprocesses - 1;
time_t t;
srand((int)time(&t) % getpid()); // get unique seed for every child
int r = (rand() % 11) + 10;
if (i==0) {
read(fd[P_READ], &buf, sizeof(int));
close(fd[P_READ]);
}else{
read(pipes[i-1][P_READ], &buf, sizeof(int));
close(pipes[i-1][P_READ]);
}
close(pipes[i][P_READ]);
buf -= r;
printf("%d|%d|%d|%d\n", buf, r, getpid(), getppid());
write(pipes[i][P_WRITE], &buf, sizeof(int)); // write
close(pipes[i][P_WRITE]);
printf("Child %d end\n", nprocesses-1);
}
else if(pid > 0)
{
forker(nprocesses - 1, pipes, fd);
}
}
}
int main (void)
{
int status = 0;
srand(time(NULL));
pid_t pid = getpid();
pid_t parent = getpid();
pid_t wpid;
int n, fd[2], buf;
printf("Please enter how many processes you want(between 6 and 15): ");
scanf("%d", &n);
while ( n < 6 || n > 15)
scanf("%d", &n);
int **pipes = (int **)malloc(n * sizeof(int*));
for(int i = 0; i < n; i++) pipes[i] = (int *)malloc(2 * sizeof(int));
printf("I'm parent - my pid is %d\n",pid);
pipe(fd);
for(int i=0; i<n; i++) {
if(pipe(pipes[i])) {
printf("pipe error");
return -1;
}
}
forker(n, pipes, fd);
if(parent == getpid()) {
close(fd[P_READ]);
buf = (rand() % 9001) + 1000;
printf("The initial number is %d created by process with pid : %d\n", buf, getpid());
write(fd[P_WRITE], &buf, sizeof(int));
close(fd[P_WRITE]);
}
while ((wpid = wait(&status)) > 0); // WAIT
if (pid == getpid()) printf("End of parent and my pid was %d\n", pid);
return 0;
}
The output looks something like this :
Please enter how many processes you want(between 6 and 15): 10
I'm parent - my pid is 3827
Child 9 created PID : 3828 , PPID : 3827
Child 8 created PID : 3829 , PPID : 3827
Child 7 created PID : 3830 , PPID : 3827
Child 6 created PID : 3831 , PPID : 3827
Child 5 created PID : 3832 , PPID : 3827
Child 2 created PID : 3835 , PPID : 3827
Child 3 created PID : 3834 , PPID : 3827
The initial number is 1625 created by process with pid : 3827
Child 1 created PID : 3836 , PPID : 3827
Child 4 created PID : 3833 , PPID : 3827
Child 0 created PID : 3837 , PPID : 3827
1609|16|3837|3827
Child 0 end
1589|20|3836|3827
1573|16|3835|3827
Child 2 end
1559|14|3834|3827
Child 3 end
1543|16|3833|3827
Child 4 end
1530|13|3832|3827
Child 5 end
Child 1 end
1511|19|3831|3827
Child 6 end
1498|13|3830|3827
Child 7 end
1488|10|3829|3827
Child 8 end
1470|18|3828|3827
Child 9 end
End of parent and my pid was 3827
The problem is that, i am not sure if the first number is randomed before the processes creation(but that's not the big issue here). The big issue is that the initial process creates all the child processes and it is not "in chain".
First fork you process N times, then generate the random number and pass it to other process.
So put the creation of the random number outside the loop for forking the main process
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
int childs[3];
for (int i = 0; i < 3; ++i) {
int p[2];
if (pipe(p) == -1) { perror("pipe"); exit(1); }
pid_t pid = fork();
if (pid) {
close(p[0]);
childs[i] = p[1];
}
else {
close(p[1]);
printf("child %d start\n", i + 1);
char buf[10];
buf[0] = 0;
int r;
if ((r = read(p[0], buf, 9)) == -1) { ... }
printf("child %d read %s (%d), finish\n", i + 1, buf, r);
sleep(2);
exit(0);
}
}
for (int i = 0; i < 3; ++i) {
// if (argc > 1) {
// write(childs[i], "42", 2);
// }
// ============== HERE >>>
close(childs[i]);
}
pid_t pid;
while ((pid = waitpid(-1, NULL, 0)) > 0) {
printf("child %d exited\n", pid);
}
return 0;
}
Output with comment:
child 1 start
child 2 start
child 3 start
child 3 read (0), finish
The next line is displayed after 2 seconds
child 2 read (0), finish
The next line is displayed after 2 seconds
child 1 read (0), finish
I do not write to the channel in the parent. Closing it, I want to give a signal to the child that will be waiting in the read.
It seems that there is a following. Сhild N expected finishes reading from the result 0, it's ok. Children 2 (N-1) and 1 are locked in a read to a child 3 is completed. Then the child 1 is similar will wait.
Why lock occur?
Child processes inherit open file descriptors from their parent. Your main process opens file descriptors in a loop (using pipe, keeping only the write ends). Child 1 inherits no descriptors (except for stdin/stdout/stderr); child 2 inherits childs[0] (the descriptor going to child 1); child 3 inherits childs[0] and childs[1] (the descriptors going to child 1 and 2).
read on a pipe blocks as long as any write descriptor is still open (because it could be used to send more data). So child 1 waits (because child 2 and child 3 still have an open write descriptor) and child 2 waits (because child 3 still has an open write descriptor); only child 3 sleeps and exits. This causes its file descriptors to close, which wakes up child 2. Then child 2 sleeps and exits, closing its file descriptors, which finally wakes up child 1.
If you want to avoid this behavior, you have to close the open file descriptors in each child:
else {
for (int j = 0; j < i; j++) {
close(childs[j]);
}
close(p[1]);
printf("child %d start\n", i + 1);
The write ends of the pipes are getting inherited by the children.
Since filedescriptor are ref-counted, the write end is only considered closed if all references to it are closed.
Below is your code, slightly refactored, with a fix added:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char **argv) {
int children_w[3];
for (int i = 0; i < 3; ++i) {
int p[2];
if (0>pipe(p))
{ perror("pipe"); exit(1); }
pid_t pid;
if(0> (pid= fork()))
{ perror("fork"); exit(1); }
if(pid==0) {
/* Fix -- close the leaked write ends */
int j;
for(j=0; j<i; j++)
close(children_w[j]);
/* end fix*/
close(p[1]);
printf("child %d start\n", i + 1);
char buf[10];
buf[0] = 0;
int r;
if ((r = read(p[0], buf, 9)) == -1) { perror("read");/*...*/ }
printf("child %d read %s (%d), finish\n", i + 1, buf, r);
sleep(2);
exit(0);
}
children_w[i] = p[1];
close(p[0]);
}
for (int i = 0; i < 3; ++i) {
// if (argc > 1) {
// write(childs[i], "42", 2);
// }
// ============== HERE >>>
close(children_w[i]);
}
pid_t pid;
while ((pid = waitpid(-1, NULL, 0)) > 0) {
printf("child %d exited\n", pid);
}
return 0;
}