xv6, line by line
tour 39
Tours39 Pipes

Tour 39 · Devices and putting it all together · about 32 minutes · 19 steps

Pipes

ls | wc prints 24 96 608: ls produced 608 bytes and wc counted them, yet ls never knew wc existed, and neither program ever touched a file. Between them sat a pipe: 512 bytes of kernel memory, two counters, one spinlock, and two wait channels.

This tour follows those 608 bytes. You will see the pipe built (two struct files and one page from kalloc), wired into two processes by fork, close and dup, then used at the same time from two harts: ls writing one byte per system call on hart 1, wc reading whatever has piled up, from one byte to a few hundred at a time, on hart 2. When the pipe is empty the reader sleeps; when it is full the writer sleeps; and when the last write end is closed the reader gets 0, end of file, which is the only way wc ever learns that ls is done.

That last point hides the classic pipe bug. Forget to close the write end in wc’s own process, or in the shell that waits for it, and wc waits forever. By the end you will see exactly why.

Best after: 5. Life of a system call, 16. sleep and wakeup, and the lost-wakeup problem, 20. fork

Who is running where

The machine has three harts. You typed ls | wc at a freshly booted shell (pid 2), which forked pid 3 to parse and run the command and is now asleep in kwait. When the tour starts:

Hart What it is doing
0 Running pid 3 (a copy of sh), about to create the pipe
1 Idle in its scheduler; it will run ls (pid 4)
2 Idle in its scheduler; it will run wc (pid 5)

The harts are this tour’s choice; in our traced runs the processes moved between harts from run to run. The byte counts are real.

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. 1The shell asks for a pipe user/sh.c
  2. 2sys_pipe builds both ends at once kernel/sysfile.c
  3. 3Two struct files from the global table kernel/file.c
  4. 4One page for the pipe itself kernel/pipe.c
  5. 5A ring of 512 bytes and two counters that never wrap back kernel/pipe.c
  6. 6The descriptors, and the two numbers handed back kernel/sysfile.c
  7. 7Fork twice, then rewire descriptors 0 and 1 user/sh.c
  8. 8wc reads first, and the pipe is empty kernel/pipe.c
  9. 9ls writes one byte kernel/file.c
  10. 10pipewrite stores the byte under pi->lock kernel/pipe.c
  11. 11Every write wakes the reader kernel/proc.c
  12. 12wc wakes on hart 2 and takes what is there kernel/pipe.c
  13. 13When ls outruns wc, the pipe fills kernel/pipe.c
  14. 14ls exits and closes its descriptors kernel/proc.c
  15. 15fileclose drops the last reference kernel/file.c
  16. 16pipeclose marks the write end closed and wakes the reader kernel/pipe.c
  17. 17wc sees end of file kernel/pipe.c
  18. 18The classic bug: one forgotten close user/sh.c
  19. 19The last close frees the page kernel/pipe.c

Keys: ← → step · Home start