OS — Contents
Overview Why OS Separates Systems Thinkers
Processes & Threads (Q1–Q7) Process, PCB, States, Context Switch Threads, Concurrency vs Parallelism
CPU Scheduling (Q8–Q12) FCFS, SJF, RR, Priority, Starvation
Synchronization (Q13–Q18) Deadlock, Semaphore, Mutex, Producer-Consumer
Memory Management (Q19–Q25) Virtual Memory, Paging, Page Faults, Thrashing
I/O & Advanced (Q26–Q33) System Calls, IPC, File Systems, Kernel Types
Jump to: Overview Processes Threads Scheduling Sync/Deadlocks Memory I/O & Syscalls
⚙ Part 2 · Phase 5 of 6 · April 2025

Operating Systems
33 Real Interview Questions

The invisible infrastructure your code runs on. Processes, threads, deadlocks, CPU scheduling, virtual memory, semaphores, IPC, and kernel internals — every question answered with real system-level depth.

Processes & PCB Threads CPU Scheduling Deadlocks Semaphores Virtual Memory Paging Page Replacement IPC System Calls
✎ The Tech Intel ⏰ ~40 min read 📋 33 Questions · All Answered ⚙ System-Level Depth

OS questions reveal whether you think like a systems engineer or just a syntax user. When your web server slows under load, the answer might be in thread scheduling. When a process hangs, deadlock theory applies. When a database is slow, virtual memory and page faults may be the cause. OS knowledge is what makes code debuggable in production.

🔄 Processes 🤔 Threads 🕐 Scheduling 🔒 Sync 🧠 Memory 🔌 I/O & Advanced
Overview

⚙ Why OS Knowledge Separates Systems Thinkers

⚡ What OS Questions Actually Test

OS questions test whether you understand the invisible infrastructure your code runs on. Infosys interviewers use OS questions to gauge: can you reason about what happens when two threads access the same variable? Do you know why context switching is expensive? Can you diagnose a deadlock? Do you understand why 32-bit systems were limited to 4GB RAM? These reveal whether you think at the system level or only at the application level.

"An operating system has no purpose other than to run application programs. The whole reason it exists is to be convenient for programs to use and to be efficient in using the hardware."
— Abraham Silberschatz · Co-author, "Operating System Concepts" (the "dinosaur book") · Yale University
Questions 1–7

🔄 Processes, Threads & Context Switching

⚡ Why Process & Thread Questions Open Every OS Round

Process and thread concepts are the foundation of every OS question. Interviewers start here to establish baseline systems knowledge. "Process vs thread" is the single most commonly asked OS question at Infosys — fumble this and everything else is uphill. Know it cold, with memory layout, trade-offs, and a real-world example.

A process is a program in execution — the active, running instance of a program. A program is a passive set of instructions on disk; a process is that program loaded into memory with its own execution context.

Process Control Block (PCB): The OS's data structure representing a process. The OS maintains one PCB per process, used to save/restore state during context switches.

PCB FieldDescription
Process ID (PID)Unique numeric identifier for the process
Process stateCurrent state: New, Ready, Running, Waiting, Terminated
Program Counter (PC)Address of the next instruction to execute
CPU registersAll register values at last context switch (accumulator, stack pointer, etc.)
Memory management infoPage table base address, segment limits, memory bounds
I/O statusList of open files, pending I/O operations
Accounting infoCPU time used, wall time, priority, resource limits
Parent PIDPID of the parent process that created this one
💡 Interview answer: "A process is a program in execution. The PCB is the OS's bookkeeping structure — it stores everything needed to pause a process and resume it later: the program counter, registers, memory maps, open files, and scheduling info."
                 admitted
  [New] ─────────────────────────▶ [Ready]
                                      │  ▲
                          scheduled   │  │  preempted /
                          by OS       │  │  time quantum expires
                                      ▼  │
                                   [Running]
                                      │  │
              I/O request / event wait│  │ exit
                                      ▼  ▼
                                   [Waiting] ──────▶ [Terminated]
                                      │
                          I/O done /  │
                          event occurs│
                                      ▼
                                   [Ready]
StateMeaningWhat triggers transition OUT
NewProcess created but not yet admitted to ready queueOS admits it (allocates memory, creates PCB)
ReadyWaiting for CPU — has everything else it needsScheduler selects it (dispatches to CPU)
RunningCurrently executing on CPUI/O request, time quantum expires, or process exits
Waiting (Blocked)Waiting for I/O completion or eventI/O completes / event occurs → moves to Ready
TerminatedProcess finished execution. PCB held briefly for parent's wait()Parent calls wait() → PCB released
📋 A process spends most of its life bouncing between Ready and Waiting, with brief bursts in Running. I/O-bound processes (web servers, DB queries) spend more time Waiting. CPU-bound processes (ML training, video encoding) spend more time Running and Ready.
PropertyProcessThread
DefinitionIndependent program in execution; its own address spaceLightweight unit of execution within a process; shares the process's address space
MemoryOwn code, data, heap, stack segmentsShares code, data, heap with siblings; own stack + registers only
Creation costHeavy — full address space duplication (fork)Light — just allocate a new stack + TCB
CommunicationIPC needed (pipes, sockets, shared memory, signals)Direct via shared memory — fast but needs synchronization
Crash impactIsolated — one process crash doesn't affect othersOne thread crash can bring down the entire process
Context switchExpensive — must switch address space (TLB flush)Cheaper — same address space, only switch registers + stack
Typical useSeparate applications (browser + word processor)Concurrent work within one app (web server handling many requests)
-- Memory layout comparison:
Process A:                       Process B:
┌──────────────┐                ┌──────────────┐
│  Stack (A)   │                │  Stack (B)   │
├──────────────┤                ├──────────────┤
│  Heap (A)    │                │  Heap (B)    │
├──────────────┤                ├──────────────┤
│  Data (A)    │                │  Data (B)    │
├──────────────┤                ├──────────────┤
│  Code (A)    │                │  Code (B)    │
└──────────────┘                └──────────────┘

