xv6, line by line
test yourself

Test yourself · category 17 of 20

Logging and crash recovery

begin_op, log_write and end_op, group commit, the single header write that commits a transaction, and how the next boot redoes it.

1warm-upChoose one

balloc has just set a bit in the bitmap buffer and calls log_write on it. What has happened on the disk by the time log_write returns?

kernel/log.c
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 }
2warm-upClick the line

In commit, click the line whose disk write is the moment the transaction becomes durable: if the power fails before this write completes, the transaction never happened; after it, the next boot will finish it.

kernel/log.c
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 }

Your pick: none yet (click a line in the code)

3warm-upPut in order

The last end_op of a group commits it. Put these events in the order they happen.

kernel/log.c
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 }
  1. end_op clears committing, increments ncommit, and wakes sleepers on &log
  2. end_op sets log.committing = 1 under log.lock, then releases the lock
  3. write_head again, with n = 0: erase the transaction
  4. write_log: copy each logged block from the cache into the log area
  5. write_head: write the header with n and the block list (the commit point)
  6. install_trans(0): copy each block to its home location and unpin it
4warm-upType a number

The log is empty (log.lh.n = 0) and no commit is running. Processes keep calling begin_op and none of them has reached end_op yet. How many of them are admitted before the next one has to sleep?

kernel/log.c
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 }
decimal, 0x hex or 0b binary
5solidPut in order

The machine crashed with a committed transaction still in the log. Put the steps of the next boot’s file-system start-up in order.

  1. write_head writes n = 0, clearing the log
  2. ireclaim scans the inodes for orphans (allocated, nlink == 0) and frees them
  3. fsinit reads the superblock (block 1) and checks its magic number
  4. read_head copies n and the block list from the header block into log.lh
  5. install_trans(1) copies each log block to its home block, printing recovering tail …
6warm-upChoose one

Inside one transaction, writei changes block 1006 and calls log_write; a moment later the same transaction changes block 1006 again and calls log_write a second time. What does the second call do?

kernel/log.c
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 }
7warm-upChoose one

Three processes are inside transactions (log.outstanding = 3). They call end_op one after another. Which call runs commit?

kernel/log.c
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 }
8warm-upMatch the pairs

echo hi > f is creating f (blocks 34, 47 and 33 are logged). Match each moment of power failure with what the disk shows after the next boot.

9solidChoose one

Why does log_write call bpin when it adds a new block to the transaction?

kernel/log.c
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 }
10solidChoose one

end_op releases log.lock before calling commit (line 172, then 177). What would happen if it called commit() while still holding log.lock?

kernel/log.c
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 }
11solidType a number

One transaction is running (log.outstanding = 1, log.committing = 0). A second process calls begin_op. What is the largest value of log.lh.n for which it is admitted without sleeping?

kernel/log.c
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 }
decimal, 0x hex or 0b binary
12deepType a number

A process writes 2000 bytes at offset 0 to an empty regular file (no data blocks yet). The log was empty and no other transaction runs. The filewrite chunk is one transaction. How many distinct blocks are in log.lh.block[] when end_op commits?

decimal, 0x hex or 0b binary
13solidChoose all that apply

Which of these situations make begin_op put the caller to sleep?

kernel/log.c
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 }
14warm-upTrue or false, and why

True or false: in this kernel, once a write() to a regular file has returned to user space, its data is on the disk.

Why?

15solidChoose one

install_trans calls bunpin only when recovering == 0. Why does it skip the unpin during crash recovery?

kernel/log.c
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 }
16deepChoose all that apply

At the end of a commit, end_op calls wakeup(&log) (line 181). Which of these sleeping processes can it wake?

17solidChoose one

Crash recovery runs from forkret in the first process (pid 1), not from main. Why?

kernel/proc.c
512void
515 extern char userret[];
516 static int first = 1;
517 struct proc *p = myproc();
519 // Still holding p->lock from scheduler.
522 if (first) {
523 first = 0;
525 // File system initialization must be run in the context of a
526 // regular process (e.g., because it calls sleep), and thus cannot
527 // be run from main().
530 // We can invoke kexec() now that file system is initialized.
531 // Put the return value (argc) of kexec into a0.
532 p->trapframe->a0 = kexec("/init", (char *[]){"/init", 0});
533 if (p->trapframe->a0 == -1) {
534 panic("exec");
535 }
536 }
18solidChoose all that apply

Which of these run inside a transaction (begin_op … end_op) in this kernel?

19solidDecode the bits

After a crash, gdb shows the first 16 bytes of block 2 (the log header, struct logheader, little-endian 32-bit ints) as 03 00 00 00 22 00 00 00 2f 00 00 00 21 00 00 00. sb.logstart is 2. Decode it.

Value: 03000000 22000000 2f000000 21000000

20solidClick the line

While a commit is running, log.lock is not held. Click the line in end_op that keeps new transactions from starting during the commit.

kernel/log.c
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 }

Your pick: none yet (click a line in the code)

21deepFill in the machine state

The machine is booting after a crash. pid 1 is in install_trans during recovery, executing line 78 (the memmove from the log block’s buffer to the home block’s buffer). Both buffers are held. What is the state of the hart running it?

kernel/log.c
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 }
22deepChoose one

commit reads log.lh.n and log.lh.block[] without holding log.lock, although log_write changes them under that lock. Why is this not a race?

kernel/log.c
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 }
23deepChoose one

Suppose commit omitted line 210 (it still sets log.lh.n = 0 in memory, but no longer writes the empty header). The disk header would keep saying n = 3: 34, 47, 33 after this commit. What could go wrong?

kernel/log.c
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 }
24deepChoose one

A process calls sync() while log.committing = 0, log.outstanding = 2 and log.ncommit = 5. When does sys_sync return?

kernel/log.c
25solidType a number

A commit finds log.lh.n = 4. How many disk writes does commit issue in total? (Count writes only, not reads.)

kernel/log.c
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 }
decimal, 0x hex or 0b binary