CS360 Final Exam - May 5, 2016 - James S. Plank

Answers and Explanations

Question 1

The easiest thing here is to start by identifying blocks on the free list. Do it directly on the exam. Then whatever blocks aren't on the free list must be allocated with malloc(). You simply need to check the sizes to identify when there are multiple chunks in a region of memory. Here is the annotated memory:

You derive the answers from that:

Free list chunks:
-----------------
1. [ 0x10758, 0x10 (16) ]
2. [ 0x10680, 0x38 (56) ]
3. [ 0x10638, 0x38 (56) ]
4. [ 0x106d0, 0x50 (80) ]
5. [ 0x10790, 0x10 (16) ]

Allocated chunks:
1. [ 0x10670, 0x10 (16) ]
2. [ 0x106b8, 0x18 (24) ]
3. [ 0x10720, 0x20 (32) ]
4. [ 0x10740, 0x18 (24) ]
5. [ 0x10768, 0x28 (40) ]
Grading: Each address is worth 1 point, as is each chunk size.


Question 2

Grading: Two points per question with no partial credit.


Question 3

This is straightforward. You need a mutex, and each philosopher needs a condition variable. So, here are the first two sets of additions:

After 3:
  PCM *lock;
  PCT **conds;

After 14:
  p->lock = new_mutex();
  p->conds = (PCT **) malloc(sizeof(PCT *)*p->num);
  for (i = 0; i < p->num; i++) p->conds[i] = new_cond();

In pickup(), you need to hold the mutex for the duration of the procedure, and instead of sleeping, you need to block on your condition variable:

After 25:
  pthread_mutex_lock(p->lock);

After 33:
  pthread_mutex_unlock(p->lock);

After 28:
  pthread_cond_wait(p->conds[i], p->lock);

Finally, in putdown(), you also need to hold the lock for the duration of the procedure, and you need to signal the two philosophers on either side after you have dropped the chopsticks:

After 38:
  pthread_mutex_lock(p->lock);

After 43:
  pthread_cond_signal(p->conds[(i+1)%p->num]);
  pthread_cond_signal(p->conds[(i+p->num-1)%p->num]);
  pthread_mutex_unlock(p->lock);

Grading: 20 points. You started with 20 if you had the right structure (one lock, one condition variable per philosopher). You started with 12 if you only had one condition variable. I then took off points for various things.


Question 4

Most definitely, this was the most time consuming question on the exam. Let's break it into its parts:

The program is in q4.c. I'll break it up into its components, each of which is commented inline. It works if you want to test it, using telnet for clients. If you do, I suggest changing TIME to something small so that you can see it working. I have an example MBI.txt you can use, but you can create your own for your own brilliant ideas!

Initial Stuff:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include "sockettome.h"

/* I've made this #define so that you can test it on smaller intervals. */

#define TIME (3600)


Global Variables:

/* Global variables: The buffer and the mutex. */

char BUFFER[501];
pthread_mutex_t lock;


The Signal Handler:

/* The sigpipe handler simply re-registers itself as a signal
   handler, and then ignores SIGPIPE. The standard I/O library will
   catch closed socket connections.  */

void sigpipe_handler(int dummy)
{
  signal(SIGPIPE, sigpipe_handler);
  return;
}


The Idea Thread:

/* The idea thread opens MBI.txt, and then reads a line from it using
   fgets every TIME seconds.  It protects the fgets call with a mutex so
   that there are no race conditions.  It exits when MBI.txt has nothing
   else to read.  */

void *idea(void *arg)
{
  FILE *f;
  
  f = fopen("MBI.txt", "r");
  if (f == NULL) { perror("MBI.txt"); exit(1); }

  while (1) {
    pthread_mutex_lock(&lock);
    if (fgets(BUFFER, 500, f) == NULL) {
      pthread_mutex_unlock(&lock);
      return NULL;
    }
    pthread_mutex_unlock(&lock);
    sleep(TIME);
  }
}


The Connection Thread:

/* The connection thread assumes that it is passed a FILE *, 
   which has been created by the main thread using fdopen() on 
   a socket connection.  Every TIME seconds it copies BUFFER
   to a local buffer, and then tries to write it to the socket
   connection.  If either fputs() or fflush() report an error,
   then the thread exits gracefully.  Each thread uses its own
   buffer so that it is not holding the mutex during the write
   to the socket. */

void *connection(void *arg)
{
  FILE *f;
  char buffer[501];

  f = (FILE *) arg;

  while (1) {
    pthread_mutex_lock(&lock);
    strcpy(buffer, BUFFER);
    pthread_mutex_unlock(&lock);
    
    if (fputs(buffer, f) == EOF) {
      fclose(f);
      return NULL;
    }

    if (fflush(f) == EOF) {
      fclose(f);
      return NULL;
    }

    sleep(TIME);
  }
}


The Main Thread:

/* Finally, the main thread sets up the socket and the mutex, and it
   creates the idea thread.  Then it accepts socket connections,
   creating FILE *'s for each connection using fdopen(), and then
   creating a thread to handle the connection.  It calls
   pthread_detach() so that when a thread dies, its state is cleaned up.
   */
   
int main()
{
  int s, fd;
  FILE *f;
  pthread_t tid;

  signal(SIGPIPE, sigpipe_handler);

  s = serve_socket(30602);
  pthread_mutex_init(&lock, NULL);

  pthread_create(&tid, NULL, idea, NULL);
  pthread_detach(tid);

  while (1) {
    fd = accept_connection(s);
    f = fdopen(fd, "w");
    pthread_create(&tid, NULL, connection, (void *) f);
    pthread_detach(tid);
  }
}

Grading

The main, idea and connection threads were worth 6 points each. SIGPIPE was worth two. I deducted for various things like holding the mutex while writing to the socket, poor exit conditions, memory leaks, etc.