THINK FIRST·CODE LATER

← Operating Systems
Chapter 3 · Week 3

Processes, Threads and Resource Management

Before You Start: What You Must Be Able to Do

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.

The Big Idea

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.

In plain words

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();
Research spotlight

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.

Industry spotlight

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)).
  • fork returns 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.