Before the questions, make sure you can: describe the OS as a resource manager and classify resources (preemptable or not, sharable or exclusive, reusable or consumable); state the goals of resource management; distinguish a program from a process and describe a process's memory (text, data, heap, stack); list the contents of a process control block; draw the process state diagram including suspended states, and name the event behind each transition; explain a context switch and compute the CPU efficiency of time slicing with context-switch overhead; trace fork, exec, wait and exit, count the processes created by repeated forks, and explain zombies and orphans; compare processes and threads, user-level and kernel-level threads and the threading models; and use Java threads and thread pools for parallel work.
A process is a program in execution — the unit to which the OS gives resources: CPU time, memory, open files, network connections. The OS's job as a resource manager is to create processes, keep track of everything each one owns (in its process control block), move them between states (running, waiting, ready…) and share the machine among them so fast that each feels it has the computer to itself. Threads are the lighter-weight units of execution inside a process — and the way modern software uses many cores.
The OS as a resource manager
A resource is anything a process needs to make progress. Resources can be classified in several ways:
| Classification | Meaning | Examples |
|---|---|---|
| Preemptable | Can be taken away and given back later without harm | CPU (save registers, resume later), memory (swap out) |
| Non-preemptable | Taking it away mid-use causes failure | A printer in the middle of a job, a DVD burner, a lock |
| Sharable | Several processes can use it at once | Read-only files, code pages of a shared library |
| Exclusive | Only one user at a time | A printer, a write lock, a GPU context (typically) |
| Reusable | Returned after use, available again | CPU, memory, devices |
| Consumable | Used up | Messages, signals, interrupts |
The resource manager performs three basic functions: abstraction (turn hardware into convenient objects), sharing/multiplexing (in time — the CPU; in space — memory) and scheduling (decide who gets what, when).
Goals of resource management: high utilization (no idle expensive resource), fairness (no starvation), isolation/protection (one process cannot harm another), responsiveness and predictability, and, increasingly, energy and cost efficiency. On a Linux server, control groups (cgroups) let the OS enforce CPU, memory and I/O limits per group of processes — the basis of containers (Chapter 12).
Program vs. process
A program is a passive file on disk (instructions and data). A process is an active instance of a program: it has a program counter, registers, memory and open files. Two students running the same editor create two processes from one program.
A process's memory (address space) is typically laid out as:
| Region | Contents | Grows |
|---|---|---|
| Text | Machine code (read-only, often shared) | Fixed |
| Data | Global and static variables | Fixed |
| Heap | Dynamically allocated objects (new, malloc) |
Upward |
| Stack | Function calls: parameters, local variables, return addresses | Downward |
The process control block (PCB)
The OS represents each process with a PCB (in Linux, a task_struct) containing:
- process ID (PID), parent PID, user ID;
- state (ready, running, blocked…);
- saved CPU context: program counter, registers, stack pointer;
- scheduling information: priority, time used, queue pointers;
- memory-management information: page tables or base/limit;
- I/O and file information: open file descriptors, current directory;
- accounting: CPU time consumed, start time.
The OS keeps PCBs in queues: the ready queue, and a wait queue for each device or event.
Process states and transitions
The five-state model:
| Transition | From → To | Cause |
|---|---|---|
| Admit | New → Ready | The OS accepts the process (memory allocated) |
| Dispatch | Ready → Running | The scheduler picks it |
| Timeout (preempt) | Running → Ready | Time slice expired or a higher-priority process arrived |
| Wait / block | Running → Blocked | It requests I/O or waits for an event |
| Wake up | Blocked → Ready | The I/O completes or the event happens |
| Exit | Running → Terminated | It finishes or is killed |
Note what is not allowed: Blocked → Running directly (a woken process must go through the ready queue and be dispatched), and Ready → Blocked (only a running process can request I/O).
When memory is full, the OS may swap out whole processes to disk: Ready → Suspended-ready and Blocked → Suspended-blocked. A suspended-blocked process whose event happens becomes suspended-ready; resume brings it back to memory (Ready). The medium-term scheduler (Chapter 4) makes these decisions.
Think of a doctor's clinic. Ready patients sit in the waiting room; the running patient is with the doctor; a blocked patient went to get an X-ray and cannot see the doctor until the result is ready; when it is, they go back to the waiting room — not straight into the doctor's office. Suspended patients were asked to wait at home because the waiting room was full.
Context switching
When the CPU moves from one process to another, the OS performs a context switch: save the running process's CPU context into its PCB, choose the next process, load its context (and switch its memory mapping), and resume it. A context switch is pure overhead — no user work happens — typically a few microseconds, plus hidden costs from cold caches and TLBs.
Time-slice efficiency. With a time slice (quantum) q and a switch cost s, the fraction of CPU time doing useful work is:
efficiency = q / (q + s)
| q | s | Efficiency |
|---|---|---|
| 100 ms | 0.1 ms | 99.90% |
| 10 ms | 0.1 ms | 99.01% |
| 1 ms | 0.1 ms | 90.91% |
| 0.1 ms | 0.1 ms | 50.00% |
But a large q hurts responsiveness: with n processes taking turns, a process may wait up to (n − 1)(q + s) before running again. For n = 20: q = 10 ms → up to 191.9 ms; q = 1 ms → up to 20.9 ms. The choice of q is a classic trade-off (Chapter 4).
Creating and ending processes: fork, exec, wait, exit
On UNIX/Linux:
fork()creates a child that is a copy of the parent. It returns twice: 0 in the child, the child's PID in the parent (−1 on failure).exec…()(e.g.,execve) replaces the current program with a new one (same PID).wait()makes the parent wait for a child to finish and collects its exit status.exit()ends the process.
pid_t pid = fork();
if (pid == 0) { // child
execlp("ls", "ls", "-l", NULL);
} else if (pid > 0) { // parent
wait(NULL); // wait for the child
printf("child done\n");
}
This is exactly how a shell runs every command you type.
Counting forks. Every existing process executes each fork() it reaches, so n consecutive forks produce 2ⁿ processes (2ⁿ − 1 new ones):
fork(); fork(); fork();
printf("hi\n"); // printed 8 times
Zombies and orphans. A child that has exited but whose parent has not yet called wait() is a zombie: it keeps its PCB entry so the parent can read its exit status (shown as Z in ps). A child whose parent exits first is an orphan; it is adopted by PID 1 (or a "subreaper"), which collects it. Many zombies usually mean a buggy parent that never calls wait().
Threads
A thread is a unit of execution within a process: it has its own program counter, registers and stack, but shares the process's code, data, heap and open files with the other threads.
| Process | Thread | |
|---|---|---|
| Memory | Separate address space | Shared with other threads of the process |
| Creation cost | High (copy/set up an address space) | Low |
| Communication | Needs OS mechanisms (pipes, sockets, shared memory) | Through shared variables (needs synchronization, Chapter 7) |
| Fault isolation | A crash affects only that process | A crash in one thread kills the whole process |
Why threads? Responsiveness (a GUI thread stays responsive while a worker computes), resource sharing, economy, and scalability on multicore CPUs.
User-level vs. kernel-level threads. User-level threads are managed by a library without the kernel knowing — fast to create and switch, but if one blocks on I/O the whole process may block, and they cannot run on several cores at once. Kernel-level threads are scheduled by the OS — they can run in parallel and block independently, at a higher switching cost. Threading models: many-to-one (many user threads on one kernel thread), one-to-one (Linux and Windows: each thread is a kernel thread) and many-to-many. Java's platform threads are one-to-one; Java 21's virtual threads multiplex many lightweight threads over a few kernel threads (a modern many-to-many design for servers with huge numbers of concurrent requests).
Thread pools. Creating a thread per request is wasteful and dangerous under load (thousands of threads exhaust memory). A thread pool keeps a fixed number of worker threads that take tasks from a queue — the standard design of web and edge servers:
ExecutorService pool = Executors.newFixedThreadPool(8);
Future<Long> part = pool.submit(() -> sum(data, 0, 1_000_000));
long result = part.get(); // waits for the task
pool.shutdown();
Choosing how many threads or workers to run, and on which cores, is itself a resource-allocation problem. Too few threads leave cores idle; too many cause contention and context-switch overhead. On heterogeneous edge servers (CPUs + GPUs), research schedulers decide which tasks go to which processing unit — the topic of Chapter 6.
In Linux, processes and threads are both "tasks" created with the same system call (clone) with different sharing flags. Tools like ps -eLf and top -H show threads; Kubernetes pods are groups of processes sharing namespaces, limited by cgroups — processes and resource management at cluster scale.
Key takeaways
- The OS manages resources that are preemptable or not, sharable or exclusive, reusable or consumable, aiming at utilization, fairness, isolation, responsiveness, energy and cost.
- A process = program in execution with text, data, heap and stack; the OS tracks it in a PCB and in queues.
- States: new, ready, running, blocked, terminated (+ suspended-ready/blocked). Blocked → Running and Ready → Blocked are not valid transitions.
- Context switch = overhead; efficiency = q / (q + s); larger q → better efficiency, worse response ((n − 1)(q + s)).
forkreturns 0 to the child and the child's PID to the parent; n forks → 2ⁿ processes; zombie = exited, not yet waited for; orphan = parent gone, adopted by PID 1.- Threads share the address space; kernel threads run in parallel; use thread pools for servers.
Ready? Close the notes and practise.
30 questions. Predict the output before you check — that is the skill the exam measures.