CS560 Midterm Exam: March 15, 2006 - Answers


Question 1

Grading

1.5 points for each of b, c, g and l. I ignored your answer of e. Minus 0.5 for k. Minus 1 point for any other answer. You couldn't go negative.

Question 2

When the scheduler determines that it is time to schedule a STARTING thread, it calls setjmp(), then modifies the stack and frame pointers of the jmpbuf so that they point to the base of the new thread's stack. Then the scheduler calls longjmp() so that it starts executing on the new stack. Obviously, at this point, the program cannot rely upon the values of any local variables. It calls the thread's function on its argument, and when that returns, it exits by either shutting down the thread system or switching to another thread, never to return again.

Grading


Question 3

The following will get you a merit raise: "Freddy's got the right idea -- you can implement a mutex using a semaphore whose initial value is one: lock() is implemented by calling P() on the semaphore, and unlock() is implemented by calling V(). However, there are two fundamental differences between mutexes and semaphores. First, calling V() on a semaphore with a positive value is a well-defined operation. Calling unlock() on an unlocked mutex is, however, not defined. Second, there is no restriction on which threads call P() and V() on semaphores, while only the thread that has locked a mutex may unlock it.

Therefore, if you want the correct semantics of mutexes, you can implement them with semaphores, but you have to augment the implementation to enforce these two restrictions. That is why we have mutexes as a separate data structure.

Grading


Question 4

The easiest way to do this is to prevent circular wait. Simply put the resources into a red-black tree, keyed on name/instance, and traverse it:

void lock_multiple(Dllist resources) 
{
  JRB t, tmp;
  Dllist dtmp;
  Resource *r;
  char *key;

  t = make_jrb();
  dll_traverse(dtmp, resources) {
    r = (Resource *) dtmp->val.v;
    key = (char *) malloc(sizeof(char) * (strlen(r->name+20));
    sprintf(key, "%10d %s", r->instance, r->name);
    jrb_insert_str(t, key, new_jval_v((void *) r));
  }
  while (!jrb_empty(t)) {
    r = (Resource *) t->flink->val.v;
    key = t->flink->key.s;
    P(r->s);
    jrb_delete_node(t->flink);
    free(key);
  }
  jrb_free_tree(t);
}

Unlock_multiple() can remain unchanged.

This removes circular wait and thus prevents deadlock. You need the first condition, because otherwise, processes can lock resources in non-ascending order, and circular wait can occur. The second condition is necessary for the same reason -- if processes can lock resources without calling lock_multiple, then they can try to lock them in any order, and circular wait can occur again.

Grading


Question 5

Grading