xv6-riscv-kernel/log.c

233 lines
5.6 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 23:15:30 +02:00
// Simple logging that allows concurrent FS system calls.
//
2014-08-27 23:15:30 +02:00
// 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;
2014-08-27 23:15:30 +02: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 23:15:30 +02:00
static void commit();
// statistics, delete eventually XXX.
static int maxsize;
static int maxoutstanding;
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 16:25:20 +02: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 12:41:37 +02:00
bwrite(dbuf); // write dst to disk
2011-09-01 16:25:20 +02: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 12:41:37 +02: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-27 23:15:30 +02:00
// an FS system call should call begin_op() when it starts.
void
2014-08-27 23:15:30 +02:00
begin_op(void)
{
acquire(&log.lock);
2014-08-27 23:15:30 +02: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 23:15:30 +02:00
} else {
log.outstanding += 1;
if(log.outstanding > maxoutstanding){
maxoutstanding = log.outstanding;
cprintf("%d outstanding\n", log.outstanding);
}
2014-08-27 23:15:30 +02:00
release(&log.lock);
break;
}
}
}
2014-08-27 23:15:30 +02:00
// an FS system call should call end_op() after it finishes.
// can't write the disk &c while holding locks, thus do_commit.
void
2014-08-27 23:15:30 +02: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 23:15:30 +02:00
}
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) {
2011-10-11 12:41:37 +02: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 12:41:37 +02:00
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");
2014-08-27 23:15:30 +02:00
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
if(log.lh.n > maxsize){
maxsize = log.lh.n;
cprintf("log size %d/%d\n", log.lh.n, LOGSIZE);
}
}
//PAGEBREAK!
// Blank page.