SchFS is a 64-bit format addressing 64 ZB. The stack under it was not, and the lowest limit was three orders of magnitude below the one anybody would have guessed: ide.c panicked on any block >= FSSIZE -- 150000 blocks, 614 MB ide.c computed the sector in an int, and addressed it with LBA28 bio.c took a 32-bit block number, capping any device at 16 TB schfs refused blocks past 0xffffffff, mirroring the cache The FSSIZE check is the one that mattered. FSSIZE describes the size of a v1 filesystem *image*; it had no business bounding a physical device, and it meant a disk larger than half a gigabyte panicked the kernel rather than being read. struct buf's blockno is uint64 now, and so is bread's. idestart computes the sector in 64 bits and picks LBA48 when it does not fit in 28 -- per request, so everything that fits stays on the LBA28 path every existing image has been read through. The 48-bit registers are written high half first, because each is a two-deep FIFO and the other order leaves the drive addressing whatever the low bytes alone describe. Verified with a 2 GB disk carrying a marker at 1 GiB -- block 262144, well past the old panic at 150000. The guest reads it back: "MARKER-PAST-THE-OLD-LIMIT". schfs-check passes including the round-trip; bootcheck 108 [ok]. Two limits deliberately left, both noted rather than guessed at: raw /dev/hdN access still goes through devsw's 32-bit offset, so a raw device is addressable to 4 GB; and usb_storage computes a 32-bit LBA, which is what SCSI READ(10) carries -- READ(16) is a different command, not a wider variable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 lines
3.8 KiB
C
160 lines
3.8 KiB
C
// Buffer cache.
|
|
//
|
|
// The buffer cache is a linked list of buf structures holding
|
|
// cached copies of disk block contents. Caching disk blocks
|
|
// in memory reduces the number of disk reads and also provides
|
|
// a synchronization point for disk blocks used by multiple processes.
|
|
//
|
|
// Interface:
|
|
// * To get a buffer for a particular disk block, call bread.
|
|
// * After changing buffer data, call bwrite to write it to disk.
|
|
// * When done with the buffer, call brelse.
|
|
// * Do not use the buffer after calling brelse.
|
|
// * Only one process at a time can use a buffer,
|
|
// so do not keep them longer than necessary.
|
|
//
|
|
// The implementation uses two state flags internally:
|
|
// * B_VALID: the buffer data has been read from the disk.
|
|
// * B_DIRTY: the buffer data has been modified
|
|
// and needs to be written to disk.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "buf.h"
|
|
|
|
struct {
|
|
struct spinlock lock;
|
|
struct buf buf[NBUF];
|
|
|
|
// Linked list of all buffers, through prev/next.
|
|
// head.next is most recently used.
|
|
struct buf head;
|
|
} bcache;
|
|
|
|
void
|
|
binit(void)
|
|
{
|
|
struct buf *b;
|
|
|
|
initlock(&bcache.lock, "bcache");
|
|
//PAGEBREAK!
|
|
|
|
// Create linked list of buffers
|
|
bcache.head.prev = &bcache.head;
|
|
bcache.head.next = &bcache.head;
|
|
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
|
|
b->next = bcache.head.next;
|
|
b->prev = &bcache.head;
|
|
initsleeplock(&b->lock, "buffer");
|
|
bcache.head.next->prev = b;
|
|
bcache.head.next = b;
|
|
}
|
|
}
|
|
|
|
// Route a request to the driver that owns b->dev. Everything above this line
|
|
// is device-agnostic; everything below picks a backend. RAMDISK_DEV is served
|
|
// out of memory (kernel/dev/ramdisk.c), USBDISK_DEV by bulk transfers to a
|
|
// USB stick, everything else by the IDE driver.
|
|
static void
|
|
bdevrw(struct buf *b)
|
|
{
|
|
if(b->dev == RAMDISK_DEV){
|
|
ramdiskrw(b);
|
|
return;
|
|
}
|
|
if(b->dev == USBDISK_DEV){
|
|
usb_storage_rw(b);
|
|
return;
|
|
}
|
|
iderw(b);
|
|
}
|
|
|
|
// Look through buffer cache for block on device dev.
|
|
// If not found, allocate a buffer.
|
|
// In either case, return locked buffer.
|
|
static struct buf*
|
|
bget(uint dev, uint64 blockno)
|
|
{
|
|
struct buf *b;
|
|
|
|
acquire(&bcache.lock);
|
|
|
|
// Is the block already cached?
|
|
for(b = bcache.head.next; b != &bcache.head; b = b->next){
|
|
if(b->dev == dev && b->blockno == blockno){
|
|
b->refcnt++;
|
|
release(&bcache.lock);
|
|
acquiresleep(&b->lock);
|
|
return b;
|
|
}
|
|
}
|
|
|
|
// Not cached; recycle some unused buffer and clean buffer
|
|
// "clean" because B_DIRTY and not locked means log.c
|
|
// hasn't yet committed the changes to the buffer.
|
|
for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
|
|
if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
|
|
b->dev = dev;
|
|
b->blockno = blockno;
|
|
b->flags = 0;
|
|
b->refcnt = 1;
|
|
release(&bcache.lock);
|
|
acquiresleep(&b->lock);
|
|
return b;
|
|
}
|
|
}
|
|
panic("bget: no buffers");
|
|
}
|
|
|
|
// Return a locked buf with the contents of the indicated block.
|
|
struct buf*
|
|
bread(uint dev, uint64 blockno)
|
|
{
|
|
struct buf *b;
|
|
|
|
b = bget(dev, blockno);
|
|
if(!(b->flags & B_VALID)) {
|
|
bdevrw(b);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
// Write b's contents to disk. Must be locked.
|
|
void
|
|
bwrite(struct buf *b)
|
|
{
|
|
if(!holdingsleep(&b->lock))
|
|
panic("bwrite");
|
|
b->flags |= B_DIRTY;
|
|
bdevrw(b);
|
|
}
|
|
|
|
// Release a locked buffer.
|
|
// Move to the head of the MRU list.
|
|
void
|
|
brelse(struct buf *b)
|
|
{
|
|
if(!holdingsleep(&b->lock))
|
|
panic("brelse");
|
|
|
|
releasesleep(&b->lock);
|
|
|
|
acquire(&bcache.lock);
|
|
b->refcnt--;
|
|
if (b->refcnt == 0) {
|
|
// no one is waiting for it.
|
|
b->next->prev = b->prev;
|
|
b->prev->next = b->next;
|
|
b->next = bcache.head.next;
|
|
b->prev = &bcache.head;
|
|
bcache.head.next->prev = b;
|
|
bcache.head.next = b;
|
|
}
|
|
|
|
release(&bcache.lock);
|
|
}
|
|
//PAGEBREAK!
|