xv6, line by line
tour 31
Tours31 The log: begin_op, commit and group commit

Tour 31 · File system · about 30 minutes · 18 steps

The log: begin_op, commit and group commit

Appending to a file changes several disk blocks: the free-block bitmap, the new data blocks, and the inode that points to them. If the power fails after some of those writes and before others, the disk is left inconsistent: a block marked used that no file owns, or worse, an inode pointing to a block still marked free. xv6’s answer is a write-ahead log: changes go first to a log area on disk, a single header write “commits” them all at once, and only then are they copied to their real places.

This tour watches the log under load. You run logstress f1 f2 f3: three children, on three harts, each call write(fd, buf, 2000) on their own empty file at the same time. Each write is a transaction that may touch up to 10 blocks. The log holds 30. You will see who gets admitted and who must wait, how 21 calls to log_write collapse into 8 logged blocks, how the last process to finish commits everyone’s changes together (group commit), and why that commit runs without holding any spinlock.

Block numbers are from a fresh fs.img of this build: the bitmap is block 46, the three files are inodes 24, 25 and 26 (all in inode block 34), the log header is block 2 and the log blocks are 3–32, and the first free data block is 1006. A trace of logstress in this build confirms the admission rule, and that the first write’s log_write calls hit 46, 1006, 1006, 46, 1007, 1007, 34. The interleaving below is illustrative: which child is admitted when, which inode each file gets, and the blocks given to B and C (1008–1011) are one possible run, chosen for clarity. (Inodes 24–26 also assume the first run after make: inode 23 is console, which init creates at first boot.)

Best after: 16. sleep and wakeup, and the lost-wakeup problem, 29. A disk read, end to end, 30. The buffer cache

Who is running where

logstress f1 f2 f3 (pid 3) has forked three children; each has created its file and is about to make its first write.

Hart What it is doing
0 Child A (pid 4): write of 2000 bytes to f1 (inode 24)
1 Child B (pid 5): write of 2000 bytes to f2 (inode 25)
2 Child C (pid 6): write of 2000 bytes to f3 (inode 26)

The log is empty: log.lh.n = 0, log.outstanding = 0, log.committing = 0.

Three harts are running. This tour follows one path through the code, but the machine has three CPUs executing at the same time. Watch the locks held display at the top of each step, and read the Meanwhile, on other harts boxes: they show what the other CPUs could be doing at that very moment.
The route
  1. 1Three children, three files, three writes user/logstress.c
  2. 2filewrite brackets each chunk with begin_op and end_op kernel/file.c
  3. 3The log's state kernel/log.c
  4. 4Child A is admitted kernel/log.c
  5. 5Child A logs the bitmap kernel/log.c
  6. 6Child C must wait for room kernel/log.c
  7. 7Absorption, twenty-one calls become eight blocks kernel/log.c
  8. 8One inode block, three files kernel/fs.c
  9. 9end_op, but not the last kernel/log.c
  10. 10The last end_op commits for everyone kernel/log.c
  11. 11Why commit runs without log.lock kernel/log.c
  12. 12Step 1: copy the blocks into the log kernel/log.c
  13. 13Step 2: the commit point kernel/log.c
  14. 14Step 3: install, and unpin kernel/log.c
  15. 15Step 4: erase the transaction kernel/log.c
  16. 16Reopening the door kernel/log.c
  17. 17sync waits for the commit in flight kernel/log.c
  18. 18What group commit bought kernel/log.c

Keys: ← → step · Home start