CS360 Final Exam, 2022, Answers and Grading

Question 1

This is straightforward assembler:

fred:
  push #4                     // Allocate j - it will be at [fp]

  ld [fp+12] -> %r0           // Load x into r0
  st %r0 -> [fp]              // And store it to j

  ld [fp+16] -> %r0           // Load y
  ld [fp] -> %r1              // And j;
  add %r0, %r1 -> %r0         // Add them
  st %r0 -> [fp+16]           // Store the result into y

  ld [fp+16] -> %r0           // Return y in register r0
  ret

Grading

8 points. Loosely one point per instruction.


Question 2

Since you're using register 2, you'll need to spill it onto the stack. The answer is:

st %r2 -> [sp]--

Grading

: This was 2 points. You got partial credit for skipping this instruction and instead loading [fp+12] into a register.

Question 3

Man, I screwed this up -- as many of you mentioned, I spaced and added instead of subtracting from the stack pointer. For that reason, I'll give full credit to answers which added or subtracted from the stack pointer. I'll put both below:
  1. This will be d: 1
  2. 0xfff5c (subtracting): This will be the argument to e(): d+b+c = 14 or 0xe
    0xfff64 (adding): This is the fp of the procedure that called a().
  3. 0xfff58 (subtracting): This will be the pc of the instruction after jsr. By my accounting the instructions of a will be:

    push #4
    st %g1 -> [fp]
    ld [fp] > %r0
    ld [fp+12] -> %r1
    add %r0, %r1 -> %r0
    ld [fp+16] -> %r1
    add %r0, %r1 -> %r0
    st %r0 -> [sp]--
    jsr e
    

    So, 9*4 = 36 = 0x24, and the answer is 0x4010+0x24 = 0x4034.
    0xfff68 (adding): This is 4 bytes past the jsr instruction that called a.

  4. 0xfff54 (adding): This is the fp if a(): 0xfff60.
    0xfff6c (subtracting). This is the first argument of a(): 5.
  5. "pop #4" -- gotta pop the argument off of the stack.
  6. You need to store the return value into d: "st %r0 -> [fp]".

Grading

One point each.
  1. Full point for 1. No partial credit.
  2. Full point for 14, or for anything that looks like an old frame pointer (something that starts with 0xfff6 and is larger than 0xfff60).
  3. Full point for anything that looks like a program counter.
  4. Full points for 5 and 0xfff60. Partial credit for answers that look like a fp, and for the value 8.
  5. Full credit for "pop #4". Partial credit for other pop's and for storing r0 into [fp].
  6. Full credit for storing r0 into [fp]. Partial credit for pop's and returns.
Below is the histogram for both questions 2 and 3:


Question 4

This question came from a bank, so the one in the exam here is just one example.
  1. This is the value at address 0x809cd0: 0x28 or 40
  2. This is the flink field, which is at 0x809cd4: 0x809cf8
  3. This is the value at address 0x809cf8: 0x30 or 48
  4. This is the flink field, which is at 0x809cfc: 0x809c58
  5. This is the value at address 0x809c58: 0x18 or 24
  6. The first address in the listed memory: 0x809c40
  7. The value at 0x809c40: 0x18 or 24
  8. That chunk abuts the free chunk at 0x809c58. That chunk is 0x18 bytes, and 0x809c58 + 0x18 = 0x809c70. That is not a free chunk, so it is the answer: 0x809c70
  9. The value at 0x809c70: 0x20 or 32
  10. 0x809c70 + 0x20 = 0x809c90. That is not a free chunk, so it is the answer: 0x809c90
  11. The value at 0x809c90: 0x40 or 64.
  12. 0x809c90 + 0x40 = 0x809cd0. That is free and 0x28 bytes, so we move on. 0x809cd0 + 0x28 = 0x809cf8. That is also free, so we move on again. 0x809cf8 + 0x30 = 0x809d28. That is the answer: 0x809d28.
  13. The value at 0x809d28: 0x28 or 40.
  14. The second chunk is at 0x809c58, so when malloc() returned, it returned 0x809c60. If that is a, then (a+1) is 0x809c64. The value there is 0x809c94.
Here are the other answers from the other banks -- they are keyed by their freelist pointers.

     1	0x8027a8: 0x28 or 40
     2	0x8027a8: 0x8027d0
     3	0x8027a8: 0x30 or 48
     4	0x8027a8: 0x802750
     5	0x8027a8: 0x18 or 24
     6	0x8027a8: 0x802710
     7	0x8027a8: 0x40 or 64
     8	0x8027a8: 0x802768
     9	0x8027a8: 0x18 or 24
    10	0x8027a8: 0x802780
    11	0x8027a8: 0x28 or 40
    12	0x8027a8: 0x802800
    13	0x8027a8: 0x20
    14	0x8027a8: 0x802774
     1	0x8075a8: 0x30 or 48
     2	0x8075a8: 0x8075d8
     3	0x8075a8: 0x28 or 40
     4	0x8075a8: 0x807548
     5	0x8075a8: 0x18 or 24
     6	0x8075a8: 0x807530
     7	0x8075a8: 0x18 or 24
     8	0x8075a8: 0x807560
     9	0x8075a8: 0x28 or 40
    10	0x8075a8: 0x807588
    11	0x8075a8: 0x20 or 32
    12	0x8075a8: 0x807600
    13	0x8075a8: 0x40 or 64
    14	0x8075a8: 0x80756c
     1	0x87a1d0: 0x18 or 24
     2	0x87a1d0: 0x87a1e8
     3	0x87a1d0: 0x28 or 40
     4	0x87a1d0: 0x87a160
     5	0x87a1d0: 0x30 or 48
     6	0x87a1d0: 0x87a120
     7	0x87a1d0: 0x40 or 64
     8	0x87a1d0: 0x87a190
     9	0x87a1d0: 0x18 or 24
    10	0x87a1d0: 0x87a1a8
    11	0x87a1d0: 0x28 or 40
    12	0x87a1d0: 0x87a110
    13	0x87a1d0: 0x20 or 32
    14	0x87a1d0: 0x87a19c

