xv6, line by line
tour 37
Tours37 A keystroke's journey

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

A keystroke's journey

The shell has printed $ and is waiting. You press l, then s, then Enter. Within a fraction of a millisecond each letter appears on your screen, and after Enter the shell has the line "ls\n" in its buffer. No program put those letters on the screen: the kernel did, from inside an interrupt handler, before any process had seen them.

This tour follows those three keystrokes from the wire into the shell’s memory: the UART raising its interrupt line, the PLIC handing it to one of the three harts, uartintr pulling the byte out of the chip, and consoleintr echoing it and editing the line under cons.lock. Then the line completes, wakeup finds the sleeping shell, and consoleread copies the bytes out, one per system call.

The input side of the console is a small producer/consumer problem. The producer is an interrupt handler that may run on any hart at any moment; the consumer is a process that may run on a different hart. One spinlock and the sleep/wakeup protocol keep them from losing a byte or a wakeup. Output through write is the subject of Tour 5: Life of a system call and Tour 38: Output to the console from three harts; here we only print the echo.

Best after: 5. Life of a system call, 9. Device interrupts and the PLIC, 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), which has just printed $ : the process this tour follows
1 Idle in its scheduler, waiting in wfi
2 Idle in its scheduler, waiting in wfi

init (pid 1) is asleep in kwait. This is the first command typed since boot, so the console’s input buffer is empty and its three indices r, w and e are all 0.

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 prints its prompt and asks for a line user/sh.c
  2. 2gets reads one byte at a time user/ulib.c
  3. 3The file layer hands the read to the console driver kernel/file.c
  4. 4The console's input buffer and its three indices kernel/console.c
  5. 5Nothing to read. Register, then let go kernel/console.c
  6. 6The shell goes to sleep and hart 0 goes idle kernel/proc.c
  7. 7You press l. Hart 2 takes the interrupt kernel/trap.c
  8. 8uartintr pulls the byte out of the chip kernel/uart.c
  9. 9consoleintr echoes l and stores it kernel/console.c
  10. 10The echo spins on the UART, interrupts off kernel/uart.c
  11. 11s, and what backspace and control-U would do kernel/console.c
  12. 12Enter commits the line and wakes the reader kernel/console.c
  13. 13wakeup finds the shell kernel/proc.c
  14. 14Some hart picks the shell up kernel/proc.c
  15. 15consoleread takes one byte and returns kernel/console.c
  16. 16The rest of the line arrives without sleeping user/ulib.c
  17. 17Control-D: end of file from a keyboard kernel/console.c
  18. 18Control-P: a process listing from inside the interrupt kernel/proc.c
  19. 19The line reaches the shell, and the next story begins user/sh.c

Keys: ← → step · Home start