Process with 3 threads:
┌──────────────────────────────────────────┐
│  Stack T1  │  Stack T2  │  Stack T3      │  (separate per thread)
├────────────────────────────────────────  │
│              Heap (shared)               │  (all threads share)
├──────────────────────────────────────────┤
│              Data (shared)               │
├──────────────────────────────────────────┤
│              Code (shared)               │
└──────────────────────────────────────────┘
💡 Real-world: a Java web server (Tomcat) uses a thread pool — one process, many threads sharing heap memory. A browser uses processes per tab for isolation — one crashed tab cannot corrupt another. Both choices are valid; the trade-off is isolation vs efficiency.

A context switch is the OS mechanism of saving the complete execution state (context) of the currently running process/thread and loading the saved state of the next one — allowing the CPU to switch between processes/threads.

Steps during a process context switch:

  1. Timer interrupt fires (or process blocks on I/O)
  2. OS saves current process state into its PCB: program counter, all CPU registers, memory limits
  3. OS updates process state (Running → Ready or Waiting)
  4. OS scheduler selects next process to run
  5. OS loads next process's PCB: restores program counter, registers
  6. OS switches memory address space — updates page table pointer (CR3 register on x86)
  7. TLB (Translation Lookaside Buffer) flush — all cached virtual-to-physical address mappings invalidated
  8. CPU resumes execution of new process

Why it's expensive:

  • No useful work done during the switch itself — pure overhead
  • TLB flush means all subsequent memory accesses must re-walk the page table until the TLB warms up — can cause hundreds of extra memory accesses
  • Cache pollution: the new process's working set evicts the previous process's cache lines
  • Typical cost: 1–10 microseconds + cache warming time
📋 Thread context switch is cheaper than process context switch because threads share the same address space — no page table switch, no TLB flush. Only the stack pointer and registers need to be swapped.

fork(): Creates a new process (child) by duplicating the calling process (parent). The child is an almost-exact copy of the parent — same code, data, heap, open files. fork() returns 0 in the child and the child's PID in the parent. Both continue execution from the same point after the fork() call.

exec(): Replaces the current process's memory image with a new program. Does NOT create a new process — just loads and starts a different program within the existing process. The calling process's code, data, stack are replaced entirely.

// Classic shell fork+exec pattern:
pid_t pid = fork();

if (pid == 0) {
    // Child process: replace myself with "ls -la"
    execvp("ls", args);   // child's memory replaced with ls
    // Code here never runs if exec succeeds
} else {
    // Parent process: wait for child to complete
    wait(NULL);           // reap the zombie
    // Continue shell loop
}
💡 Why fork then exec instead of just "create process running X"? Unix philosophy: fork gives you a full copy of the parent's state (including open file descriptors, environment variables) which you can modify BEFORE exec. This enables powerful features like I/O redirection: close stdout, open a file as stdout, then exec — the new program writes to the file without knowing about it.

Zombie process: A process that has finished executing but still has an entry in the process table because the parent has not yet called wait() to collect its exit status. It holds no resources (memory freed) but occupies a PID and a process table slot.

// Creating a zombie:
pid_t pid = fork();
if (pid == 0) {
    exit(0);     // Child exits immediately
}
sleep(10);       // Parent sleeps without calling wait()
// During this 10 seconds: child is a ZOMBIE
// It appears in ps output as "<defunct>" or "Z" state

// Fix: parent calls wait()
waitpid(pid, &status, 0);  // zombie cleaned up, PID released

Orphan process: A process whose parent has terminated before it. The OS re-parents it to the init process (PID 1 on Linux, launchd on macOS), which periodically calls wait() to clean up orphaned processes.

// Creating an orphan:
pid_t pid = fork();
if (pid > 0) {
    exit(0);     // Parent exits immediately
}
// Child continues running: it is now an orphan
// Init (PID 1) automatically becomes its new parent
// Init will wait() for it when it eventually exits
⚠ Zombie accumulation is a real production problem. If a server creates many child processes but never calls wait(), eventually it runs out of PIDs and cannot create new processes. Always collect child exit status promptly, or use SIGCHLD handler to call wait() asynchronously.

Concurrency: Multiple tasks making progress over time — not necessarily at the same physical instant. Interleaved execution on a single CPU. The OS creates the illusion of simultaneity through rapid context switching.

Parallelism: Multiple tasks executing at literally the same physical instant — requires multiple CPU cores. True simultaneous execution.

-- Concurrency on 1 CPU (interleaved):
Time:  ──────────────────────────────────────▶
CPU:   [T1][T1][T2][T2][T1][T3][T3][T2][T1]
(T1, T2, T3 all progress, but only one runs at a time)

-- Parallelism on 4 CPUs (simultaneous):
Time:  ──────────────────────────────────────▶
CPU 1: [T1][T1][T1][T1][T1][T1][T1][T1][T1]
CPU 2: [T2][T2][T2][T2][T2][T2][T2][T2][T2]
CPU 3: [T3][T3][T3][T3][T3][T3][T3][T3][T3]
CPU 4: [T4][T4][T4][T4][T4][T4][T4][T4][T4]
(All truly run simultaneously)
DimensionConcurrencyParallelism
Hardware neededSingle CPU is sufficientRequires multiple cores
Execution modelInterleaved — time-slicedSimultaneous — literally same moment
SolvesResponsiveness — keep CPU busy, handle many tasksThroughput — compute faster by dividing work
RiskRace conditions, deadlocks from interleavingSame + data sharing across cores (cache coherence)
💡 Rob Pike (Go language creator): "Concurrency is about dealing with many things at once. Parallelism is about doing many things at once." A single-core system can be concurrent but not parallel. All parallel systems are concurrent, but not vice versa.
Questions 8–12

🕐 CPU Scheduling Algorithms

⚡ Why Scheduling Algorithms Are Classic Interview Territory

