Appearance
log.c
#include "types.h"
#include "riscv.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "buf.h"
TIP
Simple logging that allows concurrent FS system calls. A log transaction contains the updates of multiple FS system calls. The logging system 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 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 block #s for block A, B, C, ... block A block B block C ... Log appends are synchronous.
TIP
Contents of the header block, used for both the on-disk header block and to keep track in memory of logged block# before commit.
struct logheader {
int n;
int block[LOGSIZE];
};
struct log {
struct spinlock lock;
int start;
int size;
int outstanding;
int committing;
int dev;
struct logheader lh;
};
struct log log;
static void recover_from_log(void);
static void commit();
void
initlog(int dev, struct superblock *sb)
{
if (sizeof(struct logheader) >= BSIZE)
panic("initlog: too big logheader");
initlock(&log.lock, "log");
log.start = sb->logstart;
log.size = sb->nlog;
log.dev = dev;
recover_from_log();
}
TIP
Copy committed blocks from log to their home location
static void
install_trans(int recovering)
{
int tail;
for (tail = 0; tail < log.lh.n; tail++) {
struct buf *lbuf = bread(log.dev, log.start+tail+1);
struct buf *dbuf = bread(log.dev, log.lh.block[tail]);
memmove(dbuf->data, lbuf->data, BSIZE);
bwrite(dbuf);
if(recovering == 0)
bunpin(dbuf);
brelse(lbuf);
brelse(dbuf);
}
}
TIP
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.block[i] = lh->block[i];
}
brelse(buf);
}
TIP
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->block[i] = log.lh.block[i];
}
bwrite(buf);
brelse(buf);
}
static void
recover_from_log(void)
{
read_head();
install_trans(1);
log.lh.n = 0;
write_head();
}
TIP
called at the start of each FS system call.
void
begin_op(void)
{
acquire(&log.lock);
while(1){
if(log.committing){
sleep(&log, &log.lock);
} else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){
TIP
this op might exhaust log space; wait for commit.
sleep(&log, &log.lock);
} else {
log.outstanding += 1;
release(&log.lock);
break;
}
}
}
TIP
called at the end of each FS system call. commits if this was the last outstanding operation.
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;
} else {
TIP
begin_op() may be waiting for log space, and decrementing log.outstanding has decreased the amount of reserved space.
wakeup(&log);
}
release(&log.lock);
if(do_commit){
TIP
call commit w/o holding locks, since not allowed to sleep with locks.
commit();
acquire(&log.lock);
log.committing = 0;
wakeup(&log);
release(&log.lock);
}
}
TIP
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);
struct buf *from = bread(log.dev, log.lh.block[tail]);
memmove(to->data, from->data, BSIZE);
bwrite(to);
brelse(from);
brelse(to);
}
}
static void
commit()
{
if (log.lh.n > 0) {
write_log();
write_head();
install_trans(0);
log.lh.n = 0;
write_head();
}
}
TIP
Caller has modified b->data and is done with the buffer. Record the block number and pin in the cache by increasing refcnt. 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;
acquire(&log.lock);
if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
panic("too big a transaction");
if (log.outstanding < 1)
panic("log_write outside of trans");
for (i = 0; i < log.lh.n; i++) {
if (log.lh.block[i] == b->blockno)
break;
}
log.lh.block[i] = b->blockno;
if (i == log.lh.n) {
bpin(b);
log.lh.n++;
}
release(&log.lock);
}