Step 1 of 5 · Reading · ~3 min
Read
Processes & Scheduling
Processes & Threads
A process is the kernel's name for "a program that is running": an address space, one or more threads of execution, and a record of everything the kernel needs to manage it. In this lesson your program is the kernel's process table. It creates children with fork, turns exiting processes into zombies, hands orphans to init, and reaps zombies in wait, with the same rules as Unix.
What the kernel remembers about each process
Each process has a process control block (in xv6 it is struct proc). The important fields:
| Field | Purpose |
|---|---|
pid | the process id, unique for as long as the entry exists |
parent | who created it; used by wait and by init adoption |
state | where it is in the lifecycle (below) |
| page table | its private address space |
| kernel stack and saved context | where it resumes when scheduled |
| open files, working directory | inherited on fork |
| exit status | filled in by exit, read by the parent's wait |
The lifecycle: embryo (being built), runnable (ready), running, sleeping (blocked on something), zombie (exited, not yet reaped). Your table needs only runnable and zombie.
fork: the only way in
fork creates a copy of the calling process: same memory contents (copied, or shared copy-on-write, later lesson), same open files. It returns twice: the child gets 0, the parent gets the child's new pid. Everything else on a Unix system follows from this one primitive: the shell forks, and the child replaces itself with the program you asked for. Process ids grow upward and are not handed out again while an entry exists.
exit, zombies and wait
When a process exits it cannot simply vanish, because its parent may want to know how it ended. So the kernel frees its memory but keeps the table entry as a zombie, holding only the pid and the exit status. The parent collects it with wait, which returns the child's pid and status and finally frees the entry. wait has three outcomes:
- a child is a zombie: reap one (the lowest pid, for determinism) and return it;
- children exist but none has exited: the caller would block;
- no children at all: error
ECHILD.
Orphans and init
If a process exits while it still has children, they must not be left pointing at a dead parent. The kernel reparents them, live or zombie, to init, process 1. Init's whole job is a loop around wait, so adopted zombies are always eventually reaped. This also explains why init must never exit: with no ancestor left to adopt orphans the kernel panics.
A worked trace
| Command | Effect | Output |
|---|---|---|
FORK 1 | pid 2, parent 1 | 2 |
FORK 2 | pid 3, parent 2 | 3 |
FORK 2 | pid 4, parent 2 | 4 |
EXIT 2 9 | 2 becomes a zombie; 3 and 4 are reparented to 1 | OK |
WAIT 1 | init reaps zombie child 2 | REAPED 2 9 |
After that the table holds pids 1, 3 and 4, with 3 and 4 children of init.
Threads: processes that share
A thread is a schedulable flow of control. A process is one address space plus one or more threads sharing it. Each thread needs its own stack and saved registers; the code, heap and open files are shared. Linux makes both with one system call that takes flags saying what to share, and the scheduler treats them alike.
Common mistakes
- Reusing a pid, or numbering from 0.
- Removing the exiting process's entry immediately, so the parent's
waitfinds nothing. - Forgetting to reparent zombie children as well as live ones.
- Letting
waitreturn a zombie that belongs to somebody else's children.
Your exercise
Implement the process table commands in the exercise. The starter parses commands and prints tables; you write the logic of fork, exit and wait.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…