xv6, line by line
lab 24
Lab 2424 Atomic rename

Lab 24 · reveal · 16 steps · 6 commits

Atomic rename: the reference solution

xv6 has no way to rename a file. You can ln old new and then rm old, which is how early Unix mv worked, but that is two system calls and two transactions, and it cannot move a directory at all. In this lab you add rename(old, new): one system call that moves a name within a directory or between directories, replaces an existing file in the same step, and moves whole directories, all inside one transaction of the log.

Each piece of the job looks small: write one directory entry, clear another, fix a link count. The questions come from doing them together. A crash between two steps must not leave both names, or neither. Two directories must be locked at once, and the tree’s existing rule, parent before child, says nothing about two directories that are not related: what order do you pick, and what does your choice do to the code that already locks inodes? How do you notice that someone is moving a directory into its own subtree, when xv6 keeps no parent pointers in memory? And does all of it fit in the ten blocks a transaction may write?

The reference solution is six commits. It survived 30 random kills of QEMU in the middle of renaming, with the file system consistent every time, and it never deadlocked on three harts under a load that, with the race window widened, deadlocked both obvious alternatives.

Each step shows one change on the branch ext/24-rename, the code around it, and the state of the machine when that code runs.

The route
  1. 1A system call that refuses everything kernel/sysfile.c
  2. 2Rewriting one directory entry in place kernel/fs.c
  3. 3Same directory: one lock covers both names kernel/sysfile.c
  4. 4The new name first, the old name second kernel/sysfile.c
  5. 5A global lock for renames between directories kernel/sysfile.c
  6. 6Walking up the tree, one lock at a time kernel/sysfile.c
  7. 7Look first, under the global lock only kernel/sysfile.c
  8. 8Lock the higher directory first kernel/sysfile.c
  9. 9Did the names change while we were not looking? kernel/sysfile.c
  10. 10Refuse to move a directory below itself kernel/sysfile.c
  11. 11The new parent must have room for one more link kernel/sysfile.c
  12. 12A directory carries its `..` and a link kernel/sysfile.c
  13. 13The commit point, and two crashes on either side of it kernel/log.c
  14. 14mv user/mv.c
  15. 15Seven processes, three harts, one lock order user/renametest.c
  16. 16Testing a crash from inside the machine that crashes user/renametest.c

Keys: ← → step · Home start