bio: a buffer cache that grows to 1/32 of RAM, looked up by hash
The cache was NBUF buffers, 1 MB, on every machine: enough for one with tens of megabytes and nothing on one with gigabytes, which re-read the same directory and binary blocks off the disk for every command. It still starts as those 256 in the kernel image, and a miss with every buffer busy now makes a new one rather than evicting, up to 1/32 of RAM or 256 MB. Growing on demand means nothing is spent that is not used, and binit() needs nothing from the page allocator. Lookups go through a hash on (dev, blockno) instead of walking the LRU list, and bflush() walks a creation-order list with an unlocked filter first, so neither costs more at 65536 buffers than it did at 256. struct buf's data is a pointer to its own page now; the drivers copy through their own DMA buffers, so where it sits does not matter. On a 2 GB QEMU guest: a 40 MB file read back in 0.89 s instead of 1.83, the big binaries in 0.17 s instead of 0.26. /proc/fsstat shows the cache as buf_cached now / most. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
51983144e3
commit
62d2a85ac8
4 changed files with 179 additions and 26 deletions
|
|
@ -102,9 +102,21 @@ every `*at()` call uses them.
|
|||
|
||||
## The buffer cache
|
||||
|
||||
`kernel/dev/bio.c`, xv6's design unchanged: a fixed pool of buffers, an LRU
|
||||
list, one sleeplock per buffer. `bread()` returns a locked buffer; `bwrite()`
|
||||
flushes it; `brelse()` releases.
|
||||
`kernel/dev/bio.c`, xv6's interface: `bread()` returns a locked buffer,
|
||||
`bwrite()` writes it, `bdirty()` marks it for later, `brelse()` releases. One
|
||||
sleeplock per buffer and an LRU list for eviction.
|
||||
|
||||
**It grows with the machine.** It starts as the 256 buffers in the kernel
|
||||
image (1 MB), and a miss with every buffer busy makes a new one instead of
|
||||
evicting, until the cache is 1/32 of RAM, at most 256 MB: 32 MB on 1 GB, 256 MB
|
||||
from 8 GB up. It never shrinks, so the fraction is kept small. Lookups go
|
||||
through a hash on `(dev, blockno)`, not the LRU walk, so a 64k-buffer cache
|
||||
costs no more per `bread()` than a 256-buffer one. `/proc/fsstat` shows
|
||||
`buf_cached` as now / most.
|
||||
|
||||
On a 2 GB machine under QEMU, reading a 40 MB file back went from 1.83 s to
|
||||
0.89 s, and `ls -R /` and the big binaries are each served from the cache on the
|
||||
second run instead of from the disk.
|
||||
|
||||
**Sleeping is the point.** A buffer lock is held across a disk read, which is
|
||||
why every path that touches a filesystem must be able to sleep — and why the
|
||||
|
|
|
|||
Loading…
Reference in a new issue