Grading

Each part was worth 1.5 points, except 1 and 14, which were worth 1.


Question 5

The problem here is that you are holding the mutex while doing I/O on the socket:

Problem 1: You are holding the mutex while you perform the second fread(). Let's suppose that the client writes an integer and then waits 60 seconds before writing the second integer. Then the interact() thread will lock out all of the other threads for 60 seconds, potentially delaying all of the other clients.

Solution 1: Instead of reading twice to x, add a second variable, say y, and do the two fread() calls before you lock the mutex. Then, when you lock the mutex, you can update data with both integers. That way, you don't lock the mutex while waiting for the second integer.

Problem 2: You are holding the mutex while you do the fwrite() and fflush(). If the connection to the client is slow, then you are locking out the other threads during this slow operation.

Solution 2: While you're holding the mutex, make a copy of data. Then unlock the mutex and write the copy. That way, you don't lock the mutex while performing the potentially slow fwrite()/fflush().

Grading

Five points for each problem and solution.


Question 6

Part A: This is a simple use of wait()/signal():

void find_match(Person *me, char *keyword)
{
  JRB tmp;
  Person *match;

  pthread_mutex_lock(lock);
  tmp = jrb_find_str(tree, keyword);                        /* Look up the keyword */

  if (tmp == NULL) {                                        /* If it's not there, put me into the tree and wait. */
    jrb_insert_str(tree, keyword, new_jval_v((void *) me);
    pthread_cond_wait(me->cond, lock);                      /* When I unblock, my match has been set and I have
                                                               been delete from the tree. */

  } else {                                                  /* If the keyword is there, make the match, remove */
    match = (Person *) tmp->val.v;                          /* the match from the tree, set the match fields, */
    jrb_delete_node(tmp);                                   /* and wake up the match. */
    me->match = match;
    match->match = me;
    pthread_mutex_signal(match->cond);
    
  }
  pthread_mutex_unlock(lock);
  return;
}

Part B: This can happen with the following sequence:

Grading

12 points for part A. Typically, you started with 12 and had points deducted for incorrect things. The remaining four points were for part B, which most students did not get. I typically gave a point for saying something reasonable.


Question 7

The code is the best explanation:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "sockettome.h"
#include <sys/wait.h>

int main()
{
  int sock, fd;
  int p[2];
  int kim, khloe;
  int pid, status;
  int khloe_done;
  int ndone;

  sock = serve_socket(90210);
  fd = accept_connection(sock);

  pipe(p);
  
  kim = fork();

  if (kim == 0) {       /* Set up kim and let her execute */
    dup2(fd, 0);        /* Stdin comes from the socket. */
    dup2(p[1], 1);      /* Stdout goes to the pipe */
    close(p[0]);
    close(p[1]);
    close(fd);
    execlp("kim", "kim", NULL);
    exit(1);                     /* Cardinal sin. */
  } 
    
  khloe = fork();        /* Set up khloe to read from kim and write to the socket. */
  if (khloe == 0) {
    dup2(p[0], 0);
    dup2(fd, 1);
    close(p[0]);
    close(p[1]);
    close(fd);
    execlp("khloe", "khloe", NULL);
    exit(1);                     /* Cardinal sin. */
  }

  khloe_done = 0;        /* Wait for khloe to finish.  You need to handle the case */
  ndone = 0;             /* where kim finishes before khloe, and count the number of */
                         /* children who have finished. */
  while (!khloe_done) {     
    pid = wait(&status);
    ndone++;
    if (pid == khloe) khloe_done = 1;
  }
     
  if (fork() == 0) {    /* Now fork off kourtney and have her take over for khloe. */
    dup2(p[0], 0);
    dup2(fd, 1);
    close(p[0]);
    close(p[1]);
    close(fd);
    execlp("kourtney", "kourtney", NULL);
    exit(1);                     /* Cardinal sin. */
  }

  /* Close all extraneous file descriptors and wait for the rest of the children to exit. */

  close(p[0]);
  close(p[1]);
  close(fd);
  
  while (ndone < 3) {
    pid = wait(&status);
    ndone++;
  }
  
  return 0;
}

Grading

Typically you started with 20 points and had points deducted. If you had "too many waits (uncommented)", it meant that you had more wait() calls than you had children. If you commented that you knew that was the case and that the superfluous waits would simply return, then you didn't lose the point.