COSC 360 Final Exam for Spring, 2021 - Answers and Grading

James S. Plank

Question 1 - 25 points

Here's the answer to the example question. First, let's enumerate the free and allocated chunks, starting from the beginning of the heap. You'll note that the heap starts with a free chunk:
Chunk Start    Size in Decimal   Size in Hex    Allocated/Free
-----------    ---------------   -----------    --------------
0x147d58             40             0x28          Free
0x147d80             56             0x38          Allocated
0x147db8             32             0x20          Free
0x147dd8             64             0x40          Allocated
0x147e18             32             0x20          Free
0x147e38             32             0x20          Allocated
0x147e58             48             0x30          Free
0x147e88            208             0xd0          Allocated
How did I figure those values out? Here's how I did it: Now, let's answer the questions:

Grading

Three points per question, with the exception of question 3, which was worth four points.


Question 2 - 5 points

The goal here is to write a program where a child is alive and its parent is not. So, the simplest strategy is to call fork(), have the parent exit instantly, and then have the child sleep and print out the line:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
  if (fork() != 0) return 0;    // The parent exits instantly. 
  sleep(1);
  printf("I am an orphan.\n");  // The child waits for a second to print its line.
  return 0;
}


Question 3 - 5 points

A zombie process is a process that has exited, but its parent has not called wait() on it to clean up its exit state. Its parent must be alive, but it hasn't called wait().


Question 4 - 10 points

The sequence of events here should be: Now, you can create the pipe in the parent or the child. It's easier if you do it in the child, because then you don't need to close file descriptors in the parent. Here are both answers:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* Have the child create the pipe. */

int main()
{
  int status;
  int p[2];

  if (fork() != 0) {                    // The parent simply creates the child and waits.
    wait(&status);
    return 0;

  } else {
    pipe(p);
    close(p[0]);                        // Close the read end.
    while(1) write(p[1], "Fred", 4);    // Repeatedly write until you generate SIGPIPE
    return 0;
  }
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* Have the parent create the pipe. */

int main()
{
  int status;
  int p[2];

  pipe(p);

  if (fork() != 0) {                    // Since the parent creates the pipe, it needs to
    close(p[0]);                        // close the read end.
    wait(&status);       
    return 0;

  } else {
    close(p[0]);                        // The child needs to close the read end, too.
    while(1) write(p[1], "Fred", 4);    // Repeatedly write until you generate SIGPIPE
    return 0;
  }
}

Grading

Typically, you started with 10 points, and then received deductions, such as not closing enough file descriptors, writing to a closed file descriptor, no wait() call, etc.


Question 5 - 5 points

This was a garden variety fork-bomb question, of the type I've asked about 5 times in clicker questions. Sure, the loop involves strings, but it's really the same loop, which executes once for each character in the string. The answer is 2strlen(s).


Question 6 - 25 points

This is a straightforward fork-pipe-dup-exec question. Let's go through what the various processes do. First, the Y process: Next the E process: The D process is very similar, but it sets up it's I/O differently: Now, the 10-second thing takes some extra care, and you can implement it either with threads or alarm(). The description will be in the answers below.

Grading

Each part was worth 3 points, except question 3 was worth two, and question 7 was worth two.


Question 7 - 15 points

There's a lot of scaffolding to this problem, but I did that so you wouldn't have to waste your time with malloc() and initialization calls. There's not much commentary needed, as the threads simply do what the question asks:


  /* Missing Thread Code. */

  pthread_mutex_lock(s->lock);
  while (1) {
    pthread_cond_wait(s->cvs[p->id], s->lock);
    if (scanf("%d", &num) != 1) exit(0);            // This kills the process when stdin is done.
    printf("%3ld: Thread %d\n", time(0) - s->base_time, p->id);
    fflush(stdout);
    sleep(num);
    pthread_cond_signal(s->main_cv);
  }

  /* Missing Main Code.  It would be OK for you to lock the mutex at the beginning
     and forget about it.  */

  i = 0;
  while (1) {
    printf("%3ld: Main\n", time(0) - s->base_time);
    sleep(1);
    pthread_mutex_lock(s->lock);
    pthread_cond_signal(s->cvs[i]);
    pthread_cond_wait(s->main_cv, s->lock);
    pthread_mutex_unlock(s->lock);
    i = (i + 1) % n;
  }

Grading

Typically you started at 15 points and received deductions. Common deductions were not holding the mutex while calling wait, having your main loop incorrect, not reading standard input correctly, and calling pthread_exit() rather than exit().

Question 8 - 10 points

This one is a little different, because you need the threads to update some shared state, and to signal the main thread when everyone is ready. Here's the code I would write -- it's a little inefficient, because all of the threads signal the main thread. You could make it so that only the last thread signals the main thread, and it would be more efficient.


  /* Missing Thread Code */

  pthread_mutex_lock(s->lock);
  printf("I'm Ready\n");
  s->n++;
  pthread_cond_signal(s->main_cv);
  pthread_mutex_unlock(s->lock);
  while(1);


  /* Missing Main Code */

  pthread_mutex_lock(s->lock);
  while (s->n < n) pthread_cond_wait(s->main_cv, s->lock);
  printf("I'm Ready Too\n");
  pthread_mutex_unlock(s->lock);  // This is unnecessary, but good form.
  while(1);

Grading

Typically you started at 10 points and received deductions. Common deductions were not holding the mutex while calling wait, having a race condition on n, and having multiple threads able to call signal before the main thread wakes up, which means that the main thread cannot count the number of times it wakes up.