xv6, line by line
tour 20
Tours20 fork

Tour 20 · Processes · about 37 minutes · 20 steps

fork

You type ls at the $ prompt and press Enter. Before a single line of ls runs, the shell has to make a second copy of itself: a new process with its own memory, its own kernel stack and its own process ID, but the same open files, the same current directory and the same registers. That is fork, and this tour watches it happen, from the shell’s call on hart 0 to the moment the copy wakes up for the first time on hart 2 and sees fork() return 0.

On the way you will see a free slot found in the process table under its lock, a PID (process ID) handed out under a second lock, five pages of the shell’s memory copied one by one, open files shared by bumping reference counts, and then the delicate part: publishing the new process to the other harts. The kernel releases one lock, takes another, then retakes the first, and every step of that dance is there to avoid a deadlock or a half-built process being run.

The system-call path in and out of the kernel is the one from Tour 5: Life of a system call; this tour starts where syscall calls sys_fork. Pids, slots, sizes and addresses were checked in a QEMU run of this build with gdb attached. The hart assignment is staged: in that run the child started on the same hart as the shell (hart 0), and this tour follows the case where another hart picks it up, because the code must be correct either way.

Best after: 5. Life of a system call, 13. swtch and the lock handed across a context switch, 16. sleep and wakeup, and the lost-wakeup problem

Who is running where

The machine has three harts. When the tour starts:

Hart What it is doing
0 Running the shell sh (pid 2) in user mode. It has just read ls\n into its buffer
1 In its scheduler, finding nothing runnable, waiting in wfi
2 The same: idle in its own scheduler loop

init (pid 1) is asleep in kwait, waiting for the shell. The process table proc[] has 64 slots: slot 0 is init, slot 1 is sh, and slots 2–63 are UNUSED.

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 decides to fork user/sh.c
  2. 2sys_fork hands over to kfork kernel/sysproc.c
  3. 3kfork asks for an empty process kernel/proc.c
  4. 4Scanning the table, one lock at a time kernel/proc.c
  5. 5A pid from its own lock kernel/proc.c
  6. 6A trapframe page and an empty page table kernel/proc.c
  7. 7Rigging the first context switch kernel/proc.c
  8. 8Copying the shell's memory kernel/proc.c
  9. 9uvmcopy, page by page kernel/vm.c
  10. 10Registers, and the one that differs kernel/proc.c
  11. 11Sharing open files and the current directory kernel/proc.c
  12. 12Name, pid, and a lock let go kernel/proc.c
  13. 13Recording the parent under wait_lock kernel/proc.c
  14. 14Publishing the child kernel/proc.c
  15. 15The parent goes back to wait user/sh.c
  16. 16Hart 2's scheduler finds the child kernel/proc.c
  17. 17swtch "returns" into forkret kernel/proc.c
  18. 18forkret leaves for user space kernel/proc.c
  19. 19The child sees fork() return 0 user/sh.c
  20. 20What one fork cost kernel/proc.c

Keys: ← → step · Home start