kernel/log.c
237 lines
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"910// Simple logging that allows concurrent FS system calls.11//12// A log transaction contains the updates of multiple FS system13// calls. The logging system only commits when there are14// no FS system calls active. Thus there is never15// any reasoning required about whether a commit might16// write an uncommitted system call's updates to disk.17//18// A system call should call begin_op()/end_op() to mark19// its start and end. Usually begin_op() just increments20// the count of in-progress FS system calls and returns.21// But if it thinks the log is close to running out, it22// 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 A28// block B29// block C30// ...31// Log appends are synchronous.3233// Contents of the header block, used for both the on-disk header block34// and to keep track in memory of logged block# before commit.35struct logheader {36 int n;37 int block[LOGBLOCKS];38};3940struct 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 struct logheader lh;47};48struct log log;4950static void recover_from_log(void);51static void commit();5253void54initlog(int dev, struct superblock *sb)55{56 if (sizeof(struct logheader) >= BSIZE)57 panic("initlog: too big logheader");5859 initlock(&log.lock, "log");60 log.start = sb->logstart;61 log.dev = dev;62 recover_from_log();63}6465// Copy committed blocks from log to their home location66static void67install_trans(int recovering)68{69 int tail;7071 for (tail = 0; tail < log.lh.n; tail++) {72 if(recovering) {73 printf("recovering tail %d dst %d\n", tail, log.lh.block[tail]);74 }75 struct buf *lbuf = bread(log.dev, log.start+tail+1); // read log block76 struct buf *dbuf = bread(log.dev, log.lh.block[tail]); // read dst77 memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst78 bwrite(dbuf); // write dst to disk79 if(recovering == 0)80 bunpin(dbuf);81 brelse(lbuf);82 brelse(dbuf);83 }84}8586// Read the log header from disk into the in-memory log header87static void88read_head(void)89{90 struct buf *buf = bread(log.dev, log.start);91 struct logheader *lh = (struct logheader *) (buf->data);92 int i;93 log.lh.n = lh->n;94 for (i = 0; i < log.lh.n; i++) {95 log.lh.block[i] = lh->block[i];96 }97 brelse(buf);98}99100// Write in-memory log header to disk.101// This is the true point at which the102// current transaction commits.103static void104write_head(void)105{106 struct buf *buf = bread(log.dev, log.start);107 struct logheader *hb = (struct logheader *) (buf->data);108 int i;109 hb->n = log.lh.n;110 for (i = 0; i < log.lh.n; i++) {111 hb->block[i] = log.lh.block[i];112 }113 bwrite(buf);114 brelse(buf);115}116117static void118recover_from_log(void)119{120 read_head();121 install_trans(1); // if committed, copy from log to disk122 log.lh.n = 0;123 write_head(); // clear the log124}125126// called at the start of each FS system call.127void128begin_op(void)129{130 acquire(&log.lock);131 while(1){132 if(log.committing){133 sleep(&log, &log.lock);134 } else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGBLOCKS){135 // this op might exhaust log space; wait for commit.136 sleep(&log, &log.lock);137 } else {138 log.outstanding += 1;139 release(&log.lock);140 break;141 }142 }143}144145// called at the end of each FS system call.146// commits if this was the last outstanding operation.147void148end_op(void)149{150 int do_commit = 0;151152 acquire(&log.lock);153 log.outstanding -= 1;154 if(log.committing)155 panic("log.committing");156 if(log.outstanding == 0){157 do_commit = 1;158 log.committing = 1;159 } else {160 // begin_op() may be waiting for log space,161 // and decrementing log.outstanding has decreased162 // the amount of reserved space.163 wakeup(&log);164 }165 release(&log.lock);166167 if(do_commit){168 // call commit w/o holding locks, since not allowed169 // to sleep with locks.170 commit();171 acquire(&log.lock);172 log.committing = 0;173 wakeup(&log);174 release(&log.lock);175 }176}177178// Copy modified blocks from cache to log.179static void180write_log(void)181{182 int tail;183184 for (tail = 0; tail < log.lh.n; tail++) {185 struct buf *to = bread(log.dev, log.start+tail+1); // log block186 struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block187 memmove(to->data, from->data, BSIZE);188 bwrite(to); // write the log189 brelse(from);190 brelse(to);191 }192}193194static void195commit()196{197 if (log.lh.n > 0) {198 write_log(); // Write modified blocks from cache to log199 write_head(); // Write header to disk -- the real commit200 install_trans(0); // Now install writes to home locations201 log.lh.n = 0;202 write_head(); // Erase the transaction from the log203 }204}205206// Caller has modified b->data and is done with the buffer.207// Record the block number and pin in the cache by increasing refcnt.208// commit()/write_log() will do the disk write.209//210// log_write() replaces bwrite(); a typical use is:211// bp = bread(...)212// modify bp->data[]213// log_write(bp)214// brelse(bp)215void216log_write(struct buf *b)217{218 int i;219220 acquire(&log.lock);221 if (log.lh.n >= LOGBLOCKS)222 panic("too big a transaction");223 if (log.outstanding < 1)224 panic("log_write outside of trans");225226 for (i = 0; i < log.lh.n; i++) {227 if (log.lh.block[i] == b->blockno) // log absorption228 break;229 }230 log.lh.block[i] = b->blockno;231 if (i == log.lh.n) { // Add new block to log?232 bpin(b);233 log.lh.n++;234 }235 release(&log.lock);236}237238