xv6-65oo2/log.c

230 lines
5.4 KiB
C
Raw Normal View History

#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "fs.h"
#include "buf.h"
2014-08-27 21:15:30 +00:00
// Simple logging that allows concurrent FS system calls.
//
2014-08-28 10:27:01 +00:00
// A log transaction contains the updates of multiple FS system
// calls. The logging system only commits when there are
2014-08-27 21:15:30 +00:00
// 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
2014-08-28 10:27:01 +00:00
// sleeps until the last outstanding end_op() commits.
//
// 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;
2014-08-27 21:15:30 +00:00
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);
2014-08-27 21:15:30 +00:00
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++) {
2011-09-01 14:25:20 +00:00
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
2011-10-11 10:41:37 +00:00
bwrite(dbuf); // write dst to disk
2011-09-01 14:25:20 +00:00
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);
}
2011-10-11 10:41:37 +00:00
// 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
}
2014-08-28 10:27:01 +00:00
// called at the start of each FS system call.
void
2014-08-27 21:15:30 +00:00
begin_op(void)
{
acquire(&log.lock);
2014-08-27 21:15:30 +00:00
while(1){
if(log.committing){
sleep(&log, &log.lock);
} else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){
// this op might exhaust log space; wait for commit.
sleep(&log, &log.lock);
2014-08-27 21:15:30 +00:00
} else {
log.outstanding += 1;
release(&log.lock);
break;
}
}
}
2014-08-28 10:27:01 +00:00
// called at the end of each FS system call.
// commits if this was the last outstanding operation.
void
2014-08-27 21:15:30 +00:00
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;
} else {
// begin_op() may be waiting for log space.
wakeup(&log);
2014-08-27 21:15:30 +00:00
}
release(&log.lock);
if(do_commit){
2014-08-28 10:27:01 +00:00
// call commit w/o holding locks, since not allowed
// to sleep with locks.
2014-08-27 21:15:30 +00:00
commit();
acquire(&log.lock);
log.committing = 0;
wakeup(&log);
release(&log.lock);
}
}
// Copy modified blocks from cache to log.
static void
write_log(void)
{
int tail;
for (tail = 0; tail < log.lh.n; tail++) {
struct buf *to = bread(log.dev, log.start+tail+1); // log block
struct buf *from = bread(log.dev, log.lh.sector[tail]); // cache block
memmove(to->data, from->data, BSIZE);
bwrite(to); // write the log
brelse(from);
brelse(to);
}
}
2014-08-27 21:15:30 +00:00
static void
commit()
{
if (log.lh.n > 0) {
write_log(); // Write modified blocks from cache to log
2011-10-11 10:41:37 +00:00
write_head(); // Write header to disk -- the real commit
install_trans(); // Now install writes to home locations
log.lh.n = 0;
2011-10-11 10:41:37 +00:00
write_head(); // Erase the transaction from the log
}
}
// Caller has modified b->data and is done with the buffer.
// Record the block number and pin in the cache with B_DIRTY.
// commit()/write_log() will do the disk 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");
2014-08-27 21:15:30 +00:00
if (log.outstanding < 1)
panic("log_write outside of trans");
for (i = 0; i < log.lh.n; i++) {
2014-08-28 10:27:01 +00:00
if (log.lh.sector[i] == b->sector) // log absorbtion
break;
}
log.lh.sector[i] = b->sector;
if (i == log.lh.n)
log.lh.n++;
2014-08-28 10:27:01 +00:00
b->flags |= B_DIRTY; // prevent eviction
}