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

• Push 4: 1 point
• Setting i to j correctly: 3 points
• Branch to the comparison: 1 point
• i = g(i,j) put in the correct place: 1 point
• The comparison: 3 points
• Branch on the negation: 1 point
• Push j then i on the stack: 2 points
• jsr g: 1 point
• Pop 8: 1 point
• Set i to r0 after calling g: 1 point
• j += 5: 3 points
• Returning j at the end: 2 points.

### Question 2

Here are the errors that I would check:
• Unaligned pointer: Check to see if x is a multiple of 8. Check every value from fp[0] to fp[i-1] to see if it is a multiple of eight.

• Bad size for x: Check the four bytes starting eight bytes behind x. The easiest way to do that is to check x[-2]. This should be equal to 24 (13 padded to 16, then add 8 for bookkeeping).

• Number of blocks on the free list: There should be two blocks on the free list, because my_malloc() does not coalesce chunks when it calls my_free().

• The size field of the smaller of the two free blocks: The value of *fp[0] or *fp[1] should be equal to 88.

• The size field of the larger of the two free blocks: The value of *fp[0] or *fp[1] should be equal to 8K-88-24. That value is 8080, but you could just give me the expression.

• The three blocks should make up a contiguous regions of 8K bytes: You need to account for every byte of the three chunks (x, fp[0] and fp[1]) and make sure that they compose a contiguous region of exactly 8K bytes. If they don't then some of those chunks overlap, or they don't define a contiguous retion.

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 #include #include #include 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); } ```

Each of these was worth a point:
• Open input.txt
• Error check open of input.txt
• Open output.txt
• Error check open of input.txt
• Fork #1
• Fork #2
• Pipe called before fork calls
• Dup standard input of child 1 (from input.txt)
• Dup standard output of child 2 (to output.txt)
• Dup standard input of child 2 from the pipe
• Dup standard output of child 1 to the pipe
• Close all extraneous file descriptors in child 1
• Close all extraneous file descriptors in child 2
• Close all extraneous file descriptors in the parent
• Execlp of a.out in child 1
• Execlp of sed in child 2
• Arguments to sed done correctly
• Cardinal sin in child 1
• Cardinal sin in child 2
• Parent waits

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

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