CS360 Final Exam - May 4, 2017 - James S. Plank

Answers and Explanations

Question 1

Straightforward assembler:

g:
    push #4              / Allocate i on the stack

    ld [fp+12] -> %r0    / i = j
    st %r0 -> [fp]      
    b l1

l0:
    ld [fp+12] -> %r0    / i = g(i, j)
    st %r0 -> [sp]--
    ld [fp] -> %r0
    st %r0 -> [sp]--
    jsr g
    pop #8
    st %r0 -> [fp]
  
l1:
    ld [fp] -> %r0       / (i > k) - branch on negation
    ld [fp+16] -> %r1
    cmp
    ble l2

    ld [fp+12] -> %r0    / j += 5
    mov #5 -> %r1
    add %r0, %r1 -> %r0
    st %r0 -> [fp+12]
    b l0                 / Back to the top of the loop

l2:
    ld [fp+12] -> %r0    / return j
    ret

Grading: 20 points:


Question 2

Here are the errors that I would check:

Grading

I was pretty lenient here. Each one of the above checks would get you five points. If you didn't tell me how you checked to see that x's size is 24, then you lost points. I gave some credit to other answers that made sense, even if they weren't the answers above. The max score here was 20.


Question 3

This is a nuts and bolts fork/exec/dup/pipe/wait problem:

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

int main()
{
  int p[2];
  int fd;
  int dummy;

  pipe(p);
  if (fork() == 0) {
    fd = open("input.txt", O_RDONLY);
    if (fd < 0) { perror("input.txt"); exit(1); }
    dup2(fd, 0);
    dup2(p[1], 1);
    close(fd);
    close(p[0]);
    close(p[1]);
    execlp("a.out", "a.out", "500", NULL);
    perror("Execlp failed on a.out");
    exit(1);
  }

  if (fork() == 0) {
    fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    dup2(fd, 1);
    dup2(p[0], 0);
    close(fd);
    close(p[0]);
    close(p[1]);
    execlp("sed", "sed", "-e", "s/X/Y/", NULL);
    perror("Execlp failed on sed");
    exit(1);
  }

  close(p[0]);
  close(p[1]);
  wait(&dummy);
  wait(&dummy);
}

Grading

Each of these was worth a point:


Question 4

There were two parts to this question. The first was straightforward -- write a main() that creates the proper shared and private information, and fork off n threads:

int main(int argc, char **argv)
{
  Shared *s;
  pthread_t tid;
  int i;
  Info *ti;

  s = (Shared *) malloc(sizeof(Shared));
  s->n = atoi(argv[1]);
  s->m = new_mutex();
  s->c = (PCT **) malloc(sizeof(PCT *) * s->n);
  for (i = 0; i < s->n; i++) s->c[i] = new_cond();
  s->turn = 0;

  for (i = 0; i < s->n; i++) {
    ti = (Info *) malloc(sizeof(Info));
    ti->id = i;
    ti->s = s;
    pthread_create(&tid, NULL, thread, (void *) ti);
  }
  pthread_exit(NULL);
}

The second part was the part where you had to figure out synchronization. You needed to call perform_calculation() outside of any mutex. Then you need to lock the mutex and potentially block until it's your turn. When it is your turn, then you call update_state(), still holding the mutex. Then you set s->turn to be the next thread and signal its condition variable. Finally, you release the mutex:

void *thread(void *arg)
{
  Info *ti;
  Shared *s;

  ti = (Info *) arg;
  s = ti->s;

  while (1) {
    perform_calculation(ti->id);
    pthread_mutex_lock(s->m);
    if (s->turn != ti->id) pthread_cond_wait(s->c[ti->id], s->m);
    update_state(ti->id);
    s->turn++;
    if (s->turn == s->n) s->turn = 0;
    pthread_cond_signal(s->c[s->turn]);
    pthread_mutex_unlock(s->m);
  }
  return NULL; /* Shut the compiler up */
}

There are a few subtleties. First, you need to create all of the condition variables before creating threads. Some of you called new_cond() in the loop that created threads. That will be a problem if a thread tries to signal the next thread's condition variable before that variable has been created.

Second, the main thread needs to call pthread_exit(NULL) and not return. If it calls return, then the process dies.

Grading

  • Extract n from the command line: 1 point
  • Malloc one copy of shared: 1 point
  • Malloc one copy of info per thread: 1 point
  • Initialize shared's condition variables: 1 point
  • Initialize the rest of shared properly: 1 point
  • Initialize info properly for each thread: 1 point
  • Call pthread_create to create each thread.: 1 point
  • Call pthread_exit on the main thread.: 1 point
  • Cast the thread argument to an Info: 1 point
  • While(1) loop: 1 point
  • Call perform_calculation outside of a mutex: 1 point
  • Lock the mutex before update_state: 1 point
  • Pthread_cond_wait until it's your turn: 2 points
  • Call update_state() when it's your turn: 1 point
  • Set turn to be the next thread: 2 points
  • Call pthread_cond_signal to signal the next thread.: 2 points
  • Unlock the mutex: 1 point If your synchronization was off (e.g. you do something like "if it's my turn, then call update_state(), otherwise call wait"), you lost three points.


    Question 5

    SIGPIPE is a signal, which the operating system sends to your program when you try to write to a file descriptor for which there is no read end. This happens, for example, when you try to write to the write-end of a pipe, and all of the read ends have been closed. A second example is when you try to write to a socket when all of the other ends of the socket have been closed.

    You need to write a signal handler, which is a procedure that you register with the operating system. When the operating system sends SIGPIPE to your program, it interrupts what the program is doing, and calls your signal handler. When you have "handled" the signal in your signal handler, you return, and the operating system has your program return to what it was doing when it was interrupted by the signal.

    The cleanest way to deal with SIGPIPE is to register a handler that does nothing. It simply returns. Then, you do output with the standard IO library (via fprintf(), fwrite() or fputs() for example, and with fflush()), and you test the return values of these calls. When they flag an error, you know that SIGPIPE has occurred. This lets you handle these output errors very cleanly.

    Grading


    Question 6

    0x27c54000.