xv6, line by line
kernel/log.c

kernel/log.c

C · 261 lines · annotated 100% · kernel · upstream

About this file

The write-ahead log, which makes file system updates crash-safe.

Most file system operations change several blocks. Creating a file, for example, marks an inode as used, writes the new inode, and adds an entry to a directory block. If the machine lost power after some of those writes and not others, the disk would be inconsistent: an inode marked used that no directory refers to, or a directory entry pointing at a free inode. This file prevents that: each group of writes reaches the disk completely or not at all.

The protocol:

  1. A file system operation that may modify the disk brackets its work with begin_op and end_op. In between, instead of writing modified blocks to disk, it calls log_write, which only records the block number and pins the buffer in the cache.
  2. When the last concurrent operation calls end_op, commit runs: it copies every modified block into the log area of the disk (write_log), then writes the log header with the count of blocks (write_head). Writing the header is the commit point. Then it copies the blocks to their real locations (install_trans) and clears the header.
  3. At boot, recover_from_log checks the header. If it records a committed transaction, recovery copies the logged blocks to their real locations again.

The system call sync (sys_sync) is also here, because it only needs to wait for the log.

Read before: kernel/bio.c. Read next: kernel/fs.c, whose functions call log_write, and kernel/sysfile.c, whose system calls call begin_op and end_op.

1#include "types.h"
2#include "riscv.h"
3#include "defs.h"
4#include "param.h"
5#include "spinlock.h"
7#include "fs.h"
8#include "buf.h"
10// Simple logging that allows concurrent FS system calls.
11//
12// A log transaction contains the updates of multiple FS system
13// calls. The logging system only commits when there are
14// no FS system calls active. Thus there is never
15// any reasoning required about whether a commit might
16// write an uncommitted system call's updates to disk.
17//
18// A system call should call begin_op()/end_op() to mark
19// its start and end. Usually begin_op() just increments
20// the count of in-progress FS system calls and returns.
21// But if it thinks the log is close to running out, it
22// sleeps until the last outstanding end_op() commits.
23//
24// The log is a physical re-do log containing disk blocks.
25// The on-disk log format:
26// header block, containing block #s for block A, B, C, ...
27// block A
28// block B
29// block C
30// ...
31// Log appends are synchronous.
33// Contents of the header block, used for both the on-disk header block
34// and to keep track in memory of logged block# before commit.
35struct logheader {
36 int n;
38};
40struct log {
41 struct spinlock lock;
42 int start;
43 int outstanding; // how many FS sys calls are executing.
44 int committing; // in commit(), please wait.
45 int dev;
46 int ncommit;
47 struct logheader lh;
48};
49struct log log;
51static void recover_from_log(void);
52static void commit();
54void
55initlog(int dev, struct superblock *sb)
57 if (sizeof(struct logheader) >= BSIZE)
58 panic("initlog: too big logheader");
60 initlock(&log.lock, "log");
66// Copy committed blocks from log to their home location
67static void
70 int tail;
72 for (tail = 0; tail < log.lh.n; tail++) {
73 if (recovering) {
74 printk("recovering tail %d dst %d\n", tail, log.lh.block[tail]);
75 }
76 struct buf *lbuf = bread(log.dev, log.start + tail + 1); // read log block
77 struct buf *dbuf = bread(log.dev, log.lh.block[tail]); // read dst
78 memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst
79 bwrite(dbuf); // write dst to disk
80 if (recovering == 0)
84 }
87// Read the log header from disk into the in-memory log header
88static void
91 struct buf *buf = bread(log.dev, log.start);
92 struct logheader *lh = (struct logheader *)(buf->data);
93 int i;
94 log.lh.n = lh->n;
95 for (i = 0; i < log.lh.n; i++) {
97 }
101// Write in-memory log header to disk.
102// This is the true point at which the
103// current transaction commits.
104static void
107 struct buf *buf = bread(log.dev, log.start);
108 struct logheader *hb = (struct logheader *)(buf->data);
109 int i;
110 hb->n = log.lh.n;
111 for (i = 0; i < log.lh.n; i++) {
113 }
118static void
122 install_trans(1); // if committed, copy from log to disk
123 log.lh.n = 0;
124 write_head(); // clear the log
127// called at the start of each FS system call.
128void
132 while (1) {
138 } else if (log.lh.n + (log.outstanding + 1) * MAXOPBLOCKS > LOGBLOCKS) {
139 // this op might exhaust log space; wait for commit.
144 } else {
147 break;
148 }
149 }
152// called at the end of each FS system call.
153// commits if this was the last outstanding operation.
154void
155end_op(void)
157 int do_commit = 0;
162 panic("log.committing");
163 if (log.outstanding == 0) {
166 } else {
167 // begin_op() may be waiting for log space,
168 // and decrementing log.outstanding has decreased
169 // the amount of reserved space.
171 }
174 if (do_commit) {
175 // call commit w/o holding locks, since not allowed
176 // to sleep with locks.
183 }
186// Copy modified blocks from cache to log.
187static void
190 int tail;
192 for (tail = 0; tail < log.lh.n; tail++) {
193 struct buf *to = bread(log.dev, log.start + tail + 1); // log block
194 struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block
196 bwrite(to); // write the log
199 }
202static void
205 if (log.lh.n > 0) {
206 write_log(); // Write modified blocks from cache to log
207 write_head(); // Write header to disk -- the real commit
208 install_trans(0); // Now install writes to home locations
209 log.lh.n = 0;
210 write_head(); // Erase the transaction from the log
211 }
214// Caller has modified b->data and is done with the buffer.
215// Record the block number and pin in the cache by increasing refcnt.
216// commit()/write_log() will do the disk write.
217//
218// log_write() replaces bwrite(); a typical use is:
219// bp = bread(...)
220// modify bp->data[]
221// log_write(bp)
222// brelse(bp)
223void
224log_write(struct buf *b)
226 int i;
229 if (log.lh.n >= LOGBLOCKS)
230 panic("too big a transaction");
232 panic("log_write outside of trans");
234 for (i = 0; i < log.lh.n; i++) {
235 if (log.lh.block[i] == b->blockno) // log absorption
236 break;
237 }
239 if (i == log.lh.n) { // Add new block to log?
241 log.lh.n++;
242 }
251 int n = log.ncommit + 1;
252 while (log.ncommit < n) {
257 }
258 }
260 return 0;