xv6, line by line
tour 28
Tours28 Crossing the user/kernel boundary in memory

Tour 28 · Memory · about 26 minutes · 15 steps

Crossing the user/kernel boundary in memory

You type ls README. To print one line, ls makes two system calls that carry pointers across the user/kernel boundary in opposite directions: open("README", …) hands the kernel the address of a string in ls’s memory, and fstat(fd, &st) hands it the address of a struct stat that the kernel must fill in.

Those addresses are just numbers. They mean something only in ls’s page table, and the kernel runs with a different page table, in which they point at nothing at all. So every byte that crosses the boundary goes through a handful of functions that walk the user page table by hand: copyinstr, copyin, copyout, and the either_copyin/either_copyout pair that lets one driver serve both user and kernel callers. They check every page on the way: is it mapped, is it a user page, is it writable, is it below MAXVA, and if it is missing, was it lazily allocated?

Real addresses in this tour come from a run of this build under QEMU and gdb: ls is pid 3, its whole address space is 0x4000 bytes, the string "README" is at 0x3fe0 and st is at 0x3d38, both on its single stack page, which lived at physical page 0x87f1c000 in that run.

Best after: 5. Life of a system call, 6. System-call arguments and user pointers, 25. A user address space, 26. sbrk, eager and lazy, and page faults

Who is running where

Hart What it is doing
0 Idle in its scheduler, or running something else
1 Running ls (pid 3) in user mode, about to call open: the process this tour follows
2 Running whatever else is runnable, or idle in its scheduler. sh (pid 2) is on no hart: it is asleep in wait, waiting for ls

ls was started by the shell with argv = {"ls", "README", 0}. kexec copied both strings to the top of its stack page.

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. 1ls passes two pointers to the kernel user/ls.c
  2. 2Why the kernel cannot just dereference the pointer kernel/syscall.c
  3. 3fetchstr hands the job to copyinstr kernel/syscall.c
  4. 4copyinstr walks page by page kernel/vm.c
  5. 5walkaddr refuses anything that is not a user page kernel/vm.c
  6. 6The walk itself, in software kernel/vm.c
  7. 7When walkaddr says no: the lazy-page fallback kernel/vm.c
  8. 8fstat and filestat prepare a kernel copy kernel/file.c
  9. 9copyout checks one more thing kernel/vm.c
  10. 10A bad pointer is an error, not a crash kernel/vm.c
  11. 11either_copyout serves two kinds of caller kernel/proc.c
  12. 12The console copies out while holding a spinlock kernel/console.c
  13. 13Pipes copy one byte at a time under pi->lock kernel/pipe.c
  14. 14Why no other hart can change the page table mid-copy kernel/proc.c
  15. 15The price of a crossing user/ls.c

Keys: ← → step · Home start