#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> void error() { perror("pmain"); exit(1); } int main() { int p1[2]; int p2[2]; int fd, status; if (pipe(p1) < 0) error(); /* Set up pipes */ if (pipe(p2) < 0) error(); if (fork() == 0) { fd = open("f1.txt", O_RDONLY); /* Standard in coming from f1.txt */ if (fd < 0) error(); dup2(fd, 0); close(fd); dup2(p1[1], 1); /* Standard out to p2's standard in */ close(p1[0]); close(p1[1]); dup2(p2[0], 10); /* fd 10 coming from the pipe */ close(p2[0]); close(p2[1]); execlp("p1", "p1", "200", NULL); error(); } if (fork() == 0) { dup2(p1[0], 0); /* Standard in from p1's standard out */ close(p1[0]); close(p1[1]); dup2(p2[1], 11); /* Fd 11 going to the pipe */ close(p2[0]); close(p2[1]); execlp("p2", "p2", "fred", NULL); error(); } close(p1[0]); /* Close extraneous fd's */ close(p1[1]); close(p2[0]); close(p2[1]); wait(&status); /* Wait for the children to exit */ wait(&status); exit(0); } |
Grading: Two points for each of the folling. It wasn't just that you had to have them; they had to be in correct place.
typedef struct { pthread_mutex_t *lock; int n; int running; } Info; void *waiter(void *arg) { Info *I; int status; I = (Info *) arg; while (1) { wait(&status); pthread_mutex_lock(I->lock); I->running--; if (I->running < I->n) { if (fork() == 0) { execlp("experiment", "experiment", NULL); exit(1); } I->running++; } pthread_mutex_unlock(I->lock); } } |
The the main() thread makes the scanf() call, and if necessary, it forks off experiment processes. It's a good idea to hold the mutex here. Why? Because you don't want the waiter thread messing with I.running during this process -- it's safer to lock out the waiter thread. Before unlocking the mutex, it should make sure that I.running and I.n have the correct values.
There's a slightly subtle issue in that you don't want to fork off the waiter thread until you've created the first experiment process. This is because wait() returns instantly when you have no children. I don't like those semantics, but they didn't consult me. Here's the code (all of it is in skipex.c)
main() { Info I; pthread_t tid; int new_n, i; I.lock = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(I.lock, NULL); I.n = 0; I.running = 0; while (1) { if (scanf("%d", &new_n) != 1 || new_n <= 0) exit(0); pthread_mutex_lock(I.lock); while (I.running < new_n) { if (fork() == 0) { execlp("experiment", "experiment", NULL); exit(1); } I.running++; } if (I.n == 0) pthread_create(&tid, NULL, waiter, (void *) &I); I.n = new_n; pthread_mutex_unlock(I.lock); } } |
You can test this using a simple experiment.c that sleeps for a random period of time between 1 and 5 seconds, and prints its status to logfile.txt. Simply check the output of ps to see if the correct number of processes are running.
I'm sure y'all will be happy to know that my original version of this question used system("experiment &"), which resulted in fork-bombing my Mac. Ha ha.
Grading: Out of 20. I simply assigned a grade which I felt assessed how well your program solved the problem.
The first approach: The key observation here is that if open a pipe to a child process and try to read from it, the read call will return when the child dies, because the write end is gone. So, you will maintain a list of read fd's to your children. You can use a FD_SET for this -- isn't that handy. When you fork off a child, you set up a pipe so that the child has the write end open and the parent has the read end open. You add the read fd to the FD_SET. You also add file descriptor 0 to the FD_SET.
The main loop has the parent calling select() on a copy of the FD_SET. If you see that file descriptor 0 has something to read, then you read a new value of n and perhaps fork off new children. If you see that another fd is set, then you clear it from the FD_SET and call wait(). You can be guaranteed that the wait() call will return. If necessary, you fork off another child.
Here's a picture when n equals three.
The code is in skipex-386.c. In some respects, its easier than the pthread code
The second approach. The second approach had two processes. Call them the parent and child. The parent has two pipes -- one going to and one coming from the child. It calls select() on the pipe coming from the child and on standard input. When it reads n, it simply stores it. When it reads from the pipe, it reads a request from the child for n, and sends n to the child as a response.
The child, on the other hand, first requests n, which it gets once the parent reads it for the first time. At that point, the child forks/execs experiment processes and calls wait(). Each time that wait() returns, the child requests n from the parent. That way, the child knows how many more experiment processes to create.
A few of you tried this approach with one pipe, having the parent call wait, but having the child fork off the experiments. This doesn't work , because the parent can't wait for threads created by the child. Instead, you need two pipes, so that the two threads can have two-way communication between them.
This approach is a little flawed, because when the user changes n, he/she has to wait for an experiment process to die before the extra threads are created. Grading: Out of 20. This is the same as the previous problem. It was easier for me to simply look at your solution wholistically and assign a grade according to how well your solution worked..
Address | Identity | Value(s) |
0x60f8 to 0x60fb | ip's chunk size | 24 |
0x60fc to 0x60ff | Nothing | |
0x6100 to 0x6103 | ip[0] | 5 |
0x6104 to 0x6107 | ip[1] | 6 |
0x6108 to 0x610b | ip[2] | 7 |
0x610c to 0x610f | ip[3] | 8 |
0x6110 to 0x6113 | s's chunk size | 24 |
0x6114 to 0x6117 | Nothing | |
0x6118 to 0x611b | s[0] to s[3] | 'A', 'B', 'C', 'D' |
0x611c to 0x611f | s[4] to s[7] | 'E', 'F', 'G', 'H' |
0x6120 to 0x6123 | s[8] and three bytes | '\0', -, -, - |
0x6124 to 0x6127 | Nothing | |
0x6128 to 0x612b | The free block's size | 48 |
0x612c to 0x612f | The free block's flink: | 0x6173 |
0x6130 to 0x6133 | The free block's blink: | NULL |
0x6134 to 0x6137 | Nothing -- the free block | |
0x6138 to 0x613b | Nothing -- the free block | |
0x613c to 0x613f | Nothing -- the free block | |
0x6140 to 0x6143 | Nothing -- the free block | |
0x6144 to 0x6147 | Nothing -- the free block | |
0x6148 to 0x614b | Nothing -- the free block | |
0x614c to 0x614f | Nothing -- the free block | |
0x6150 to 0x6153 | Nothing -- the free block | |
0x6154 to 0x6157 | Nothing -- the free block | |
0x6158 to 0x615b | i2's chunk size | 24 |
0x615c to 0x615f | Nothing | |
0x6160 to 0x6163 | i2[0] | 10 |
0x6164 to 0x6167 | i2[1] | 11 |
0x6168 to 0x616b | i2[2] | 12 |
0x616c to 0x616f | Nothing | |
0x6170 to 0x6173 | The free block's size | 8072 |
0x6174 to 0x6177 | The free block's flink: | NULL |
0x6178 to 0x617b | The free block's blink: | 0x6128 |
0x617c to 0x617f | Nothing -- the free block | |
.... | Nothing -- the free block |
Grading Again, out of 20, allocated as follows: