In short
A process is a program in execution - the passive code on disk plus its current activity: registers, program counter, stack, heap and the operating system's bookkeeping about it. A thread is a single flow of control inside a process; threads of one process share its code, data and open files but keep their own stack and registers. Multiprogramming (many processes) and multithreading (many threads per process) are how an OS keeps the CPU and I/O devices busy at once, and are the base on which scheduling, synchronisation and memory management are built. This is examined heavily in GATE CSE, ISRO/BARC scientist papers, UGC-NET and university OS papers (fork-output counting, state diagrams, process-vs-thread comparisons), and asked in almost every software interview - TCS, Infosys, Wipro, Cognizant, Zoho, Amazon, Microsoft, Samsung R&D, Qualcomm, Nvidia and Bangalore/Pune systems teams.
Explained
Process concept, image and PCB
- Program vs process: a program is a passive file; a process is an active entity with a program counter, resources and a state. One program can run as many processes (several Chrome tabs), and one process can run several programs over its life (via
exec). - Process image (address space) in memory: text (machine code, read-only), data (initialised and uninitialised globals), heap (grows upward,
malloc/new), stack (grows downward, holds activation records: parameters, locals, return addresses). - Process Control Block (PCB) - the OS's record per process, kept in the process table:
- process identifier (PID), parent PID, process state;
- CPU context: program counter, all registers, stack pointer, status word;
- scheduling information: priority, pointers to scheduling queues, accounting (CPU time used, time limits);
- memory-management information: base/limit registers or page-table pointer;
- I/O status: list of open files (file-descriptor table), devices held, current working directory;
- credentials (user and group ids) and signal/exception handler table.
- Types: CPU-bound (long bursts of computation) vs I/O-bound (short bursts, frequent waits); foreground/interactive vs background/batch; a daemon or Windows service is a long-lived background process with no controlling terminal.
Process states, queues and context switching
- Five-state model: New (being created) → Ready (in memory, waiting for CPU) → Running (executing) → Waiting/Blocked (waiting for I/O or an event) → Terminated. Legal transitions: Ready→Running (dispatch), Running→Ready (preemption/timer interrupt), Running→Blocked (I/O request or wait), Blocked→Ready (completion interrupt). Blocked→Running is not a legal direct transition.
- Seven-state model adds Suspended-Ready and Suspended-Blocked for processes swapped out to disk (medium-term scheduler).
- Queues: a job queue (all processes), a single ready queue and one device/wait queue per device. Processes migrate between these queues over their lifetime.
- Context switch - saving the running process's context into its PCB and loading another's:
- an interrupt, trap or system call transfers control to the kernel;
- the CPU state (PC, registers, flags) is saved into the current PCB;
- the process state is updated and it is placed in the correct queue;
- the next process is selected and its PCB context restored;
- memory maps are switched (page-table/base registers), and the TLB is flushed unless its entries are tagged (ASID/PCID) - caches are not flushed but go cold;
- control returns to the new process in user mode.
- Context switching is pure overhead - no useful work is done. Typical cost is a few microseconds of direct work, but the hidden cost (cold caches and TLB misses) is often larger. Hardware support such as multiple register sets reduces it.
- Mode switch vs context switch: a system call or interrupt is only a mode switch (user mode ↔ kernel mode, same process); a context switch changes the process. Every context switch includes mode switches, but not vice versa.
- Dual-mode operation: a mode bit in the status register; privileged instructions (I/O, timer, interrupt mask, page-table load) execute only in kernel mode. User code reaches the kernel through a system call (software interrupt /
syscallinstruction) carrying a call number and arguments.
Process creation, termination and the UNIX calls
- A process creates children through a create-process call, forming a process tree (root is
init/systemd, PID 1 on Linux). - **
fork()creates a near-duplicate child: it returns 0 in the child, the child's PID in the parent, and −1 on failure. Parent and child have separate address spaces; modern kernels use copy-on-write** so pages are copied only when one of them writes. - **
exec()family** (execl,execv,execvp...) replaces the current process image with a new program - same PID, new text/data/heap/stack. On success it never returns. The standard idiom isfork()thenexec()in the child;vfork()andposix_spawn()are cheaper variants. Windows uses one call,CreateProcess(), which takes the program name directly. - **
wait()/waitpid()** blocks the parent until a child terminates and collects its exit status (the low 8 bits ofexit()). - **
exit()terminates the process, returns status to the parent, and the kernel frees its memory and closes its files.abort()** terminates abnormally;kill()requests another process's termination. - Zombie process: has terminated but its parent has not yet called
wait(); only the PCB entry with the exit status survives. Harmless singly, but many zombies exhaust PID table entries. - Orphan process: its parent died first;
init/systemdadopts it (reparenting) and reaps it. - Resource inheritance: the child inherits open file descriptors (and so the file offset), environment, working directory, user id and signal handlers - but not the parent's record locks, and its set of pending signals starts empty.
- Cascading termination: in some systems, killing a parent kills all descendants; UNIX reparents instead.
Interprocess communication (IPC)
- Separate address spaces mean processes need explicit IPC. Two paradigms:
- Shared memory - a region mapped into two or more address spaces (
shmget/shmat, POSIXshm_open+mmap). Fast (no kernel involvement after setup), but the programmer must coordinate access; the classic producer-consumer bounded buffer lives here. - Message passing -
send(message)andreceive(message)through the kernel. Slower (copying, system calls per message) but safer and works across machines.- Direct naming (process to process) vs indirect (through a mailbox/port).
- Blocking (synchronous) vs non-blocking (asynchronous) send and receive; a blocking send + blocking receive pair is a rendezvous.
- Buffering: zero capacity (sender must wait), bounded, unbounded.
- Mechanisms in practice:
- Pipe - half-duplex byte stream between related processes (
pipe(), and the shell's|); a named pipe/FIFO works between unrelated processes and survives the processes. - Message queues - System V (
msgget, selectable message types) and POSIX (mq_open, message priorities). - Sockets - endpoint pairs (IP + port), the basis of all networked IPC and of loopback IPC on one host; UNIX domain sockets are the local, faster form.
- Signals - asynchronous notification only (no data payload beyond the number).
- RPC / RMI - a procedure call marshalled by a stub into a message and executed on a remote server; the basis of microservices and gRPC.
- Pipe - half-duplex byte stream between related processes (
| IPC method | Direction | Speed | Scope |
|---|---|---|---|
| Shared memory | Any | Fastest | Same machine; needs explicit synchronisation |
| Pipe | Half-duplex | Fast | Related processes (parent-child) |
| FIFO (named pipe) | Half-duplex | Fast | Unrelated processes on one host |
| Message queue | Any | Moderate | One host; messages are discrete and can be typed/prioritised |
| Socket | Full-duplex | Moderate/slow | Same host or across the network |
| RPC | Request-reply | Slowest | Across machines, language-neutral |
Threads: motivation, structure and models
- A thread has its own thread ID, program counter, register set and stack; it shares the process's code, global/static data, heap, open files, signal handlers and user id. Its record is the TCB (thread control block).
- Benefits: responsiveness (a UI thread stays live while a worker blocks), resource sharing (no IPC setup needed), economy (creation and switching are far cheaper than for processes - no new address space, no page-table switch), scalability (threads run on different cores in parallel).
- User-level threads (ULT): managed by a library in user space; the kernel sees only one process. Creation and switching are very fast and portable, but one blocking system call blocks all threads and they cannot use multiple cores.
- Kernel-level threads (KLT): the kernel schedules each thread. Blocking affects only that thread, and true parallelism is possible, but every operation is a system call, so it is slower.
- Multithreading models:
- Many-to-one - many ULTs onto one KLT (early Green threads); simple, no parallelism.
- One-to-one - each user thread maps to a kernel thread (Linux, Windows, Solaris 9+); true parallelism, but thread count is limited by kernel resources.
- Many-to-many - m user threads multiplexed over n kernel threads (n ≤ m); flexible, complex to implement.
- Two-level - many-to-many plus the option of binding a critical user thread to its own kernel thread.
- Thread libraries: POSIX Pthreads (
pthread_create,pthread_join,pthread_exit,pthread_detach,pthread_self), Windows threads, Java (Thread,Runnable,ExecutorService; virtual threads in recent Java versions), Pythonthreading(limited by the GIL for CPU-bound work), Go goroutines (user-level, multiplexed). - Implicit threading - the programmer identifies tasks and a runtime creates the threads: thread pools (fixed set of workers consuming a task queue - avoids per-request creation cost and bounds concurrency), fork-join frameworks, OpenMP (
#pragma omp parallel for), Intel TBB, Grand Central Dispatch. - Threading issues:
- **
fork()in a multithreaded process** - POSIX duplicates only the calling thread; usepthread_atforkhandlers, and preferexecimmediately after. - Thread cancellation - asynchronous (kill at once; may leak resources or leave data inconsistent) vs deferred (target checks cancellation points - preferred).
- Signal delivery - synchronous signals go to the thread that caused them; asynchronous ones to any thread not masking them, or to a dedicated signal thread.
- Thread-local storage (TLS) - per-thread copies of data that look global (
__thread,pthread_key_create, JavaThreadLocal). - Thread-safe vs reentrant code; stack size must be set deliberately for large thread counts.
- Scheduler activations - kernel upcalls that tell a user-level scheduler a thread has blocked, used in many-to-many systems.
- **
Concurrency, parallelism and multicore
- Concurrency - more than one task makes progress in overlapping time (possible on one core by interleaving). Parallelism - more than one task executes at the same instant (needs multiple cores/processors). Concurrency is a program structure; parallelism is a hardware outcome.
- Data parallelism - the same operation on different slices of data across cores. Task parallelism - different operations on (possibly) the same data.
- Challenges in multicore programming: identifying independent tasks, load balancing, splitting data, handling data dependencies, and testing/debugging non-deterministic interleavings (race conditions, heisenbugs).
- Hardware terms: a core is a physical execution unit; simultaneous multithreading (SMT / Intel Hyper-Threading) presents two (on some designs more) hardware threads per core sharing execution units - near but not equal to two cores.
- Amdahl's law bounds speedup by the serial fraction; Gustafson's law notes that with bigger problems the parallel part grows, so scaled speedup is more optimistic.
Key points
- Process - a program in execution with its own address space, descriptors and PCB entry.
- PCB - per-process kernel record: PID, state, saved CPU context, scheduling data, memory maps, open files, credentials.
- Process image - text, data, heap (grows up), stack (grows down).
- Five states - new, ready, running, waiting, terminated; blocked goes to ready, never straight to running.
- Context switch - save-and-restore of context; pure overhead, worsened by cache and TLB cold-starts.
- Mode switch ≠ context switch - a system call changes privilege level only.
- fork() - returns 0 to child, child PID to parent, −1 on error; address spaces separated by copy-on-write.
- exec() - replaces the image, keeps the PID, and does not return on success.
- Zombie - terminated, not yet reaped by the parent; orphan - parent dead, adopted by PID 1.
- Shared memory vs message passing - fastest but needs explicit coordination, vs slower but safer and network-capable.
- Pipe - half-duplex, related processes; FIFO - named, unrelated processes; socket - full duplex, across hosts.
- Thread - own TID, PC, registers and stack; shares code, data, heap and files with its process.
- Thread benefits - responsiveness, sharing, economy, scalability.
- ULT vs KLT - fast but one block stalls all and no multicore use, vs system-call cost but true parallelism.
- Models - many-to-one, one-to-one (Linux/Windows), many-to-many, two-level.
- Thread pool - reuses a bounded set of workers over a task queue; avoids unbounded thread creation.
- Concurrency vs parallelism - interleaved progress vs simultaneous execution.
- Amdahl's law - maximum speedup is 1/(1 − P), however many cores are added.
Formulas and examples
fork() calls = 2ⁿ (children = 2ⁿ − 1). Same for for(i=0;i<n;i++) fork();CPU utilisation with degree of multiprogramming: U = 1 − pⁿ, where p = fraction of time a process spends waiting for I/O, n = processes resident in memory
Context-switch overhead = (switches per second × time per switch) ÷ 1 s, expressed as a fraction of CPU time
Amdahl's law: S(N) = 1 / [(1 − P) + P/N]; maximum speedup = 1/(1 − P). P = parallel fraction, N = processing cores
Gustafson's law (scaled): S(N) = (1 − P) + N·P
Parallel efficiency E = S(N)/N (expressed as %)
Memory for T threads ≈ T × (stack size + TCB size); process memory ≈ text + data + heap + Σ thread stacks
Effective CPU time per job = (useful work) + (switches × switch cost)
Example 1 - counting processes created by fork.
int main(void) {
for (int i = 0; i < 3; i++) fork();
printf("Hello\n");
return 0;
}
- Each
fork()doubles the number of processes: 1 → 2 → 4 → 8. - Total processes = 2³ = 8; children created = 7; "Hello" is printed 8 times.
Example 2 - short-circuit fork (a standard GATE trick).
fork() && fork();
- Parent P: first
fork()returns a positive PID (true) → it evaluates the secondfork()→ creates C2. - Child C1 (from the first fork): receives 0 (false) →
&&short-circuits → it does not call the second fork. - Processes alive = P, C1, C2 = 3 (not 4). With
fork() || fork()the answer is also 3, but the extra process is created by C1, not by P.
Example 3 - separate vs shared data.
int x = 5; /* with fork */ int x = 5; /* with threads */
pid_t p = fork(); void *f(void *a){ x += 10; return 0; }
if (p == 0) { x += 10; printf("child x=%d\n", x); } pthread_create(&t,0,f,0); pthread_join(t,0);
else { wait(NULL); printf("parent x=%d\n", x); } printf("x=%d\n", x);
- With
fork: output ischild x=15thenparent x=5- the child wrote to its own copy of the page (copy-on-write). - With threads: output is
x=15- the global is in shared data. Same arithmetic, opposite visibility: that single contrast is the whole process/thread distinction.
Example 4 - multiprogramming and utilisation. Processes spend p = 80% of their time waiting on I/O.
| n (processes in memory) | U = 1 − 0.8ⁿ |
|---|---|
| 1 | 20.0% |
| 2 | 36.0% |
| 3 | 48.8% |
| 4 | 59.0% |
| 5 | 67.2% |
| 10 | 89.3% |
- Going from 1 to 2 processes buys 16 percentage points; from 4 to 5 only 8.2. Diminishing returns, and beyond a point memory pressure and context-switch cost dominate.
Example 5 - context-switch overhead and Amdahl's law.
- A server performs 25,000 context switches per second, each costing 5 µs: lost time = 25,000 × 5 µs = 125,000 µs = 125 ms per second → 12.5% of one CPU is burnt on switching alone. Halving the switch rate with a thread pool recovers ~6%.
- A program is 90% parallelisable (P = 0.9). On N = 4 cores: S = 1/(0.1 + 0.9/4) = 1/0.325 = 3.08×, efficiency 3.08/4 = 77%. On N = 16: S = 1/(0.1 + 0.05625) = 1/0.15625 = 6.40×, efficiency 40%. Ceiling, with infinite cores: 1/0.1 = 10×.
- Thread memory: Linux's default per-thread stack is typically 8 MB (
ulimit -s= 8192 KB). 500 threads reserve 500 × 8 MB = 4 GB of virtual address space - impossible in a 32-bit process (~3 GB user space) even though resident memory is small. Setting the stack to 512 KB brings it to 250 MB.
| Attribute | Process | Thread |
|---|---|---|
| Address space | Own, isolated | Shared with peer threads |
| Creation cost | High (page tables, descriptors) | Low |
| Context switch | Expensive (memory map, TLB flush) | Cheap (registers + stack pointer) |
| Communication | IPC: pipes, shared memory, sockets | Shared variables directly |
| Fault isolation | A crash kills only that process | A crash/invalid write kills the whole process |
| Owns | Code, data, heap, files, PCB | PC, registers, stack, TLS, TCB |
| Called | Heavyweight | Lightweight |
Where it is used
- Operating systems and systems programming: Linux/Android kernel and BSP teams at Qualcomm, Samsung R&D (Bengaluru/Noida), Nvidia, Intel India, Bosch and Tata Elxsi - process/thread creation, scheduling hooks and driver threads.
- Backend and web services: Apache's process/thread models, Nginx's event loop with worker processes, Tomcat and Spring Boot thread pools, Gunicorn/uWSGI workers behind Indian scale systems (IRCTC, UPI switches, Aadhaar authentication, exchange order gateways) where pool sizing decides throughput.
- Databases and middleware: PostgreSQL's process-per-connection vs MySQL's thread-per-connection, connection pools (HikariCP), Kafka consumer threads.
- Mobile and desktop apps: Android's main (UI) thread rule with
HandlerThread/AsyncTask/coroutines and background services - a blocking call on the UI thread causes the familiar ANR dialog. - Embedded and real-time: FreeRTOS and VxWorks tasks in automotive ECUs, metro signalling and ISRO/DRDO avionics software, where tasks are threads in one address space.
- Job roles and exams: SDE and systems-software interviews, DevOps/SRE (reading
ps,top, thread dumps, zombie counts), performance engineering, and the OS section of GATE CSE, ISRO, BARC, DRDO, NIELIT and UGC-NET papers.
Interview questions
- Q: What is the difference between a program and a process? A: A program is a passive file of instructions on disk; a process is that program in execution, with its own program counter, address space, resources and PCB entry.
- Q: What does
fork()return and why two different values? A: 0 in the child, the child's PID in the parent, −1 on failure - so the single copy of code after the call can tell which process it is running in and the parent can laterwait()on that PID. - Q: What is a zombie process and how do you avoid it? A: A terminated child whose exit status the parent has not collected; avoid it by calling
wait()/waitpid(), or by handling or ignoringSIGCHLDso the kernel reaps automatically. - Q: What happens to a child when its parent dies? A: It becomes an orphan and is reparented to
init/systemd(PID 1), which reaps it on exit; it is not killed. - Q: Why is a thread called lightweight? A: Because it carries only a stack, registers and a TCB - it reuses the process's address space, open files and heap, so creation and switching skip page-table and memory-map work.
- Q: List four pieces of state a thread does not share with its peers. A: Thread ID, program counter and register set, its own stack, and thread-local storage; plus its own errno and signal mask.
- Q: If one user-level thread blocks on a read, what happens to the others? A: In a many-to-one model all of them block, because the kernel sees a single process; in a one-to-one model only that thread blocks.
- Q: Difference between a context switch and a mode switch? A: A mode switch changes privilege level within the same process (system call, interrupt); a context switch saves one process's context and restores another's, and always costs more.
- Q: Which is better for a web server - process per request or thread per request? A: Threads cost less memory and switch faster, so they scale further; processes give fault and security isolation (one crash or memory corruption cannot take down the rest). Production servers usually combine a few worker processes with a thread or event pool inside each.
- Q: Concurrency or parallelism - which does a single-core machine provide? A: Only concurrency: tasks interleave through time-slicing and appear simultaneous, but no two instructions execute at the same instant.
- Q: With 95% of code parallelisable, what is the best possible speedup? A: 1/(1 − 0.95) = 20×, no matter how many cores are used - the serial 5% sets the ceiling (Amdahl's law).
- Q: What does
exec()do to the PID and the memory? A: The PID, open descriptors and most attributes stay; the text, data, heap and stack are replaced by the new program, and the call does not return if it succeeds. - Q: Why use a thread pool instead of creating a thread per task? A: It amortises creation cost, bounds the number of concurrent threads (so memory and switching stay predictable), and lets tasks queue rather than thrash the scheduler.
Common mistakes
- Treating
fork()inside a conditional, loop or short-circuit expression as if it always doubled the count - the answer is 2ⁿ only for unconditional, sequential calls. - Assuming the parent's variables change when the child modifies them, or that
exec()returns on success. - Believing copy-on-write means the memory is shared - it is a lazy copy; after a write, the pages are independent.
- Drawing a Blocked→Running arrow in the state diagram, or forgetting the Running→Ready (preemption) transition.
- Saying threads share "everything" - stack, registers and thread-local storage are private, and that is where most thread bugs come from.
- Spawning one thread per request with default 8 MB stacks and then wondering why the process runs out of virtual memory or spends 10%+ of CPU switching.
- Confusing cores with threads: 4 cores with SMT report 8 logical CPUs but do not give 8× the throughput.
- Reporting output order of parent and child as deterministic - without
wait()or a join, interleaving is up to the scheduler, andprintfbuffering can duplicate buffered text across a fork.
Related topics
CPU scheduling · Process synchronisation and deadlocks · Memory management and virtual memory · Linux internals: processes, signals and permissions · Virtualisation and containers · I/O systems and device management