<html> <head> <title>Lab: locks</title> <link rel="stylesheet" href="homework.css" type="text/css" /> </head> <body> <h1>Lab: locks</h1> <p>In this lab you will try to avoid lock contention for certain workloads. <h2>lock contention</h2> <p>The program user/kalloctest stresses xv6's memory allocator: three processes grow and shrink there address space, which will results in many calls to <tt>kalloc</tt> and <tt>kfree</tt>, respectively. <tt>kalloc</tt> and <tt>kfree</tt> obtain <tt>kmem.lock</tt>. To see if there is lock contention for <tt>kmem.lock</tt> replace the call to <tt>acquire</tt> in <tt>kalloc</tt> with the following code: <pre> while(!tryacquire(&kmem.lock)) { printf("!"); } </pre> <p><tt>tryacquire</tt> tries to acquire <tt>kmem.lock</tt>: if the lock is taking it returns false (0); otherwise, it returns true (1) and with the lock acquired. Your first job is to implement <tt>tryacquire</tt> in kernel/spinlock.c. <p>A few hints: <ul> <li>look at <tt>acquire</tt>. <li>don't forget to restore interrupts when acquision fails <li>Add tryacquire's signature to defs.h. </ul> <p>Run usertests to see if you didn't break anything. Note that usertests never prints "!"; there is never contention for <tt>kmem.lock</tt>. The caller is always able to immediately acquire the lock and never has to wait because some other process has the lock. <p>Now run kalloctest. You should see quite a number of "!" on the console. kalloctest causes many processes to contend on the <tt>kmem.lock</tt>. This lock contention is a bit artificial, because qemu is simulating 3 processors, but it is likely on real hardware, there would be contention too. <h2>Removing lock contention</h2> <p>The root cause of lock contention in kalloctest is that there is a single free list, protected by a single lock. To remove lock contention, you will have to redesign the memory allocator to avoid a single lock and list. The basic idea is to maintain a free list per CPU, each list with its own lock. Allocations and frees on each CPU can run in parallel, because each CPU will operate on a different list. <p> The main challenge will be to deal with the case that one CPU runs out of memory, but another CPU has still free memory; in that case, the one CPU must "steal" part of the other CPU's free list. Stealing may introduce lock contention, but that may be acceptable because it may happen infrequently. <p>Your job is to implement per-CPU freelists and stealing when one CPU is out of memory. Run kalloctest() to see if your implementation has removed lock contention. <p>Some hints: <ul> <li>You can use the constant <tt>NCPU</tt> in kernel/param.h <li>Let <tt>freerange</tt> give all free memory to the CPU running <tt>freerange</tt>. <li>The function <tt>cpuid</tt> returns the current core, but note that you can use it when interrupts are turned off and so you will need to turn on/off interrupts in your solution. </ul> <p>Run usertests to see if you don't break anything. <h2>Lock-free bcache lookup</h2> <p>Several processes reading different files repeatedly will bottleneck in the buffer cache, bcache, in bio.c. Replace the acquire in <tt>bget</tt> with <pre> while(!tryacquire(&bcache.lock)) { printf("!"); } </pre> and run test0 from bcachetest and you will see "!"s. <p>Modify <tt>bget</tt> so that a lookup for a buffer that is in the bcache doesn't need to acquire <tt>bcache.lock</tt>. This more tricky than the kalloc assignment, because bcache buffers are truly shared among processes. You must maintain the invariant that a buffer is only once in memory. <p> There are several races that <tt>bcache.lock</tt> protects against, including: <ul> <li>A <tt>brelse</tt> may set <tt>b->ref</tt> to 0, while concurrent <tt>bget</tt> is incrementing it. <li>Two <tt>bget</tt> may see <tt>b->ref = 0</tt> and one may re-use the buffer, while the other may replaces it with another block. <li>A concurrent <tt>brelse</tt> modifies the list that <tt>bget</tt> traverses. </ul> <p>A challenge is testing whether you code is still correct. One way to do is to artificially delay certain operations using <tt>sleepticks</tt>. <p>Here are some hints: <ul> <li> Use an atomic increment instruction for incrementing and decrementing <tt>b->ref</tt> (see <tt>__sync_fetch_and_add() and related primitives</tt>) <li>Don't walk the <tt>bcache.head</tt> list to find a buffer </ul> </body> </html>