The OS needs a per-process data structure to park all of a process’s state. This note covers that structure (the process control block), the queues the scheduler keeps those structures on, what a context switch does, and how fork and exec create processes. Handles fit in as the way user code names kernel objects: each process has a local handle table, and the kernel keeps a global handle table.
Process control block
The PCB tracks a process’s state, with each entry identified by PID. While a process isn’t running, the OS keeps all of its state in a proc structure within the PCB.
proc has many fields: the PID, a pointer to the parent’s proc, execution state, and so on. Linux defines it as task_struct in include/linux/sched.h, and it is huge; the lecture counted over 95 fields.
proc and CPU state
A context switch takes the running process’s state off the CPU, saves it into the PCB, then grabs the next scheduled process’s proc and loads it onto the CPU to run. Choosing which process runs next is called scheduling.
The kernel itself has no process of its own; it is a block of code. The CPU is always executing in the context of some process, and that code can be in either kernel or user mode.
State queues
The OS typically keeps a queue of procs for each state a process can be in (WAITING, READY, and so on). There can be many wait queues, one for each kind of wait: a particular device, a timer, a message.
The queues implement the canonical process state machine, with each transition moving the proc between queues:
stateDiagram-v2 [*] --> READY: fork places child on ready queue READY --> RUNNING: scheduler dispatches, context switch in RUNNING --> READY: preempted, context switch out RUNNING --> WAITING: blocks on device, timer, or message WAITING --> READY: awaited event occurs RUNNING --> [*]: exit
procs are plain data structures, dynamically allocated in OS memory. Existing processes create new ones. The creator is the parent, and the created process is the child.
Creating processes
fork clones the calling process. exec keeps the process and replaces its program: it loads a new program into the address space (overwriting the old one), initializes the hardware state and arguments for the program, and places the process on the ready queue. exec does not create a new process.
To start a program from a shell, the shell forks itself and the child execs the program.
Making creation faster
fork copies the whole address space, which is wasted work when the child immediately calls exec. Two fixes exist.
vfork is the older approach, now uncommon. The child’s address space is a pointer to the parent’s rather than a copy, same page table and everything, along with an unenforced promise that the child won’t modify it.
Copy-on-write is the modern approach. The child gets its own page table, but every entry maps to the parent’s frames and both mappings are marked read-only. The first write to a page faults, and the OS copies just that page and makes it writable. Pages nobody writes never get copied.