Last Minute Notes

Processes and Threads

Processes and threads in depth: process memory layout, the PCB, process states, context switching, fork and exec, zombies and orphans, threads vs processes, user vs kernel threads and threading models, green threads and virtual threads, and inter-process communication (pipes, shared memory, message queues, sockets, signals).

📘 Processes and Threads

A process is a program in execution. A thread is a unit of execution inside a process. This page covers how the OS represents them, how it switches between them, how they are created, and how they talk to each other.

Table of Contents

  1. Program vs Process
  2. Process Memory Layout
  3. The Process Control Block
  4. Process States
  5. Context Switching
  6. Creating Processes: fork and exec
  7. Zombies, Orphans, and Daemons
  8. Threads
  9. Threading Models
  10. Green Threads, Coroutines, and Virtual Threads
  11. Inter-Process Communication
  12. Questions

1. Program vs Process

ProgramProcess
Passive file on disk (an ELF, Mach-O, or PE executable)Active instance with state
One copyMany processes can run the same program
No resourcesOwns memory, open files, CPU state, a PID

Running python twice gives two processes from one program, each with its own memory.


2. Process Memory Layout

Each process sees its own virtual address space.

High addresses
+---------------------------+
| Kernel space              |  mapped but inaccessible from user mode
+---------------------------+
| Stack                     |  grows down; locals, return addresses, one per thread
|            |              |
|            v              |
|                           |
| Memory-mapped region      |  shared libraries, mmap'd files, thread stacks
|                           |
|            ^              |
|            |              |
| Heap                      |  grows up; malloc / new
+---------------------------+
| BSS                       |  uninitialized globals (zeroed)
| Data                      |  initialized globals and statics
| Text (code)               |  read-only, executable, shareable
+---------------------------+
Low addresses
SegmentContentsNotes
TextMachine codeRead and execute only; shared between processes running the same binary
Dataint count = 5; at global scopeLoaded from the executable
BSSstatic int buf[1024];Zero-filled at load time, takes no space in the file
HeapDynamic allocationsManaged by malloc via brk and mmap
StackCall framesDefault 8 MB for the main thread on Linux (ulimit -s); overflow causes a segfault

ASLR (address space layout randomization) places these at random offsets each run to make exploits harder.

See it live: cat /proc/<pid>/maps on Linux or vmmap <pid> on macOS.


3. The Process Control Block

The kernel tracks each process in a PCB (on Linux, struct task_struct).

FieldPurpose
PID, parent PIDIdentity and family tree
StateRunning, ready, blocked, and so on
Saved CPU contextProgram counter, stack pointer, general registers, flags
Scheduling infoPriority, nice value, time used, policy
Memory infoPage table pointer, memory map
Open filesFile descriptor table
CredentialsUID, GID, capabilities
SignalsPending and blocked signals, handlers
AccountingCPU time, start time, resource limits

On Linux, threads are also task_structs that share the memory map and file table; the kernel schedules tasks, not processes.


4. Process States

StateLinux ps code
Running or runnableR
Interruptible sleep (waiting, can be woken by signals)S
Uninterruptible sleep (usually disk or NFS I/O)D (cannot be killed until the I/O returns)
Stopped (Ctrl+Z, debugger)T
ZombieZ

Load average on Linux counts tasks in R and D, so a high load with idle CPUs often means processes stuck on I/O.


5. Context Switching

A context switch saves the state of the running task and restores another.

  1. Timer interrupt or blocking system call enters the kernel.
  2. Save registers, program counter, and stack pointer into the current task's PCB.
  3. Scheduler picks the next task.
  4. If it belongs to a different process, switch the page table (update CR3 on x86).
  5. Restore the next task's registers and return to user mode.
CostDetail
DirectA few microseconds on modern hardware
Indirect (usually bigger)Cold CPU caches, TLB flushes on process switches (mitigated with PCID / ASID tags), branch predictor state
Thread vs process switchThreads in the same process share the address space, so no page table change: cheaper