CPU scheduling algorithms are asked because they require you to reason about trade-offs: fairness vs efficiency, response time vs throughput, starvation vs performance. They also reveal whether you can analyze algorithms — not just name them. Always know: how does each work, what does it optimize, what is its weakness, and what is the time complexity of the scheduling decision itself.

FCFS

First Come First Served. Simplest. Convoy effect problem.

SJF

Shortest Job First. Optimal average wait. Requires knowing burst time.

SRTF

Shortest Remaining Time First. Preemptive SJF. Optimal but starvation risk.

Round Robin

Time quantum. Fair. Quantum size is critical design choice.

Priority

By priority value. Flexible. Can starve low-priority processes.

Multilevel Queue

Multiple queues per class. Different policy per queue.

Different scheduling algorithms optimize for different goals. Understanding these metrics is essential for evaluating which algorithm is "best" for a given workload.

MetricDefinitionFormulaWho cares
CPU Utilization% of time CPU is doing useful workBusy time / Total timeData centers, batch systems
ThroughputNumber of processes completed per unit timeProcesses / TimeBatch processing
Turnaround TimeTotal time from submission to completionCompletion − Arrival timeBatch jobs
Waiting TimeTotal time spent in the Ready queueTurnaround − Burst timeAll systems
Response TimeTime from submission to FIRST responseFirst run − Arrival timeInteractive systems, UIs
📋 There is no single "best" scheduler. Interactive systems (desktops, web servers) optimize for response time. Batch systems (payroll, backups) optimize for throughput. Real-time systems (medical devices, aircraft) optimize for deadline guarantees.

FCFS (First Come First Served): Non-preemptive. Processes executed in arrival order. Simple but suffers the convoy effect — one long job blocks all short jobs behind it.

-- Processes: P1(burst=24ms), P2(burst=3ms), P3(burst=3ms) all arrive at t=0
-- FCFS order: P1 → P2 → P3
-- Gantt: [P1: 0-24][P2: 24-27][P3: 27-30]
-- Waiting: P1=0, P2=24, P3=27 → Average = 17ms (BAD for P2, P3!)

SJF (Shortest Job First): Non-preemptive. Always run the process with the shortest CPU burst time next. Provably optimal for minimizing average waiting time. Problem: requires knowing burst time in advance (usually estimated from history).

-- Same processes with SJF: P2(3) → P3(3) → P1(24)
-- Gantt: [P2: 0-3][P3: 3-6][P1: 6-30]
-- Waiting: P1=6, P2=0, P3=3 → Average = 3ms (MUCH better!)

SRTF (Shortest Remaining Time First): Preemptive version of SJF. If a new process arrives with a shorter remaining burst than the current process, preempt and run the new one. Minimizes average waiting time even more than SJF but risks starvation of long jobs.

