CS360 Final Exam - May 9, 2011 - James S. Plank

Answer all questions on the answer sheet

Question 1

This is a nuts-and-bolts fork/dup/exec/wait question:

#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.


Question 2

Your two threads should share three pieces of data:

  1. A mutex.
  2. n: The number of processes that you want running.
  3. running: The number of processes that are actually running.
The thread that calls wait() is the more straightforward of the two -- call wait() and when you return, lock the mutex and decrement running. If running is less than n, then create another experiment process. Finally, unlock the mutex and repeat:

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.


Question 3

There are two ways to do this one, really. The first is how I went about solving it. However, a few of you tried a second approach which is a little flawed. I'll talk about both.

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..


Question 4

We have the following sequence of actions:

We are left with a freelist that has two chunks -- a 48-byte one followed by a 8072-byte one. Since ip is equal to 0x6100 we know that the 8192-byte block that sbrk() returned is equal to 0x60f8. This gives us the following picture of memory:

Address IdentityValue(s)
0x60f8 to 0x60fbip's chunk size24
0x60fc to 0x60ffNothing
0x6100 to 0x6103ip[0]5
0x6104 to 0x6107ip[1]6
0x6108 to 0x610bip[2]7
0x610c to 0x610fip[3]8
0x6110 to 0x6113s's chunk size24
0x6114 to 0x6117Nothing
0x6118 to 0x611bs[0] to s[3]'A', 'B', 'C', 'D'
0x611c to 0x611fs[4] to s[7]'E', 'F', 'G', 'H'
0x6120 to 0x6123s[8] and three bytes'\0', -, -, -
0x6124 to 0x6127Nothing
0x6128 to 0x612bThe free block's size48
0x612c to 0x612fThe free block's flink:0x6173
0x6130 to 0x6133The free block's blink:NULL
0x6134 to 0x6137Nothing -- the free block
0x6138 to 0x613bNothing -- the free block
0x613c to 0x613fNothing -- the free block
0x6140 to 0x6143Nothing -- the free block
0x6144 to 0x6147Nothing -- the free block
0x6148 to 0x614bNothing -- the free block
0x614c to 0x614fNothing -- the free block
0x6150 to 0x6153Nothing -- the free block
0x6154 to 0x6157Nothing -- the free block
0x6158 to 0x615bi2's chunk size24
0x615c to 0x615fNothing
0x6160 to 0x6163i2[0]10
0x6164 to 0x6167i2[1]11
0x6168 to 0x616bi2[2]12
0x616c to 0x616fNothing
0x6170 to 0x6173The free block's size8072
0x6174 to 0x6177The free block's flink:NULL
0x6178 to 0x617bThe free block's blink:0x6128
0x617c to 0x617fNothing -- the free block
....Nothing -- the free block

Grading Again, out of 20, allocated as follows: