#include "types.h" #include "defs.h" #include "param.h" #include "mmu.h" #include "proc.h" #include "x86.h" #include "spinlock.h" #include "fs.h" #include "buf.h" // Dirt simple "logging" supporting only one transaction. All file system calls // that potentially write a block should be wrapped in begin_trans and commit_trans, // so that there is never more than one transaction. This serializes all file system // operations that potentially write, but simplifies recovery (only the last // one transaction to recover) and concurrency (don't have to worry about reading a modified // block from a transaction that hasn't committed yet). // The header of the log. If head == 0, there are no log entries. All entries till head // are committed. sector[] records the home sector for each block in the log // (i.e., physical logging). struct logheader { int head; int sector[LOGSIZE]; }; struct { struct spinlock lock; int start; int size; int intrans; int dev; struct logheader lh; } log; static void recover_from_log(void); void initlog(void) { if (sizeof(struct logheader) >= BSIZE) panic("initlog: too big logheader"); struct superblock sb; initlock(&log.lock, "log"); readsb(ROOTDEV, &sb); log.start = sb.size - sb.nlog; log.size = sb.nlog; log.dev = ROOTDEV; recover_from_log(); } // Copy committed blocks from log to their home location static void install_trans(void) { int tail; if (log.lh.head > 0) cprintf("install_trans %d\n", log.lh.head); for (tail = 0; tail < log.lh.head; tail++) { cprintf("put entry %d to disk block %d\n", tail, log.lh.sector[tail]); struct buf *lbuf = bread(log.dev, log.start+tail+1); // read i'th block from log struct buf *dbuf = bread(log.dev, log.lh.sector[tail]); // read dst block memmove(dbuf->data, lbuf->data, BSIZE); bwrite(dbuf); brelse(lbuf); brelse(dbuf); } } // Read the log header from disk into the in-memory log header static void read_head(void) { struct buf *buf = bread(log.dev, log.start); struct logheader *lh = (struct logheader *) (buf->data); int i; log.lh.head = lh->head; for (i = 0; i < log.lh.head; i++) { log.lh.sector[i] = lh->sector[i]; } brelse(buf); if (log.lh.head > 0) cprintf("read_head: %d\n", log.lh.head); } // Write the in-memory log header to disk, committing log entries till head static void write_head(void) { if (log.lh.head > 0) cprintf("write_head: %d\n", log.lh.head); struct buf *buf = bread(log.dev, log.start); struct logheader *hb = (struct logheader *) (buf->data); int i; hb->head = log.lh.head; for (i = 0; i < log.lh.head; i++) { hb->sector[i] = log.lh.sector[i]; } bwrite(buf); brelse(buf); } static void recover_from_log(void) { read_head(); install_trans(); // Install all transactions till head log.lh.head = 0; write_head(); // Reclaim log } void begin_trans(void) { acquire(&log.lock); while (log.intrans) { sleep(&log, &log.lock); } log.intrans = 1; release(&log.lock); } void commit_trans(void) { write_head(); // This causes all blocks till log.head to be commited install_trans(); // Install all the transactions till head log.lh.head = 0; write_head(); // Reclaim log acquire(&log.lock); log.intrans = 0; wakeup(&log); release(&log.lock); } // Write buffer into the log at log.head and record the block number log.lh.entry, but // don't write the log header (which would commit the write). void log_write(struct buf *b) { int i; if (log.lh.head >= LOGSIZE) panic("too big a transaction"); if (!log.intrans) panic("write outside of trans"); cprintf("log_write: %d %d\n", b->sector, log.lh.head); for (i = 0; i < log.lh.head; i++) { if (log.lh.sector[i] == b->sector) // log absorbtion? break; } log.lh.sector[i] = b->sector; struct buf *lbuf = bread(b->dev, log.start+i+1); memmove(lbuf->data, b->data, BSIZE); bwrite(lbuf); brelse(lbuf); if (i == log.lh.head) log.lh.head++; }