xv6, line by line
tour 22
Tours22 exec

Tour 22 · Processes · about 29 minutes · 18 steps

exec

The shell’s child (pid 3) is a copy of the shell (Tour 20: fork). It has parsed the command line, found the single word ls, and now calls exec("ls", argv). When that call succeeds, the process is no longer the shell: its memory holds the code of ls, its stack holds ls’s arguments, its program counter is at ls’s first instruction, and its name is ls. Its pid, its open files, its current directory and its parent stay the same.

This tour follows kexec line by line: reading the ELF file through the file system, building a brand-new page table beside the old one, loading two segments, laying out a guard page and a stack, pushing the argument strings, and the single commit point where the new image replaces the old. Before that point, any failure leaves the shell intact and exec returns -1; after it, there is nothing to return to.

The concurrency story is unusual. The process’s own memory needs no locks at all, because nothing else can touch it. The locks that matter belong to the file system: the log, the ls inode's sleep-lock and the buffer cache, all shared with every other hart. Addresses and sizes come from readelf on user/_ls and from a gdb run of this build.

Best after: 5. Life of a system call, 20. fork, 25. A user address space

Who is running where

The machine has three harts. When the tour starts:

Hart What it is doing
0 Idle in its scheduler. The shell (pid 2) is asleep in wait (Tour 21: exit, wait and zombies)
1 Idle, or running whatever else is runnable
2 Running the shell’s child, pid 3, still named sh, in user mode

The child’s memory is the shell’s five pages plus a 64 KiB heap that parsecmd just allocated through malloc (Tour 26: sbrk, eager and lazy, and page faults): p->sz = 0x15000.

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 child asks to become ls user/sh.c
  2. 2sys_exec copies everything into the kernel first kernel/sysfile.c
  3. 3A transaction, a path lookup and an inode lock kernel/exec.c
  4. 4Is this an ELF file? kernel/exec.c
  5. 5A second page table, beside the first kernel/exec.c
  6. 6Reading the program headers kernel/exec.c
  7. 7Allocating pages for each segment kernel/vm.c
  8. 8loadseg copies the file into physical pages kernel/exec.c
  9. 9Done with the file kernel/exec.c
  10. 10A guard page and a one-page stack kernel/exec.c
  11. 11Pushing the argument strings kernel/exec.c
  12. 12Pushing argv, and what main will see kernel/exec.c
  13. 13Naming the process kernel/exec.c
  14. 14The commit point kernel/exec.c
  15. 15Every way to fail kernel/exec.c
  16. 16The return value becomes argc kernel/sysfile.c
  17. 17ls starts at start() user/ulib.c
  18. 18What exec cost, and why it is shaped this way kernel/exec.c

Keys: ← → step · Home start