A process is a program in execution. The program is static code on disk. The process is the live instance: code plus current state, registers, open files, and a place in the scheduling queue.
Process vs program
A program is a passive file. A process is active. One program can spawn many processes (open two terminals running the same shell). Each process has its own address space, program counter, and stack. The operating system tracks all of this in a Process Control Block (PCB).
Process states
Every process moves through a lifecycle. The five-state model covers it: new, ready, running, waiting, terminated.
Process Control Block
The PCB stores everything the OS needs to manage a process: PID, state, program counter, CPU registers, memory limits, list of open files, and scheduling priority. Context switching means saving one PCB and loading another.
Scheme
; A process control block as a simple association list; Each PCB holds: pid, state, program-counter, registers
(define (make-pcb pid state pc registers)
(list (cons 'pid pid)
(cons 'state state)
(cons 'pc pc)
(cons 'registers registers)))
(define (pcb-get pcb key)
(cdr (assoc key pcb)))
; Create two processes
(define p1 (make-pcb 1 'running 100 '(ax=0 bx=5)))
(define p2 (make-pcb 2 'ready 200 '(ax=3 bx=7)))
(display "P1 state: ") (display (pcb-get p1 'state)) (newline)
(display "P2 state: ") (display (pcb-get p2 'state)) (newline)
(display "P1 PC: ") (display (pcb-get p1 'pc)) (newline)
(display "P2 PC: ") (display (pcb-get p2 'pc))
Python
# Process Control Block in Pythonclass PCB:
def __init__(self, pid, state, pc, registers):
self.pid = pid
self.state = state
self.pc = pc
self.registers = registers.copy()
def __repr__(self):
returnf"PCB(pid={self.pid}, state={self.state}, pc={self.pc})"# Create processes
p1 = PCB(1, "running", 100, {"ax": 0, "bx": 5})
p2 = PCB(2, "ready", 200, {"ax": 3, "bx": 7})
print(p1)
print(p2)
# Context switch: save p1, load p2def context_switch(old, new):
old.state = "ready"
new.state = "running"print(f"Switched from PID {old.pid} to PID {new.pid}")
return old, new
p1, p2 = context_switch(p1, p2)
print(p1)
print(p2)
Context switching
When the OS switches from one process to another, it saves the entire CPU state (registers, program counter, stack pointer) into the outgoing PCB and loads the state from the incoming PCB. This is pure overhead. The faster you can do it, the more responsive the system feels. Hardware support (like special save/restore instructions) helps.
Scheme
; Simulate context switching between processes; A "ready queue" is just a list of PCBs
(define (make-pcb pid state pc)
(list (cons 'pid pid) (cons 'state state) (cons 'pc pc)))
(define (pcb-get pcb key) (cdr (assoc key pcb)))
(define (set-state pcb new-state)
(map (lambda (pair)
(if (eq? (car pair) 'state)
(cons 'state new-state)
pair))
pcb))
; Three processes in the ready queue
(define ready-queue
(list (make-pcb 1 'ready 100)
(make-pcb 2 'ready 200)
(make-pcb 3 'ready 300)))
; Dispatch: take first from queue, set to running
(define running (set-state (car ready-queue) 'running))
(define remaining (cdr ready-queue))
(display "Now running PID: ")
(display (pcb-get running 'pid)) (newline)
(display "State: ")
(display (pcb-get running 'state)) (newline)
(display "Remaining in queue: ")
(display (length remaining))
Neighbors
🔀 Turing Machines — abstract processes: a tape, a head, and a state machine
💻 TOC Ch.5 — pushdown automata and stack machines: processes use a call stack that is a runtime instance of the pushdown automaton
🪄 SICP Ch.8 — environments and closures: OS processes are isolated environments, analogous to lexical closures in a language
⚙ Algorithms Ch.1 — asymptotic analysis: process creation and context-switch costs determine scheduling feasibility