root/kernel/log.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. initlog
  2. install_trans
  3. read_head
  4. write_head
  5. recover_from_log
  6. begin_op
  7. end_op
  8. write_log
  9. commit
  10. log_write
  11. sys_sync

   1 #include "types.h"
   2 #include "riscv.h"
   3 #include "defs.h"
   4 #include "param.h"
   5 #include "spinlock.h"
   6 #include "sleeplock.h"
   7 #include "fs.h"
   8 #include "buf.h"
   9 
  10 // Simple logging that allows concurrent FS system calls.
  11 //
  12 // A log transaction contains the updates of multiple FS system
  13 // calls. The logging system only commits when there are
  14 // no FS system calls active. Thus there is never
  15 // any reasoning required about whether a commit might
  16 // write an uncommitted system call's updates to disk.
  17 //
  18 // A system call should call begin_op()/end_op() to mark
  19 // its start and end. Usually begin_op() just increments
  20 // the count of in-progress FS system calls and returns.
  21 // But if it thinks the log is close to running out, it
  22 // sleeps until the last outstanding end_op() commits.
  23 //
  24 // The log is a physical re-do log containing disk blocks.
  25 // The on-disk log format:
  26 //   header block, containing block #s for block A, B, C, ...
  27 //   block A
  28 //   block B
  29 //   block C
  30 //   ...
  31 // Log appends are synchronous.
  32 
  33 // Contents of the header block, used for both the on-disk header block
  34 // and to keep track in memory of logged block# before commit.
  35 struct logheader {
  36   int n;
  37   int block[LOGBLOCKS];
  38 };
  39 
  40 struct log {
  41   struct spinlock lock;
  42   int start;
  43   int outstanding; // how many FS sys calls are executing.
  44   int committing;  // in commit(), please wait.
  45   int dev;
  46   int ncommit;
  47   struct logheader lh;
  48 };
  49 struct log log;
  50 
  51 static void recover_from_log(void);
  52 static void commit();
  53 
  54 void
  55 initlog(int dev, struct superblock *sb)
  56 {
  57   if (sizeof(struct logheader) >= BSIZE)
  58     panic("initlog: too big logheader");
  59 
  60   initlock(&log.lock, "log");
  61   log.start = sb->logstart;
  62   log.dev = dev;
  63   recover_from_log();
  64 }
  65 
  66 // Copy committed blocks from log to their home location
  67 static void
  68 install_trans(int recovering)
  69 {
  70   int tail;
  71 
  72   for (tail = 0; tail < log.lh.n; tail++) {
  73     if (recovering) {
  74       printk("recovering tail %d dst %d\n", tail, log.lh.block[tail]);
  75     }
  76     struct buf *lbuf = bread(log.dev, log.start + tail + 1); // read log block
  77     struct buf *dbuf = bread(log.dev, log.lh.block[tail]);   // read dst
  78     memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst
  79     bwrite(dbuf);                           // write dst to disk
  80     if (recovering == 0)
  81       bunpin(dbuf);
  82     brelse(lbuf);
  83     brelse(dbuf);
  84   }
  85 }
  86 
  87 // Read the log header from disk into the in-memory log header
  88 static void
  89 read_head(void)
  90 {
  91   struct buf *buf = bread(log.dev, log.start);
  92   struct logheader *lh = (struct logheader *)(buf->data);
  93   int i;
  94   log.lh.n = lh->n;
  95   for (i = 0; i < log.lh.n; i++) {
  96     log.lh.block[i] = lh->block[i];
  97   }
  98   brelse(buf);
  99 }
 100 
 101 // Write in-memory log header to disk.
 102 // This is the true point at which the
 103 // current transaction commits.
 104 static void
 105 write_head(void)
 106 {
 107   struct buf *buf = bread(log.dev, log.start);
 108   struct logheader *hb = (struct logheader *)(buf->data);
 109   int i;
 110   hb->n = log.lh.n;
 111   for (i = 0; i < log.lh.n; i++) {
 112     hb->block[i] = log.lh.block[i];
 113   }
 114   bwrite(buf);
 115   brelse(buf);
 116 }
 117 
 118 static void
 119 recover_from_log(void)
 120 {
 121   read_head();
 122   install_trans(1); // if committed, copy from log to disk
 123   log.lh.n = 0;
 124   write_head(); // clear the log
 125 }
 126 
 127 // called at the start of each FS system call.
 128 void
 129 begin_op(void)
 130 {
 131   acquire(&log.lock);
 132   while (1) {
 133     if (log.committing) {
 134       sleep_prepare(&log);
 135       release(&log.lock);
 136       sleep();
 137       acquire(&log.lock);
 138     } else if (log.lh.n + (log.outstanding + 1) * MAXOPBLOCKS > LOGBLOCKS) {
 139       // this op might exhaust log space; wait for commit.
 140       sleep_prepare(&log);
 141       release(&log.lock);
 142       sleep();
 143       acquire(&log.lock);
 144     } else {
 145       log.outstanding += 1;
 146       release(&log.lock);
 147       break;
 148     }
 149   }
 150 }
 151 
 152 // called at the end of each FS system call.
 153 // commits if this was the last outstanding operation.
 154 void
 155 end_op(void)
 156 {
 157   int do_commit = 0;
 158 
 159   acquire(&log.lock);
 160   log.outstanding -= 1;
 161   if (log.committing)
 162     panic("log.committing");
 163   if (log.outstanding == 0) {
 164     do_commit = 1;
 165     log.committing = 1;
 166   } else {
 167     // begin_op() may be waiting for log space,
 168     // and decrementing log.outstanding has decreased
 169     // the amount of reserved space.
 170     wakeup(&log);
 171   }
 172   release(&log.lock);
 173 
 174   if (do_commit) {
 175     // call commit w/o holding locks, since not allowed
 176     // to sleep with locks.
 177     commit();
 178     acquire(&log.lock);
 179     log.committing = 0;
 180     log.ncommit += 1;
 181     wakeup(&log);
 182     release(&log.lock);
 183   }
 184 }
 185 
 186 // Copy modified blocks from cache to log.
 187 static void
 188 write_log(void)
 189 {
 190   int tail;
 191 
 192   for (tail = 0; tail < log.lh.n; tail++) {
 193     struct buf *to = bread(log.dev, log.start + tail + 1); // log block
 194     struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block
 195     memmove(to->data, from->data, BSIZE);
 196     bwrite(to); // write the log
 197     brelse(from);
 198     brelse(to);
 199   }
 200 }
 201 
 202 static void
 203 commit()
 204 {
 205   if (log.lh.n > 0) {
 206     write_log();      // Write modified blocks from cache to log
 207     write_head();     // Write header to disk -- the real commit
 208     install_trans(0); // Now install writes to home locations
 209     log.lh.n = 0;
 210     write_head(); // Erase the transaction from the log
 211   }
 212 }
 213 
 214 // Caller has modified b->data and is done with the buffer.
 215 // Record the block number and pin in the cache by increasing refcnt.
 216 // commit()/write_log() will do the disk write.
 217 //
 218 // log_write() replaces bwrite(); a typical use is:
 219 //   bp = bread(...)
 220 //   modify bp->data[]
 221 //   log_write(bp)
 222 //   brelse(bp)
 223 void
 224 log_write(struct buf *b)
 225 {
 226   int i;
 227 
 228   acquire(&log.lock);
 229   if (log.lh.n >= LOGBLOCKS)
 230     panic("too big a transaction");
 231   if (log.outstanding < 1)
 232     panic("log_write outside of trans");
 233 
 234   for (i = 0; i < log.lh.n; i++) {
 235     if (log.lh.block[i] == b->blockno) // log absorption
 236       break;
 237   }
 238   log.lh.block[i] = b->blockno;
 239   if (i == log.lh.n) { // Add new block to log?
 240     bpin(b);
 241     log.lh.n++;
 242   }
 243   release(&log.lock);
 244 }
 245 
 246 uint64
 247 sys_sync(void)
 248 {
 249   acquire(&log.lock);
 250   if (log.committing || log.outstanding > 0) {
 251     int n = log.ncommit + 1;
 252     while (log.ncommit < n) {
 253       sleep_prepare(&log);
 254       release(&log.lock);
 255       sleep();
 256       acquire(&log.lock);
 257     }
 258   }
 259   release(&log.lock);
 260   return 0;
 261 }

/* [<][>][^][v][top][bottom][index][help] */