Too many switches (thousands of threads, heavy lock contention) waste CPU; check vmstat 1 (cs column) or pidstat -w.

Mode switch vs context switch: a system call switches user to kernel mode in the same task; a context switch changes which task runs.


6. Creating Processes: fork and exec

Unix separates creation into two steps.

pid_t pid = fork();              // duplicate the calling process
if (pid == 0) {
    // child: replace the program image
    execlp("ls", "ls", "-l", NULL);
    perror("exec failed");       // only reached if exec fails
    _exit(1);
} else if (pid > 0) {
    int status;
    waitpid(pid, &status, 0);    // parent waits and reaps the child
} else {
    perror("fork failed");
}
CallEffect
fork()Creates a child that is a copy of the parent; returns 0 in the child and the child's PID in the parent
exec*()Replaces the current program with a new one; same PID, open FDs kept unless close-on-exec
wait() / waitpid()Parent collects the child's exit status
exit()Terminates, flushing stdio buffers
vfork(), posix_spawn(), clone()Faster or more flexible variants; clone creates threads and containers on Linux

Copy-on-write: fork does not copy memory. Parent and child share pages marked read-only; a page is copied only when one of them writes to it. This makes fork fast, and is why Redis can snapshot a large dataset with fork (only pages modified during the snapshot get copied).

Classic puzzle:

fork(); fork(); fork();
printf("hi\n");   // prints 8 times: 2^3 processes

Why split fork and exec? The child can adjust its environment between the two (redirect file descriptors, change directory, drop privileges), which is exactly how a shell implements ls > out.txt.


7. Zombies, Orphans, and Daemons

TermWhat it isFix or role
ZombieChild exited but the parent has not called wait; only its PCB entry and exit status remainParent must wait, or handle SIGCHLD; killing the parent lets init reap it
OrphanParent exited while child still runsRe-parented to PID 1 (or a subreaper), which reaps it
DaemonBackground service without a controlling terminalManaged by systemd today

Zombies use no memory or CPU, but each holds a PID; enough of them can exhaust the PID limit. In containers, your app often runs as PID 1 and must reap zombies itself, which is why tiny init processes like tini (or docker run --init) exist.


8. Threads

A thread has its own stack, registers, and program counter, and shares everything else with other threads in the process.

Shared by threads of a processPrivate to each thread
Code, heap, globalsStack
Open files, socketsRegisters, program counter
Signal handlersThread ID
Working directory, user IDSignal mask, errno, thread-local storage
ProcessThread
MemoryIsolatedShared
Creation costHigherLower
Context switchHigher (page table change)Lower
CommunicationIPC neededShared memory, needs synchronization
FailureCrash containedOne thread crashing (segfault) kills the whole process
UseIsolation: browser tabs, worker processes, microservicesParallelism inside one program, shared state

Chrome uses processes per site for security and crash isolation; a database like PostgreSQL uses a process per connection, while MySQL uses a thread per connection.

Why use threads:

  • Use multiple cores for CPU-bound work.
  • Keep a UI or server responsive while one thread blocks on I/O.
  • Share large in-memory data without copying.

Amdahl's law: if a fraction p of work can be parallelized, the max speedup on n cores is 1 / ((1 - p) + p / n). With 90 percent parallel code, even infinite cores cap at 10x.


9. Threading Models

ModelMappingProsCons
Many-to-oneMany user threads on one kernel threadCheap switching in user spaceOne blocking call blocks all; no multi-core
One-to-oneEach user thread is a kernel threadTrue parallelism, simpleKernel threads cost memory (stack) and creation time
Many-to-many (M:N)Many user threads multiplexed over a pool of kernel threadsCheap and parallelComplex runtime scheduler

Linux pthreads, Windows threads, and Java platform threads use one-to-one. Go goroutines, Java virtual threads, and Erlang processes use M:N inside their runtimes.


