xv6, line by line
tour 36
Tours36 Reading and writing a file

Tour 36 · File system · about 29 minutes · 20 steps

Reading and writing a file

wc README reads 2441 bytes, 512 at a time. echo hi > f writes three bytes, two of them in one write and the newline in another, and the second lands right after the first. Both look trivial from user space. In the kernel, each read and write must find the file’s current offset, translate it into a disk block, perhaps allocate that block, copy bytes across the user/kernel boundary, and, for writes, put every changed block into the log.

This tour follows both, through fileread and filewrite, readi and writei, and bmap, which maps a position in a file to a block on the disk, allocating with balloc and bzero when the file grows. You will see why filewrite cuts big writes into 3072-byte pieces, each in its own transaction, and what that means when the power fails or when two processes write to the same file.

The last part puts two processes on two harts writing through one shared file descriptor, and shows exactly what the inode lock guarantees about the result and what it leaves to chance.

Best after: 31. The log: begin_op, commit and group commit, 33. The life of an inode, 34. Path lookup

Who is running where

The machine has three harts, on a fresh boot. The user runs wc README (pid 3), then echo hi > f (pid 4, which creates f as inode 24; Tour 35: Creating and naming files covers the creation). When the tour starts:

Hart What it is doing
0 Idle in its scheduler
1 Running wc (pid 3), later echo (pid 4): the processes this tour follows
2 Idle, or running another process using the same files

README is inode 2: 2441 bytes in blocks 48, 49 and 50, its dinode in block 33. Block numbers come from this build’s mkfs and a traced copy of the kernel.

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. 1wc reads 512 bytes at a time user/wc.c
  2. 2sys_read finds the open file kernel/sysfile.c
  3. 3The offset is read and advanced under the inode lock kernel/file.c
  4. 4readi clamps the request to the file kernel/fs.c
  5. 5One block at a time, copied straight to user memory kernel/fs.c
  6. 6bmap, the direct case kernel/fs.c
  7. 7echo writes two bytes user/echo.c
  8. 8filewrite cuts writes into 3072-byte pieces kernel/file.c
  9. 9One transaction per piece kernel/file.c
  10. 10writei refuses holes and huge files kernel/fs.c
  11. 11bmap allocates block 0 of f kernel/fs.c
  12. 12balloc finds a free bit kernel/fs.c
  13. 13bzero, so old data never leaks kernel/fs.c
  14. 14Copy from user space into the block, and log it kernel/fs.c
  15. 15Grow the size, write the inode, commit kernel/fs.c
  16. 16The second write appends, because the offset moved kernel/file.c
  17. 17Past twelve blocks, the indirect block kernel/fs.c
  18. 18Two processes, one file descriptor kernel/file.c
  19. 19What a big write looks like to a second writer kernel/file.c
  20. 20What a read and a write cost kernel/fs.c

Keys: ← → step · Home start