Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
255 lines
6.4 KiB
C
255 lines
6.4 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/buf.h"
|
|
|
|
// 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.
|
|
|
|
// 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; // 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(int dev)
|
|
{
|
|
if (sizeof(struct logheader) >= BSIZE)
|
|
panic("initlog: too big logheader");
|
|
|
|
struct superblock sb;
|
|
initlock(&log.lock, "log");
|
|
readsb(dev, &sb);
|
|
log.start = sb.logstart;
|
|
log.size = sb.nlog;
|
|
log.dev = dev;
|
|
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.block[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.block[i] = lh->block[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->block[i] = log.lh.block[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
|
|
}
|
|
|
|
// called at the start of each FS system call.
|
|
void
|
|
begin_op(void)
|
|
{
|
|
// Nothing to do: the root filesystem is SchFS, which does not use this log.
|
|
//
|
|
// This is not a shortcut. The log exists to make a multi-block v1 update
|
|
// atomic; SchFS gets that from copy-on-write instead -- every schfs_k* call
|
|
// that changes anything is one transaction and is durable by the time it
|
|
// returns. So there is nothing here for begin_op to protect.
|
|
//
|
|
// It also cannot do anything: initlog() ran inside fsinit(), which no longer
|
|
// runs at all, so the lock below and everything it guards are uninitialised.
|
|
// Taking them is where the first SchFS-root boot stopped -- after the mount,
|
|
// before init ever ran, looking for all the world like a hang.
|
|
//
|
|
// The rest of this file is dead with it and goes when the v1 format does.
|
|
// begin_op/end_op stay as no-ops meanwhile because sys_fs.c calls them in
|
|
// dozens of places, and emptying those out is a separate change.
|
|
return;
|
|
|
|
#if 0
|
|
acquire(&log.lock);
|
|
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);
|
|
} else {
|
|
log.outstanding += 1;
|
|
release(&log.lock);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// called at the end of each FS system call.
|
|
// commits if this was the last outstanding operation.
|
|
void
|
|
end_op(void)
|
|
{
|
|
return; // see begin_op()
|
|
|
|
#if 0
|
|
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);
|
|
}
|
|
release(&log.lock);
|
|
|
|
if(do_commit){
|
|
// 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);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 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.block[tail]); // cache block
|
|
memmove(to->data, from->data, BSIZE);
|
|
bwrite(to); // write the log
|
|
brelse(from);
|
|
brelse(to);
|
|
}
|
|
}
|
|
|
|
static void
|
|
commit()
|
|
{
|
|
if (log.lh.n > 0) {
|
|
write_log(); // Write modified blocks from cache to log
|
|
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.
|
|
// 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");
|
|
if (log.outstanding < 1)
|
|
panic("log_write outside of trans");
|
|
|
|
acquire(&log.lock);
|
|
for (i = 0; i < log.lh.n; i++) {
|
|
if (log.lh.block[i] == b->blockno) // log absorbtion
|
|
break;
|
|
}
|
|
log.lh.block[i] = b->blockno;
|
|
if (i == log.lh.n)
|
|
log.lh.n++;
|
|
b->flags |= B_DIRTY; // prevent eviction
|
|
release(&log.lock);
|
|
}
|
|
|