One command, every concept
Typing python3 hello.py and pressing Enter sets off the whole story below, and every step has already been covered.
- The shell reads your keystrokes. It is just a user-space process, blocked on a
readsyscall from the keyboard, from lessons 6-2 and 8-1. - The shell asks the kernel for a child process and has it load the
python3program from disk into RAM, from lessons 3-3 and 1-1. The kernel builds a process with a fresh PID, a brand-new page table mapping its private virtual address space, an empty stack and heap, and an entry in the process table, from lessons 3-1, 3-2, 4-1, and 4-4. - The scheduler starts giving it time slices, from lesson 6-1, and the CPU begins the fetch-decode-execute cycle on Python's instructions, from lesson 1-2.
- Python reads hello.py with
openandreadsyscalls, decoding UTF-8 into text, from lessons 7-1 and 2-3, and starts interpreting it.
Notice how much has happened before a single line of hello.py runs. Steps 1 through 3 are pure operating system work, which is why launching a process costs milliseconds while calling a function costs nanoseconds.
The ending of the same command
- hello.py calls print("Hello!"). The text goes to a buffer, the newline flushes it, the
writesyscall crosses into the kernel, the bytes land in file descriptor 1, and the terminal draws glyphs, from lessons 7-2 and 8-2. - The script ends. Python exits with code 0. Had it hung instead, Ctrl-C would have delivered SIGINT, from lesson 3-4. The kernel frees the process's memory and page table, closes its descriptors, removes the process-table entry, and hands the exit code to its parent, the shell, from lessons 3-3, 3-4, 4-3, and 4-4.
- The shell wakes up. It had been blocked waiting for its child, so it sees exit code 0 and prints the next prompt.
| Phase | Who is doing the work |
|---|---|
| steps 1 to 3 | the shell and the kernel |
| step 4 to 5 | Python, with kernel help at each syscall |
| step 6 to 7 | the kernel, then the shell |
Elapsed time is maybe 30 milliseconds, containing thousands of context switches system-wide and every single mechanism in this course.
The order of the four milestones
For python3 hello.py, the four events happen in this order:
| Order | Event |
|---|---|
| 1 | the kernel creates a new process and loads python3 |
| 2 | the scheduler gives the new process its first time slice |
| 3 | a write syscall sends Hello! to the terminal |
| 4 | the shell receives exit code 0 |
The reasoning is a chain of prerequisites. The process must exist before it can be scheduled, it must get CPU time before its code can run far enough to print, and it must die before the parent can collect an exit code.
The dependency in each step is worth stating, because it is what makes the order forced rather than merely typical. No amount of scheduling luck can move an exit code before the process that produced it has finished.
The state of a process waiting on the disk
While python3 waits for hello.py's bytes to arrive from the disk, its state is waiting, also called blocked, and the kernel is what moved it off the ready queue.
A blocking read syscall parks the process in the waiting state from lesson 3-1, the scheduler spends its slices on other processes as described in lesson 6-2, and the kernel wakes it when the disk data arrives.
| Unit | Contribution to this one moment |
|---|---|
| 3 | the waiting state exists |
| 6 | the scheduler skips waiters |
| 8 | the syscall is what blocked it |
Three units are cooperating in a single instant, and the process itself experiences none of it. From inside, read simply took a while to return.
Naming the number handed to the shell
The number 0 that the kernel hands from the dying python3 process to the waiting shell is the exit code, also called the exit status.
Every dying process reports one, with 0 for success and non-zero for failure, and the parent process receives it. This is the value from lesson 3-3.
| Consumer | How it uses the code |
|---|---|
| the shell | exposes it as $? |
&& chains | runs the next command only on 0 |
| CI systems | pass or fail the build |
Zero means success by universal convention, which is what allows tools written decades apart in different languages to be chained together without agreeing on anything else.