10. Green Threads, Coroutines, and Virtual Threads

ConceptSchedulingExamples
Kernel threadPreemptive, by the OSpthreads, Java platform threads
Green thread / user threadBy a runtime, not the kernelEarly Java, Go goroutines, Java virtual threads
CoroutineCooperative, yields at explicit pointsPython async/await, Kotlin coroutines, JavaScript async functions
FiberCooperative user-level thread with its own stackWindows fibers, Ruby fibers

Go goroutines start with a small stack (a few KB) that grows as needed, and the Go scheduler multiplexes them across OS threads (GOMAXPROCS), so millions of goroutines are practical.

Java virtual threads (Java 21) let blocking-style code scale like async code: a virtual thread unmounts from its carrier platform thread when it blocks on I/O. Java 24 removed the main pinning problem with synchronized blocks.

Event loops (Node.js, nginx, Redis) use a single thread plus non-blocking I/O instead of many threads; see I/O and Linux Internals.


11. Inter-Process Communication

Processes are isolated by design, so the OS provides explicit channels.

MechanismDirectionScopeSpeedNotes
Anonymous pipeOne wayRelated processesFastKernel buffer (64 KB on Linux); writer blocks when full
Named pipe (FIFO)One wayAny process on the hostFastAppears as a file (mkfifo)
Unix domain socketTwo waySame hostVery fastCan pass file descriptors and credentials; used by Docker, PostgreSQL, systemd
TCP/UDP socketTwo wayAcross machinesSlowerUniversal
Shared memoryBothSame hostFastest (no copying)Needs synchronization (semaphores, futexes); shm_open, mmap
Message queueMessagesSame hostMediumPOSIX mq_open, System V msgget
SignalsNotification onlySame hostFastTiny payload (a number)
Memory-mapped filesBothSame hostFastShared through the page cache

Message passing is simpler and safer; shared memory is faster but you must synchronize.

Signals

SignalNumberDefaultCatchable
SIGINT2Terminate (Ctrl+C)Yes
SIGTERM15Terminate (polite stop, what kill and Kubernetes send first)Yes
SIGKILL9Terminate immediatelyNo
SIGSTOP19StopNo
SIGHUP1Terminate; often used to reload configYes
SIGSEGV11Core dump on invalid memory accessYes (rarely wise)
SIGCHLD17Ignore; child changed stateYes
SIGPIPE13Terminate; wrote to a pipe or socket with no readerYes (servers ignore it)
SIGUSR1, SIGUSR210, 12TerminateYes, app-defined

Graceful shutdown pattern: handle SIGTERM, stop accepting new work, finish in-flight work within a deadline, exit. Kubernetes waits terminationGracePeriodSeconds (30 by default) before sending SIGKILL.

Signal handlers run asynchronously and may interrupt anything, so only async-signal-safe functions may be called in them (not malloc or printf); the usual trick is to set a flag or write to a self-pipe.


12. Questions

QuestionShort answer
Process vs thread?Own address space vs shared address space with private stack and registers
What does a PCB hold?PID, state, saved registers, scheduling info, memory map, open files, credentials
Explain process states.New, ready, running, waiting, terminated, with transitions
What is a context switch and its cost?Save and restore task state; direct microseconds plus cache and TLB effects
How does fork work?Duplicates the process with copy-on-write; returns 0 to child, PID to parent
Why fork then exec?Child can set up FDs and environment before loading the new program
Zombie vs orphan?Exited but not reaped vs parent died, adopted by init
User vs kernel threads?Runtime-scheduled vs OS-scheduled; threading models 1:1, M:1, M:N
Fastest IPC?Shared memory, but needs synchronization
SIGTERM vs SIGKILL?Catchable request vs uncatchable immediate kill
Why can one thread crash a whole process?Threads share the address space; a fatal signal like SIGSEGV kills the process
How many times does fork(); fork(); print?Four processes, so four times

Next: CPU Scheduling.

On this page