summaryrefslogtreecommitdiff
path: root/kernel/bio.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/bio.c')
-rw-r--r--kernel/bio.c145
1 files changed, 145 insertions, 0 deletions
diff --git a/kernel/bio.c b/kernel/bio.c
new file mode 100644
index 0000000..90f9af9
--- /dev/null
+++ b/kernel/bio.c
@@ -0,0 +1,145 @@
+// 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 "param.h"
+#include "spinlock.h"
+#include "sleeplock.h"
+#include "riscv.h"
+#include "defs.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;
+ }
+}
+
+// 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, uint 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 an unused buffer.
+ // Even if refcnt==0, B_DIRTY indicates a buffer is in use
+ // because log.c has modified it but not yet committed it.
+ 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, uint blockno)
+{
+ struct buf *b;
+
+ b = bget(dev, blockno);
+ if((b->flags & B_VALID) == 0) {
+ ramdiskrw(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;
+ ramdiskrw(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!
+// Blank page.
+