1What does bcache.lock protect, and what could be split?
Before changing anything, list what bget, brelse, bpin and bunpin read
or write while they hold bcache.lock. Sort the list into facts about one buffer or
block, and facts about the cache as a whole. Then commit to an answer: when a hart asks
for block 46, which buffers must its lookup examine to be sure that block 46 is not
cached already, and which may it safely ignore?
Read bget and brelse (kernel/bio.c:57-kernel/bio.c:137) next to the table in Tour 30: The buffer cache that says which lock protects which field of a buffer.
The cache’s main promise is that a block is in at most one buffer. A lookup must see every buffer that could hold block 46, and nothing else. A buffer’s label (dev, blockno) changes only when the buffer is recycled for another block.
Make the place where a buffer lives a function of its block number, so that block n can only ever be found in one group of buffers. Give each group its own list and its own lock. Then go through the operations and see which ones still need more than one group.
The reference design
bcache.lock protects four different things:
| data | about | changed by |
|---|---|---|
the LRU list (prev, next, head) |
the whole cache | brelse (moves to the front), binit |
each buffer’s label: dev, blockno, and valid when relabeling |
one buffer | the recycling scan in bget |
each buffer’s refcnt |
one buffer | bget, brelse, bpin, bunpin |
| the answer to “is block n cached?” | one block, but potentially every buffer | nobody writes it; it is the scan |
The last line is why one lock covers all 30 buffers: in the original, a block can be in any buffer, so a lookup must scan them all, and nothing may relabel a buffer during the scan.
Fix where a block can live and the lookup shrinks. With a hash table
of 13 buckets and the rule “a buffer is always in bucket blockno % 13”, a lookup of
block 46 needs only bucket 7, under bucket 7’s lock: no other bucket can hold block
46. brelse, bpin and bunpin touch one buffer’s refcnt, so they need only
that buffer’s bucket. Two things remain global: the LRU order (the next question) and
recycling, which takes a buffer out of one bucket and puts it into another (the
question after that).
Why 13, a prime? Consecutive block numbers spread evenly over any number of buckets. A prime only protects against access patterns with a stride that shares a factor with the bucket count (with 12 buckets, blocks 0, 4, 8, … would use only 3 of them). The measurements show a different limit: hashing spreads blocks evenly, not accesses; see “Measure”.
Check yourself
With 13 buckets and bucket = blockno % 13, which bucket holds the free-block
bitmap of this file system, block 46 (sb.bmapstart, see Tour 30: The buffer cache)?
After the split, which operations need the lock of only one bucket?