Please see the final grade histogram and comments.
Please give us feedback if you have time so we can do whatever tweaking might be necessary for the offering in the Fall.
I also figured out why the in-class demonstration of how to use gdb to find deadlocks didn't work as planned - to get reliable backtraces, one has to force the use of an older version of Linux's pthread implementation by setting the LD_ASSUME_KERNEL environment variable to 2.4.1. Here's how to use jstack to look at backtraces in a deadlocked JVM. Here's how to attach gdb to a deadlocked process. The code for the account example is here: account.cc (C++), Account.java (Java).
The actual ordering is: P's return address, P's prev frame pointer, x1, x2, y1, Q's return address, Q's prev frame, y2.
In other words, given the situation in Figure 1.28 (a), the arguments (y1 here) are pushed first, then a CALL is made that pushes the return address, then the callee saves the caller's frame pointer, and then space for locals (y2 in this case) is allocated on the stack by adjusting the stack pointer.
In addition, the drawing in the picture is upside-down, as most stacks grow downward, from higher to lower addresses. An exception is (really was) HP's PA-RISC architecture.