-- P1 arrives t=0 (burst=8), P2 arrives t=1 (burst=4), P3 arrives t=2 (burst=9), P4 arrives t=3 (burst=5)
-- t=0: P1 starts (remaining=8)
-- t=1: P2 arrives (remaining=4 < P1's remaining=7) → preempt P1, run P2
-- t=2: P3 arrives (remaining=9 > P2's remaining=3) → continue P2
-- t=3: P4 arrives (remaining=5 > P2's remaining=2) → continue P2
-- t=5: P2 done, compare P1(7), P3(9), P4(5) → run P4...
⚠ SJF/SRTF can cause starvation — if short jobs keep arriving, a long job may NEVER run. Fix: aging (gradually increase priority of waiting processes based on waiting time).

Round Robin: Each process gets a fixed time quantum (time slice). When the quantum expires, the process is preempted and moved to the back of the ready queue. Fair by design — no starvation possible.

-- P1(burst=53ms), P2(burst=17ms), P3(burst=68ms), P4(burst=24ms), quantum=20ms
-- Gantt:
-- [P1:0-20][P2:20-37][P3:37-57][P4:57-77][P1:77-97][P3:97-117][P4:117-121][P1:121-134][P3:134-154]
-- All processes get CPU turns in rotation. No one starves.

How quantum size affects performance:

Quantum SizeBehaviorResult
Very small (1ms)Context switch every 1msNearly all CPU time wasted on context switching! Terrible throughput.
Very large (∞)Never preempts — runs to completionDegenerates to FCFS. Poor response time for short jobs.
Sweet spot (10–100ms)Mostly completes jobs in one quantumGood balance: <10% time in context switching, responsive
💡 Rule of thumb: quantum should be slightly larger than the typical CPU burst of an interactive request — so most interactions complete in one quantum without preemption. Linux default: 100ms. Modern systems typically use 4–20ms for interactive responsiveness.

Each process is assigned a priority number. The scheduler always runs the highest-priority ready process. Two variants:

  • Non-preemptive: Current process runs to completion (or I/O block) before checking priority
  • Preemptive: If a higher-priority process becomes ready, immediately preempt the current process
-- Example (lower number = higher priority):
-- P1(priority=3, burst=10), P2(priority=1, burst=1), P3(priority=4, burst=2), P4(priority=5, burst=1), P5(priority=2, burst=5)
-- Non-preemptive order (arrival at t=0): P2(1) → P5(2) → P1(3) → P3(4) → P4(5)
-- P4 always runs last regardless of how long it waits — starvation if P1-P3 keep arriving!

Starvation: Low-priority processes may wait indefinitely if high-priority processes keep arriving. A process submitted at priority 10 may never run if priority 1–9 processes keep arriving.

Fix — Aging: Gradually increase the priority of processes that have been waiting for a long time.

-- Aging example: increase priority by 1 every 15 minutes of waiting
-- P4 starts at priority 5
-- After 15 min waiting: priority becomes 4
-- After 30 min waiting: priority becomes 3
-- Eventually P4 reaches priority 1 and MUST run
-- Aging guarantees eventual execution for all processes
PropertyPreemptiveNon-Preemptive
CPU controlOS can forcibly remove CPU from running processProcess keeps CPU until it voluntarily yields
Trigger for switchTimer interrupt, higher-priority arrival, I/O blockOnly when process blocks (I/O) or terminates
Response timeBetter — urgent tasks can preempt immediatelyWorse — must wait for current process to finish
Context switch overheadHigher — more frequent switchesLower — fewer switches
RiskRace conditions if shared data modified mid-operationOne CPU-bound process can monopolize CPU indefinitely
ExamplesRound Robin, Priority Preemptive, SRTFFCFS, SJF, Non-preemptive Priority
Best forInteractive systems, real-time systemsBatch processing, embedded systems
🧠 Kernel preemption: Modern OS kernels (Linux 2.6+) are themselves preemptible — a high-priority process can preempt the kernel even while it is in the middle of a system call. Older kernels were non-preemptible in kernel mode, causing latency spikes for real-time applications.
Questions 13–18

🔒 Synchronization, Deadlocks & Semaphores

⚡ Why Synchronization Is the Heart of Concurrent Programming

Synchronization problems are the root cause of some of the most catastrophic and hardest-to-reproduce bugs in production systems. Race conditions, deadlocks, and starvation have caused real financial losses, system outages, and safety failures. Interviewers test these because they reveal whether you can reason about concurrent execution — the fundamental skill of systems programming.

A race condition occurs when the correctness of a program depends on the relative timing of events, particularly when multiple threads access shared data concurrently and at least one access is a write.

// Classic race condition: two threads incrementing a shared counter
int counter = 0;

// Thread 1 and Thread 2 both execute:
void increment() {
    counter++;  // NOT atomic! This is 3 operations:
                // 1. LOAD: read counter from memory into register (counter=5)
                // 2. ADD:  increment register (register=6)
                // 3. STORE: write register back to memory (counter=6)
}

// Timeline showing the race:
// T1: LOAD counter=5
// T2: LOAD counter=5      (T2 reads BEFORE T1 stores)
// T1: ADD  register=6
// T2: ADD  register=6
// T1: STORE counter=6
// T2: STORE counter=6     (T2 OVERWRITES T1's result!)
// Expected: counter=7. Actual: counter=6. Lost update!

Critical Section: The portion of code that accesses shared resources. Must be executed atomically — only one thread at a time.

Conditions for a correct critical section solution:

  1. Mutual Exclusion: Only one process in the critical section at any time
  2. Progress: If no process is in the critical section and some want to enter, the decision cannot be postponed indefinitely
  3. Bounded Waiting: There is a limit on how many times other processes can enter the critical section before a waiting process is allowed in

A semaphore is a synchronization primitive: an integer variable with two atomic operations. Proposed by Edsger Dijkstra. Used to control access to shared resources and coordinate between processes/threads.

Two atomic operations:

  • wait() / P() / down(): Decrement value. If value becomes negative, block the calling process (add to semaphore's waiting queue).
  • signal() / V() / up(): Increment value. If value is still ≤ 0, wake one blocked process from the waiting queue.
// wait(S):
if (S.value > 0) {
    S.value--;         // can proceed immediately
} else {
    block(current_process);  // add to S.waiting_queue, block
}

// signal(S):
S.value++;
if (S.value <= 0) {
    wake_up(one_from_waiting_queue);  // unblock one waiter
}

Types:

TypeInitial ValueUsed ForExample
Binary Semaphore (Mutex)1Mutual exclusion — only 1 in critical sectionProtecting a shared counter
Counting SemaphoreN (>1)Control access to N instances of a resourceConnection pool with 10 connections (N=10)
💡 Semaphore operations MUST be atomic — the decrement+check+block sequence must happen without interruption. Implemented using hardware atomic instructions (test-and-set, compare-and-swap) or by disabling interrupts briefly in kernel mode.
PropertyMutexSemaphore
Full nameMutual Exclusion LockSemaphore (general synchronization)
OwnershipThe thread that locked it MUST unlock itNo ownership — one thread can signal what another waited on
Value rangeBinary: locked (0) or unlocked (1)Any non-negative integer (counting semaphore)
Primary useMutual exclusion: protect a critical sectionSignaling between threads; counting resources
Can be released by non-owner?No — undefined behaviorYes — any thread can signal
Priority inversion handlingYes — priority inheritance in most OSNo built-in mechanism
// MUTEX: protect a shared counter (ownership matters)
mutex.lock();        // only ONE thread can be here
counter++;           // critical section
mutex.unlock();      // MUST be the same thread that locked

// SEMAPHORE: signal between threads (no ownership)
sem_t sem;
sem_init(&sem, 0, 0);  // initial value 0

// Producer thread:
produce_item();
sem_post(&sem);    // signal: item is ready (value now 1)

// Consumer thread:
sem_wait(&sem);    // wait: blocks until value > 0, then decrements
consume_item();
// (Different threads do wait and signal — OK for semaphore, wrong for mutex)

Classic synchronization problem: producer adds to a bounded buffer; consumer removes. Constraints: producer must not write when buffer full; consumer must not read when buffer empty; mutual exclusion when accessing buffer.

// Solution using 3 semaphores:
semaphore mutex = 1;     // binary: mutual exclusion for buffer access
semaphore empty = N;     // counting: # empty slots (starts at N = buffer capacity)
semaphore full  = 0;     // counting: # filled slots (starts at 0)

// PRODUCER:
while(true) {
    item = produce_item();
    wait(empty);          // wait for an empty slot
    wait(mutex);          // lock the buffer
    add_item(buffer, item);
    signal(mutex);         // unlock the buffer
    signal(full);          // notify: one more item available
}

// CONSUMER:
while(true) {
    wait(full);            // wait for an item to be available
    wait(mutex);           // lock the buffer
    item = remove_item(buffer);
    signal(mutex);         // unlock the buffer
    signal(empty);         // notify: one more empty slot
    consume_item(item);
}
⚠ ORDER MATTERS: always wait(empty/full) BEFORE wait(mutex). If you do wait(mutex) first and then wait(empty/full), you can get a deadlock: Producer holds mutex, waits for empty; Consumer cannot enter buffer to signal empty because Producer holds mutex.

Deadlock: Two or more processes permanently blocked, each waiting for a resource held by another in the group.

Four Coffman Conditions (ALL must hold simultaneously for deadlock):

  1. Mutual Exclusion: At least one resource is non-sharable (only one process can use it at a time)
  2. Hold and Wait: A process holds at least one resource while waiting for additional resources held by others
  3. No Preemption: Resources cannot be forcibly taken from a process; must be voluntarily released
  4. Circular Wait: A circular chain of processes exists, each waiting for a resource held by the next in the chain

Breaking any ONE condition prevents deadlock. Four handling strategies:

StrategyApproachCostReal Usage
PreventionEliminate one Coffman condition permanently (e.g., require all resources upfront = no Hold and Wait)Very restrictive, low utilizationEmbedded systems, safety-critical
AvoidanceDynamically check before granting a request — Banker's Algorithm checks if granting leads to a "safe state"Requires knowing max needs upfront, overhead per requestRarely practical — theoretical
Detection + RecoveryAllow deadlocks, detect via wait-for graph cycles, kill a victim process (or rollback a transaction)Overhead of periodic detection; victim loses workDatabase systems (transaction deadlock detection)
Ignorance (Ostrich)Pretend deadlocks don't happen. Reboot if one occurs.Occasional crash, but low overheadMost desktop OS (Windows, macOS) for process deadlocks
📋 The Banker's Algorithm: maintains state for each process's maximum needs, current allocation, and remaining request. Before granting a request, checks if the resulting state is "safe" (there exists an ordering in which all processes can eventually complete). Theoretically elegant; rarely used in production because requirements are too strict.

Problem: 5 philosophers sit around a circular table. Between each pair is one chopstick (5 total). To eat, a philosopher needs BOTH the left and right chopstick. If all philosophers simultaneously pick up their left chopstick and then wait for the right one — circular wait — deadlock.

// Naive approach (DEADLOCK):
while(true) {
    think();
    pick_up(left_chopstick);   // all philosophers do this simultaneously
    pick_up(right_chopstick);  // all wait here - DEADLOCK!
    eat();
    put_down(right_chopstick);
    put_down(left_chopstick);
}

Solutions:

  1. Asymmetric: Odd philosophers pick left first, even pick right first. Breaks circular wait.
  2. At most N-1 philosophers sit simultaneously: At least one philosopher always gets both chopsticks — guarantees progress.
  3. Atomic pickup: A philosopher picks up both chopsticks in one atomic action or neither. If both not available, wait without holding any.
  4. Resource hierarchy: Number chopsticks 1–5. Always pick up lower-numbered first. Breaks circular wait.
  5. Monitor/Waiter: A central coordinator (monitor) decides who gets to eat — never lets circular wait form.
💡 The Dining Philosophers is a model for any system where multiple concurrent actors compete for multiple shared resources (network connections, database locks, file handles). The asymmetric solution maps directly to lock ordering in production code: always acquire locks in a consistent order to prevent circular wait.
Questions 19–25

🧠 Memory Management

⚡ Why Memory Management Reveals Systems Depth

Memory management questions test whether you understand the gap between what a programmer sees (a flat address space, unlimited memory) and what actually exists (physical RAM, page tables, TLBs, swap). These concepts explain why 32-bit systems were limited to 4GB, why reading from RAM is 100x faster than disk, and why a misconfigured JVM heap causes GC pauses. These are daily production concerns.

Virtual memory is an abstraction that gives each process the illusion of its own large, private, contiguous address space — regardless of the actual physical RAM size or fragmentation.

Why it exists — 4 key benefits:

  1. Programs larger than RAM: The OS can run a 16GB program on a machine with 8GB RAM by swapping less-used pages to disk. Not all pages need to be in RAM simultaneously.
  2. Process isolation: Each process has its own virtual address space. Process A at virtual address 0x1000 accesses completely different physical memory than Process B at virtual address 0x1000. No process can access another's memory (without explicit sharing).
  3. Simplified programming model: Programs can be written assuming they start at address 0 and have the entire address space available. The OS handles the physical layout transparently.
  4. Efficient memory sharing: Multiple processes can share the same physical pages (e.g., shared libraries like libc — loaded once in RAM, mapped into every process's address space).
-- Virtual address space per process (64-bit):
-- 0x0000000000000000 ... 0x00007FFFFFFFFFFF (128 TB user space)
-- The SAME virtual addresses in DIFFERENT processes map to DIFFERENT physical pages
-- Process A: virtual 0x1000 → physical 0x4A3000
-- Process B: virtual 0x1000 → physical 0x7B1000  (completely different!)
📋 Why 32-bit systems were limited to 4GB: a 32-bit address space has 2′ = 4,294,967,296 addresses. Each address is 1 byte. Total addressable memory = 4GB. 64-bit systems: 2⸼ = 16 exabytes of addressable space — effectively unlimited for any current workload.

Paging divides both virtual address space and physical memory into fixed-size blocks: pages (virtual) and frames (physical). A page table maps virtual page numbers to physical frame numbers.

-- Address translation:
-- Virtual Address = [Virtual Page Number (VPN) | Offset]
-- VPN is used to look up the page table → gives Physical Frame Number (PFN)
-- Physical Address = [PFN | Offset] (offset is unchanged)

-- Example (page size = 4KB = 2^12 bytes):
-- Virtual address: 0x12345678
-- Offset (lower 12 bits): 0x678
-- VPN   (upper bits):      0x12345
-- Page table lookup: VPN 0x12345 → PFN 0x00ABC
-- Physical address: 0x00ABC678

-- Page Table Entry (PTE) contains:
-- Physical Frame Number (PFN)
-- Present bit:    1 = page in RAM, 0 = page on disk (triggers page fault)
-- Dirty bit:      1 = page modified, must write back to disk on eviction
-- Referenced bit: 1 = page accessed recently (used by replacement algorithms)
-- Protection bits: Read / Write / Execute permissions

TLB (Translation Lookaside Buffer): Hardware cache of recent page table entries. Avoids slow page table walk on every memory access. TLB hit: single cycle translation. TLB miss: full page table walk (10–100 cycles), then cache in TLB.

💡 TLB hit rate must be very high (99%+) for paging to be efficient. Fortunately, programs exhibit spatial and temporal locality — the same pages are accessed repeatedly, so a small TLB (64–1024 entries) handles most accesses.
PropertySegmentationPaging
Division unitVariable-size logical segments (code, data, stack, heap)Fixed-size pages (typically 4KB)
Address format[Segment number, Offset][Page number, Offset]
External fragmentationYes — variable-size gaps between segmentsNo — all frames are the same size
Internal fragmentationNoYes — last page may be partially empty
Programmer visibilityVisible — programmer manages segmentsTransparent — automatic, programmer unaware
ProtectionPer-segment (code = execute-only, data = read-write)Per-page (with NX bit, read/write bits in PTE)

Why modern systems use paging (not segmentation):

  • External fragmentation is hard to manage — memory becomes Swiss cheese over time
  • Fixed-size pages make allocation trivial (any free frame fits any page)
  • Paging enables clean virtual memory: any subset of virtual pages can be in RAM

Modern systems (x86-64) use paged segmentation: hardware segments reduced to ring 0/3 privilege (kernel vs user mode). Actual memory management is entirely paging-based.

A page fault occurs when a process accesses a virtual page that is not currently in physical RAM (the present bit in the page table entry is 0).

-- Step-by-step page fault handling:
1. CPU accesses virtual address → looks up TLB → TLB miss
2. CPU walks page table → PTE found, but present bit = 0 (page not in RAM)
3. CPU raises a page fault exception → control transfers to OS page fault handler
4. OS checks: is this a valid virtual address for this process?
   If NO: segmentation fault → SIGSEGV, process killed
   If YES: continue
5. OS finds a free physical frame (or evicts one — see page replacement)
6. OS initiates disk I/O to read the required page from swap/file into the frame
7. Process is blocked during disk I/O
8. I/O completes: OS updates PTE (set present=1, PFN=new frame)
9. OS updates TLB
10. OS restarts the faulting instruction → process continues transparently

-- Cost: ~10ms for a disk page fault (vs ~100ns for RAM access)
-- 100,000x slower than a RAM access!

Types of page faults:

  • Minor (soft) fault: Page is in RAM but not in this process's page table (e.g., shared library, copy-on-write). Cheap — just update page table. No disk I/O.
  • Major (hard) fault: Page must be read from disk. Expensive — 10ms+ disk I/O.
  • Invalid fault: Access to unmapped virtual address → Segmentation Fault (SIGSEGV).

When a page fault occurs and no free frames are available, the OS must evict (replace) an existing page. Which one?

AlgorithmPolicyPerformanceImplementable?
FIFOEvict the oldest page (first loaded)Simple but poor — may evict heavily used pages. Suffers Belady's Anomaly: MORE frames can cause MORE page faults!Yes — just a queue
Optimal (OPT)Evict the page that won't be used for the longest time in the futureTheoretically best — minimum page faults. Used as a benchmark.No — requires future knowledge
LRUEvict the page Least Recently UsedNear-optimal in practice. Uses past as proxy for future (locality).Expensive — must track access time for every page
Clock (Second Chance)Pages arranged in a circle with a reference bit. Clock hand sweeps; if reference bit=1, clear it (give second chance); if 0, evict. On access, set reference bit=1.Good approximation of LRU with much lower overheadYes — used in Linux (approx. LRU)
-- LRU example: page reference string = 7,0,1,2,0,3,0,4,2,3,0,3,2 (3 frames)
-- Track which page was used LEAST recently at each step
-- Evict the LRU page when a fault occurs

-- Clock algorithm (simplified):
-- Pages in circular buffer: [2*, 7*, 1, 5] (* = reference bit set)
-- Need to evict one. Clock hand at position 0 (page 2):
-- Page 2: ref bit=1 → clear bit, advance (page 2 gets second chance)
-- Page 7: ref bit=1 → clear bit, advance
-- Page 1: ref bit=0 → EVICT page 1, load new page here

Thrashing occurs when the system spends more time handling page faults than executing actual program instructions — the OS is constantly swapping pages in and out with no useful progress.

-- Thrashing scenario:
-- 5 processes each need 4 frames to run efficiently
-- System has only 10 frames (not enough for even 3 processes properly)
-- Each process constantly needs pages not in RAM
-- One process faults → OS swaps in its page → evicts another process's page
-- That process faults → evicts the first process's page → cycle repeats
-- CPU utilization drops to near 0 (CPU always waiting for disk I/O)

Causes:

  • Too many processes competing for too few physical frames
  • Degree of multiprogramming too high for available RAM
  • Process's working set does not fit in allocated frames

Solutions:

  • Working Set Model: Track the set of pages each process actively uses (working set). Only run a process if there are enough free frames to hold its full working set. Suspend processes if necessary.
  • Reduce degree of multiprogramming: Swap out entire processes to disk — free up frames for remaining processes.
  • Local replacement: Each process has a fixed frame allocation; can only evict its own pages — prevents one process thrashing from polluting others.
  • Add more RAM: The physical fix for a RAM-starved system.
💡 Thrashing is observable: CPU utilization suddenly drops while disk I/O shoots up (the OS is spending all its time on page fault handling). In Linux: watch /proc/vmstat for pgfault and pgmajfault rates.
TypeDefinitionCaused ByFix
Internal FragmentationAllocated block is larger than what is needed. Unused bytes INSIDE the allocated block.Fixed-size allocation: requesting 100 bytes but getting a 128-byte block (the 28 extra bytes are wasted inside)Use variable-size allocation or smaller block sizes
External FragmentationEnough total free memory exists but it is scattered in non-contiguous chunks. A large request cannot be satisfied even though total free > request size.Variable-size allocation over time: allocate/free blocks of different sizes leaves holesCompaction (defragmentation), paging, buddy system
-- External fragmentation example:
-- Memory: [FREE:10KB][USED:20KB][FREE:10KB][USED:30KB][FREE:10KB]
-- Total free = 30KB. Request for 25KB CONTIGUOUS block → FAILS
-- (No single free block is 25KB, even though 30KB is free in total)

-- Paging ELIMINATES external fragmentation:
-- The 25KB request needs 7 pages (7x4KB = 28KB, 3KB internal fragmentation)
-- The 7 frames can come from ANY 7 free frames in physical RAM
-- They do NOT need to be contiguous in physical memory!
Questions 26–33

🔌 I/O, System Calls, IPC & Kernel Architecture

⚡ Why I/O and System Call Questions Round Out OS Knowledge

Every interaction between application code and the hardware goes through system calls. Every process communicating with another uses IPC. Every OS design decision about monolithic vs microkernel involves fundamental trade-offs. These concepts explain why switching from HTTP polling to WebSockets is fast, why pipes are faster than sockets for local IPC, and why Linux is a monolithic kernel but is still modular.

Modern CPUs operate in at least two privilege levels (rings):

ModePrivilegeWhat runs hereCan access
User mode (Ring 3)RestrictedApplication programs (your Java/Python code, browsers, etc.)Own process memory only; cannot execute privileged instructions; cannot directly access hardware
Kernel mode (Ring 0)UnrestrictedOS kernel, device driversAll physical memory; all hardware; all privileged CPU instructions

Why this boundary exists:

  • Stability: A buggy application cannot crash the OS or corrupt other processes' memory
  • Security: Applications cannot directly access each other's memory or hardware (keyboard, disk, network)
  • Control: The OS mediates all hardware access — can enforce policies (quotas, permissions, sandboxing)
-- How applications cross the boundary: system calls
-- Application code (user mode):
char buf[100];
read(fd, buf, 100);   // application cannot directly access disk!
                      // this triggers a system call:

-- Kernel mode execution (transparent to application):
// 1. CPU switches to kernel mode (privilege ring 0)
// 2. Kernel validates fd, permissions, buffer address
// 3. Kernel issues I/O command to disk controller
// 4. Kernel copies data from kernel buffer to user buffer
// 5. CPU switches back to user mode
// 6. read() returns to application with byte count

A system call is the mechanism through which user-space programs request services from the OS kernel. It is the only legal way for a user-mode program to perform privileged operations.

-- System call mechanism:
// 1. Application calls wrapper function (e.g., C library's read())
// 2. Wrapper loads system call number into CPU register (e.g., EAX on x86)
// 3. Wrapper executes SYSCALL instruction (or INT 0x80 on older x86)
// 4. CPU switches to kernel mode, jumps to syscall handler
// 5. Kernel identifies call by number, validates arguments
// 6. Kernel performs the operation, places return value in register
// 7. CPU switches back to user mode
// 8. Wrapper returns to application
CategorySystem CallWhat it does
File I/Oopen(), read(), write(), close()Open, read, write, and close files
Processfork(), exec(), wait(), exit()Create, replace, wait for, and terminate processes
Memorymmap(), brk()Map files into memory, extend heap (malloc uses these)
Networkingsocket(), connect(), send(), recv()Create and use network sockets
Signalskill(), signal()Send signals between processes
IPC MethodHow it worksSpeedBest for
Pipes (anonymous)Unidirectional byte stream. Created with pipe(). Parent-child only.Fast — kernel bufferls | grep txt — shell pipelines between related processes
Named Pipes (FIFOs)Like pipes but with a filesystem path. Unrelated processes can use it.FastSimple unidirectional communication between unrelated processes
Message QueuesKernel-managed queue of typed messages. Both sender and receiver can be asynchronous.MediumAsynchronous work queues, buffering bursts between producer and consumer
Shared MemoryMultiple processes map the same physical pages. Communicate by reading/writing the shared region directly.Fastest — no copying, direct memory accessHigh-throughput data transfer (video frames, sensor streams); requires external synchronization (semaphore/mutex)
SocketsBidirectional communication endpoint. Can be local (Unix domain socket) or network (TCP/UDP).Medium — Unix fast, TCP slowestNetwork communication; local RPC (gRPC uses TCP sockets)
SignalsSoftware interrupts: one process sends a signal (integer) to another. Very limited data — just the signal number.FastNotifications and control: SIGTERM (graceful shutdown), SIGKILL (force kill), SIGUSR1/SIGUSR2 (custom)
💡 Speed order: Shared Memory > Pipes/FIFOs > Message Queues > Unix Sockets > TCP Sockets. Shared memory has no data copying — just read/write memory directly. TCP involves copying: app buffer → kernel send buffer → network stack → kernel recv buffer → app buffer.

DMA (Direct Memory Access): A hardware feature that allows I/O devices (disk controllers, network cards) to transfer data directly to/from RAM without involving the CPU for each byte transferred.

-- WITHOUT DMA (Programmed I/O — CPU does everything):
for each byte to transfer:
    CPU waits for device to be ready
    CPU reads byte from device I/O port
    CPU writes byte to memory address
-- CPU is 100% occupied during transfer — cannot do anything else!

-- WITH DMA:
CPU: "DMA controller, transfer 64KB from disk to memory address 0x5000. Tell me when done."
DMA controller: *handles the entire transfer*
CPU: *free to do other work*
DMA completes: *raises interrupt* "Done!"
CPU: *handles interrupt, processes data*

Why it matters:

  • Without DMA: a 1GB file read would consume the CPU for the entire transfer duration
  • With DMA: CPU initiates the transfer and is immediately free to schedule other processes
  • Modern NVMe SSDs and 10Gbps NICs transfer gigabytes per second — DMA is essential for this throughput without starving the CPU
Kernel TypeArchitectureProsConsExamples
MonolithicAll OS services (scheduler, file system, device drivers, networking) run in kernel space (ring 0)Fast — no IPC needed between kernel components; direct function callsLarge codebase in privileged mode; one buggy driver can crash the whole OSLinux, traditional Unix
MicrokernelMinimal kernel (scheduling, basic IPC, memory management). All other services (file system, drivers) run as user-space serversMore reliable — a crashed driver doesn't crash the kernel; easier to verify formallySlower — every service interaction requires IPC (user→kernel→user); higher overheadMach, Minix, QNX, seL4
HybridMonolithic base + some microkernel principles. Performance-critical code in kernel; some services user-spaceBalance of performance and modularityComplexity of both approachesWindows NT, macOS XNU (Mach + BSD)
📋 Linux is technically monolithic but designed modularly — device drivers are loadable kernel modules (LKMs) that can be added/removed at runtime without recompiling the kernel. This gives most of the flexibility of a microkernel at monolithic performance.

A file system is the OS component that organizes and provides access to files on storage devices — managing directory hierarchies, file metadata (name, size, permissions, timestamps), and the mapping of file data to physical storage blocks.

File SystemOSKey FeatureMax File Size
ext4Linux (default)Journaling, backwards compatible with ext2/316TB
NTFSWindowsJournaling, ACLs, compression, encryption16EB
APFSmacOSCopy-on-write, snapshots, strong encryption8EB
FAT32UniversalMaximum compatibility (USB drives)4GB (huge limitation)
ZFSFreeBSD, LinuxBuilt-in checksumming (detects silent corruption), RAID, snapshots16EB
XFSLinux (RHEL default)High performance, excellent for large files and parallel I/O8EB

Journaling: Before modifying the file system, write the intended changes to a log (journal). If a crash occurs mid-operation, replay the journal on next boot to restore consistency. Without journaling: a crash mid-write can corrupt the file system structure (fsck needed, potentially data loss).

PropertyCPU-boundI/O-bound
BottleneckCPU computation speedI/O device speed (disk, network, DB)
CPU burstsLong — runs CPU for a while before blockingShort — runs briefly then waits for I/O
Time stateMostly Running or ReadyMostly Waiting (blocked on I/O)
ExamplesML training, video encoding, scientific computing, cryptographyWeb servers, database queries, file copying, anything network-dependent
Scales withMore CPU cores, faster CPUFaster disk/network, async I/O, connection pools
Typical multiplexingProcess-per-core, parallel computationMany threads/coroutines, async/await, event loops

How schedulers handle them: I/O-bound processes voluntarily release the CPU frequently (they block on I/O). Schedulers often give I/O-bound processes higher priority or shorter quantum — they do not use their full quantum anyway, and responding to their I/O completion quickly improves user-perceived responsiveness. CPU-bound processes get longer quantum to reduce context switch overhead.

💡 Most real-world web applications are I/O-bound — they spend 90% of their time waiting for database queries, external APIs, or file reads. This is why Node.js's single-threaded event loop works: the single thread is almost always waiting for I/O callbacks, not computing. CPU-bound work BLOCKS the event loop and must be offloaded to a worker thread.

Buffering: Storing data in a temporary area (buffer) in memory while it is being transferred between two devices or between a device and an application. Solves speed mismatches — producer produces faster than consumer can consume.

-- Buffering example: playing a video from a slow network
-- Network data arrives at variable rate (5 Mbps avg but bursty)
-- Video player needs smooth 4 Mbps stream
-- Buffer: accumulate data ahead of playback position
-- When network is fast: buffer fills up (building cushion)
-- When network is slow: buffer drains (player still plays smoothly)
-- "Buffering..." on screen = buffer completely drained

Spooling (Simultaneous Peripheral Operations On-Line): A special form of buffering where jobs are stored in a disk queue (spool) and processed by a slower device (like a printer) at its own pace, allowing the CPU to continue processing other jobs.

-- Print spooling example:
-- App 1: "print document" → instantly returns (job sent to spool on disk)
-- App 2: "print photo"    → instantly returns (job sent to spool on disk)
-- App 3: "print report"   → instantly returns (job sent to spool on disk)
-- Print spooler daemon:   processes jobs one by one at printer's pace
-- All apps continue without waiting for printer to finish!
📋 Spooling is the foundation of print queues, mail queues (SMTP), job schedulers (HPC cluster queues), and message queues (RabbitMQ, SQS). The pattern: fast producer puts work on a queue; slow consumer processes at its own pace; no blocking on either side.
· · ·
Summary

🗀 OS Quick-Review Cheatsheet

12 OS Rules to Know Cold
1. Process = program in execution (own address space). Thread = lightweight unit within a process (shared memory, own stack). 2. Process context switch: expensive (TLB flush, cache pollution). Thread switch: cheap (same address space). 3. Process states: New → Ready → Running → Waiting → Terminated. Know ALL valid transitions. 4. Scheduling: FCFS (convoy), SJF (optimal avg wait), RR (fair, quantum matters), Priority (aging fixes starvation). 5. Preemptive = OS can forcibly remove CPU. Non-preemptive = process yields voluntarily. 6. Race condition: shared data + concurrent access + at least one write. Fix: mutual exclusion. 7. Semaphore: P/wait (decrement) and V/signal (increment). Binary for mutex. Counting for resource pools. 8. Deadlock: all 4 Coffman conditions must hold. Break any one = no deadlock possible. 9. Virtual memory: each process gets own virtual address space. Page table maps VPN to PFN. 10. Page fault: present bit=0 → OS loads page from disk. Major fault = 100,000x slower than RAM access. 11. Thrashing: more time on page faults than execution. Fix: reduce multiprogramming, use working set model. 12. IPC speed: Shared memory > Pipes > Message queues > Unix sockets > TCP sockets.

Up Next: Phase 6 — Networks + System Design + SDLC

The final phase: Computer Networks (OSI model, TCP/UDP, HTTP/HTTPS, DNS), System Design fresher problems, and SDLC/Agile questions — all in one comprehensive phase.

Phase 6: Final Phase →