123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- #include "types.h"
- #include "defs.h"
- #include "param.h"
- #include "spinlock.h"
- #include "fs.h"
- #include "buf.h"
- // Simple logging. Each system call that might write the file system
- // should be surrounded with begin_trans() and commit_trans() calls.
- //
- // The log holds at most one transaction at a time. Commit forces
- // the log (with commit record) to disk, then installs the affected
- // blocks to disk, then erases the log. begin_trans() ensures that
- // only one system call can be in a transaction; others must wait.
- //
- // Allowing only one transaction at a time means that the file
- // system code doesn't have to worry about the possibility of
- // one transaction reading a block that another one has modified,
- // for example an i-node block.
- //
- // Read-only system calls don't need to use transactions, though
- // this means that they may observe uncommitted data. I-node and
- // buffer locks prevent read-only calls from seeing inconsistent data.
- //
- // The log is a physical re-do log containing disk blocks.
- // The on-disk log format:
- // header block, containing sector #s for block A, B, C, ...
- // block A
- // block B
- // block C
- // ...
- // Log appends are synchronous.
- // Contents of the header block, used for both the on-disk header block
- // and to keep track in memory of logged sector #s before commit.
- struct logheader {
- int n;
- int sector[LOGSIZE];
- };
- struct log {
- struct spinlock lock;
- int start;
- int size;
- int busy; // a transaction is active
- int dev;
- struct logheader lh;
- };
- struct log log;
- static void recover_from_log(void);
- void
- initlog(void)
- {
- if (sizeof(struct logheader) >= BSIZE)
- panic("initlog: too big logheader");
- struct superblock sb;
- memset(&log, 0, sizeof(log));
- 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;
- for (tail = 0; tail < log.lh.n; tail++) {
- struct buf *lbuf = bread(log.dev, log.start+tail+1); // read log block
- struct buf *dbuf = bread(log.dev, log.lh.sector[tail]); // read dst
- memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst
- bwrite(dbuf); // write dst to disk
- 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.n = lh->n;
- for (i = 0; i < log.lh.n; i++) {
- log.lh.sector[i] = lh->sector[i];
- }
- brelse(buf);
- }
- // Write in-memory log header to disk.
- // This is the true point at which the
- // current transaction commits.
- static void
- write_head(void)
- {
- struct buf *buf = bread(log.dev, log.start);
- struct logheader *hb = (struct logheader *) (buf->data);
- int i;
- hb->n = log.lh.n;
- for (i = 0; i < log.lh.n; i++) {
- hb->sector[i] = log.lh.sector[i];
- }
- bwrite(buf);
- brelse(buf);
- }
- static void
- recover_from_log(void)
- {
- read_head();
- install_trans(); // if committed, copy from log to disk
- log.lh.n = 0;
- write_head(); // clear the log
- }
- void
- begin_trans(void)
- {
- acquire(&log.lock);
- while (log.busy) {
- sleep(&log, &log.lock);
- }
- log.busy = 1;
- release(&log.lock);
- }
- void
- commit_trans(void)
- {
- if (log.lh.n > 0) {
- write_head(); // Write header to disk -- the real commit
- install_trans(); // Now install writes to home locations
- log.lh.n = 0;
- write_head(); // Erase the transaction from the log
- }
-
- acquire(&log.lock);
- log.busy = 0;
- wakeup(&log);
- release(&log.lock);
- }
- // Caller has modified b->data and is done with the buffer.
- // Append the block to the log and record the block number,
- // but don't write the log header (which would commit the write).
- // log_write() replaces bwrite(); a typical use is:
- // bp = bread(...)
- // modify bp->data[]
- // log_write(bp)
- // brelse(bp)
- void
- log_write(struct buf *b)
- {
- int i;
- if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
- panic("too big a transaction");
- if (!log.busy)
- panic("write outside of trans");
- for (i = 0; i < log.lh.n; 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.n)
- log.lh.n++;
- b->flags |= B_DIRTY; // XXX prevent eviction
- }
- //PAGEBREAK!
- // Blank page.
|