xv6, line by line
lab 22
Lab 2222 Doubly-indirect blocks: large files

Lab 22 · reveal · 18 steps · 8 commits

Doubly-indirect blocks: large files: the reference solution

In this tree a file can have at most 268 blocks: 12 whose numbers sit in the inode itself, and 256 more listed in one indirect block. At 1024 bytes a block, that is 268 KiB, and write refuses to go further. In this lab you give the inode one more level: a doubly-indirect block that lists 256 indirect blocks, each listing 256 data blocks. One direct slot is traded for it, so the on-disk inode keeps its size, and the limit grows to 11 + 256 + 65,536 = 65,803 blocks, a little over 64 MiB.

The change to bmap is about thirty lines. What makes the lab worth doing is everything those lines touch. Each new level is a block that must be allocated inside a transaction, zeroed, recorded in its parent and logged, or a crash (or just the buffer cache running out of room) loses part of the file. itrunc must free a three-level tree, in an order that is safe while other harts allocate blocks. The log’s fixed budget per system call, sized for one level of indirection, has to be recounted. The disk itself must grow from 2000 to 70,000 blocks for a maximum-size file to fit, which changes the layout that mkfs computes. And because the meaning of one inode slot changes, every old fs.img is misread by the new kernel, and even written to wrongly.

The recount finds a real problem: the deeper tree lets one operation log more blocks than the log promised, and on this kernel a broken promise ends in panic: bget: no buffers. So the budget is raised too. The reference solution is eight small commits. With it, a 6580-block file (24 times the old limit) is written, read back and freed in about 40 seconds on an otherwise idle computer, and usertests’ writebig writes a full 65,803-block file.

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

The route
  1. 1Thirteen slots, new meanings kernel/fs.h
  2. 2The in-memory inode must agree kernel/file.h
  3. 3bmap, third range: the doubly-indirect block kernel/fs.c
  4. 4Allocating a block inside the transaction kernel/fs.c
  5. 5The middle level, logged with its parent kernel/fs.c
  6. 6The bottom level, and the data block kernel/fs.c
  7. 7Why a crash leaves a consistent file kernel/fs.c
  8. 8itrunc: children first, root last kernel/fs.c
  9. 9One transaction for the whole truncation kernel/fs.c
  10. 10FSSIZE, the size of the disk kernel/param.h
  11. 11What mkfs derives from FSSIZE mkfs/mkfs.c
  12. 12balloc scans every bitmap block, from the start kernel/fs.c
  13. 13The switch: MAXFILE kernel/fs.h
  14. 14mkfs keeps its own, smaller limit mkfs/mkfs.c
  15. 15Re-budget the log: MAXOPBLOCKS 13 kernel/param.h
  16. 16The write chunk, recounted term by term kernel/file.c
  17. 17A system call to count free blocks kernel/fs.c
  18. 18The test user/bigfiletest.c

Keys: ← → step · Home start