215 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#include "types.h"
 | 
						|
#include "defs.h"
 | 
						|
#include "param.h"
 | 
						|
#include "spinlock.h"
 | 
						|
#include "fs.h"
 | 
						|
#include "buf.h"
 | 
						|
 | 
						|
// Simple logging that allows concurrent FS system calls.
 | 
						|
//
 | 
						|
// A log transaction contains the updates of *multiple* FS system
 | 
						|
// calls. The logging systems only commits when there are
 | 
						|
// no FS system calls active. Thus there is never
 | 
						|
// any reasoning required about whether a commit might
 | 
						|
// write an uncommitted system call's updates to disk.
 | 
						|
//
 | 
						|
// A system call should call begin_op()/end_op() to mark
 | 
						|
// its start and end. Usually begin_op() just increments
 | 
						|
// the count of in-progress FS system calls and returns.
 | 
						|
// But if it thinks the log is close to running out, it
 | 
						|
// blocks this system call, and causes the system to wait
 | 
						|
// until end_op() indicates there are no executing FS
 | 
						|
// system calls, at which point the last end_op() commits
 | 
						|
// all the system calls' writes.
 | 
						|
//
 | 
						|
// 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 outstanding; // how many FS sys calls are executing.
 | 
						|
  int committing;  // in commit(), please wait.
 | 
						|
  int dev;
 | 
						|
  struct logheader lh;
 | 
						|
};
 | 
						|
struct log log;
 | 
						|
 | 
						|
static void recover_from_log(void);
 | 
						|
static void commit();
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
  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
 | 
						|
}
 | 
						|
 | 
						|
// an FS system call should call begin_op() when it starts.
 | 
						|
void
 | 
						|
begin_op(void)
 | 
						|
{
 | 
						|
  acquire(&log.lock);
 | 
						|
  while(1){
 | 
						|
    if(log.committing){
 | 
						|
      sleep(&log, &log.lock);
 | 
						|
    } else {
 | 
						|
      // XXX wait (for a commit) if log is longish.
 | 
						|
      //     need to reserve to avoid over-commit of log space.
 | 
						|
      log.outstanding += 1;
 | 
						|
      release(&log.lock);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// an FS system call should call end_op() after it finishes.
 | 
						|
// can't write the disk &c while holding locks, thus do_commit.
 | 
						|
void
 | 
						|
end_op(void)
 | 
						|
{
 | 
						|
  int do_commit = 0;
 | 
						|
 | 
						|
  acquire(&log.lock);
 | 
						|
  log.outstanding -= 1;
 | 
						|
  if(log.committing)
 | 
						|
    panic("log.committing");
 | 
						|
  if(log.outstanding == 0){
 | 
						|
    do_commit = 1;
 | 
						|
    log.committing = 1;
 | 
						|
  }
 | 
						|
  release(&log.lock);
 | 
						|
 | 
						|
  if(do_commit){
 | 
						|
    commit();
 | 
						|
    acquire(&log.lock);
 | 
						|
    log.committing = 0;
 | 
						|
    wakeup(&log);
 | 
						|
    release(&log.lock);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
commit()
 | 
						|
{
 | 
						|
  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
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// 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.outstanding < 1)
 | 
						|
    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.
 | 
						|
 |