Interview section
How OS actually gets asked
Almost nobody asks "what is an operating system". They ask you to trace code, compute a number, distinguish two things that sound identical, or debug a box that's misbehaving. Four formats, and they want different preparation.
The four question formats
| Format | Looks like | What they're testing | How to prepare |
|---|---|---|---|
| 1. Distinguish | "Mutex vs semaphore?" "Paging vs segmentation?" | Whether you have precise definitions or vibes | Learn the one property that separates each pair — see below |
| 2. Numerical | "Average waiting time for SRTF." "Translate this address." "Faults under LRU with 3 frames." | Mechanical accuracy under time pressure | Work them by hand. There is no shortcut; speed comes from repetition |
| 3. Trace the code | "How many times does this print?" (nested fork) "Is this thread-safe?" | Whether you can execute code in your head | Practise fork trees and spot-the-race snippets |
| 4. Debug the box | "Load average is 40, CPU is 5% idle-ish, service is slow. Go." | Whether theory connects to production | The drill below. This is the senior/SRE format and it dominates those loops |
Answer the question, then add the consequence. "LRU evicts the least recently used page — and the cost is that it needs bookkeeping on every access, which is why real systems use Clock instead, approximating it with a single reference bit." The first clause is the textbook; the second clause is why they'd hire you.
The distinctions that get tested
For each pair, memorise the single discriminating property. That's what turns a fuzzy answer into a crisp one.
| Pair | The one difference |
|---|---|
| Process vs thread | Address space. Threads share one; processes don't. |
| Mutex vs semaphore | Ownership. Only the mutex's locker can unlock it. |
| Binary semaphore vs mutex | Same count, but no ownership ⇒ no priority inheritance. |
| Deadlock vs starvation | Deadlock: nobody can proceed. Starvation: others proceed, you don't. |
| Starvation vs livelock | Livelock threads are executing, just not progressing. |
| Internal vs external fragmentation | Internal is wasted inside an allocation; external is unusable gaps between them. |
| Paging vs segmentation | Fixed size vs variable size — which then determines which fragmentation you get. |
| Paging vs swapping | Granularity: a page vs (strictly) a whole process. |
| Page fault vs segfault | Page fault: legal address, not resident — recoverable. Segfault: illegal address — fatal. |
| Minor vs major fault | Whether it touches disk. Minor ≈ µs, major ≈ ms. |
| Preemptive vs non-preemptive | Can the OS take the CPU away from a running task? |
| Virtual vs physical address | Whether the MMU has translated it yet. |
| Hard vs soft link | Hard link shares the inode; soft link stores a path string. |
| Zombie vs orphan | Zombie: child dead, parent hasn't reaped. Orphan: parent dead, child adopted by PID 1. |
| Concurrency vs parallelism | Interleaved (one core is enough) vs simultaneous (needs multiple cores). |
| Spinlock vs mutex | Busy-wait vs sleep — i.e. burn CPU vs pay two context switches. |
| Kernel vs user thread | Whether the scheduler knows it exists. |
| Monolithic vs microkernel | Do drivers run in kernel mode (fast, fragile) or user mode (slow, restartable)? |
| VM vs container | Own kernel vs shared host kernel. |
| Consistency vs durability (fs) | Journaling gives the first; fsync gives the second. |
| RSS vs VSZ | Physically resident vs merely mapped. |
The Linux debugging drill
This is the format that separates candidates in backend, SRE and platform loops. Learn the symptom → hypothesis → command chain, not the flags.
| Symptom | Likely cause | Confirm with |
|---|---|---|
| High load average, low CPU utilisation | Tasks blocked in state D on I/O — load counts them | vmstat 1 (high b, high wa), ps -eo state,comm | grep ^D |
| 100% CPU, no throughput | Spin/lock contention, or a GC loop | top -H for the hot thread, then perf top or a thread dump |
| High CPU but all system time | Syscall storm or context-switch storm | vmstat 1 (huge cs), strace -c -p PID to count syscalls |
| Memory grows forever | Leak, or allocator retention, or page cache (which is fine) | free -h (is it buff/cache?), ps -o rss over time, /proc/PID/smaps_rollup |
| Everything slow, disk busy | Thrashing | vmstat 1 — sustained non-zero si/so |
| Process vanished, no logs | OOM killer (or a cgroup limit → exit 137) | dmesg -T | grep -i kill, journalctl -k |
| "Too many open files" | fd leak or a low ulimit | ls /proc/PID/fd | wc -l, ulimit -n, lsof -p PID |
Disk full but du shows little | Deleted file still held open by a process | lsof +L1 (link count 0), then restart the holder |
Process won't die even with -9 | Uninterruptible sleep in a driver (state D) | cat /proc/PID/stack, check the mount or device |
| Latency spikes with steady load | GC, compaction, CPU throttling by cgroup quota | cat /sys/fs/cgroup/cpu.stat — look at nr_throttled |
$ vmstat 1
r b swpd free buff cache si so in cs us sy id wa
│ │ │ │ │ │
│ └ blocked (state D) └ swap in/out │ └ I/O wait %
└ runnable ↑ nonzero = thrash
└ context switches/sec
# The four numbers to read first, in order:
# wa high → it's I/O, not CPU
# si/so nonzero → memory pressure, real thrashing
# cs enormous → too many runnable threads / lock convoy
# r >> cores → genuine CPU saturation
Question bank — 60 questions with answers
Answer out loud before opening each one.
Basics & processes
01What does an operating system actually do?
Two things. Abstraction: turning awkward hardware into clean interfaces — disks become files, network cards become sockets, physical RAM becomes a private address space. Arbitration: deciding who gets the CPU, memory and I/O, and enforcing that against programs that would rather have everything. The mechanism that makes arbitration enforceable rather than advisory is the CPU's privilege bit: user code physically cannot execute the instructions that would let it escape.
02Kernel mode vs user mode — why does the distinction exist?
Because you need some code to be able to reprogram the MMU, disable interrupts and do raw I/O, and you need almost all code not to. The CPU has a privilege level (ring 0 vs ring 3 on x86) that gates which instructions are legal. In user mode a privileged instruction traps instead of executing. The practical consequence: a bug in user code kills one process with a segfault, while a bug in kernel code panics the machine — which is the whole argument for microkernels.
03What happens when you call read()?
libc puts the syscall number and arguments in registers and executes
SYSCALL. The CPU switches to ring 0 and jumps to the kernel entry point,
which saves user registers, switches to a kernel stack, and validates every
argument — is the fd open, is the buffer a writable user address (skipping
this is a privilege escalation bug). If the data is in the page cache it's copied out
and you return immediately; otherwise the process blocks, the scheduler runs someone
else, and a disk completion interrupt eventually wakes you. Then registers are restored
and SYSRET returns to user mode.
04Why is a system call expensive, and what do you do about it?
The direct cost is modest — 50–100 ns for SYSCALL/SYSRET
on modern x86 — but the entry and exit disturb caches, the branch predictor and
speculation state, and Spectre/Meltdown mitigations roughly doubled it. The answer is
always batching: buffered I/O so one write covers many
printfs, readv/writev for scatter-gather,
epoll instead of one select per descriptor, and
io_uring to submit many operations per syscall. A design with one syscall
per small event does not scale.
05What's in a PCB?
PID and parent PID, process state, saved registers and program counter (so it can be
resumed), the page-table pointer, the open file descriptor table, signal handlers and
pending signals, working directory and umask, resource limits, accounting data (CPU
time, fault counts), and the exit status once it terminates — kept so the parent can
collect it. In Linux this is task_struct, and notably it is
per-thread, with threads sharing pointers to the common address space and fd
table.
06Name the process states and the transition people forget.
New, ready, running, waiting (blocked), terminated. The one people forget is
running → ready: preemption. The process didn't block and didn't
finish — the OS took the CPU away because its quantum expired or something higher
priority woke up. That transition is what makes a system responsive rather than fair
to whoever grabbed the CPU first. In Linux ps these show as R, S, D, Z, T
— and D (uninterruptible sleep) is worth calling out because such a process can't even
be killed.
07What does fork() return, and how does the child differ?
It returns twice: 0 in the child, the child's PID in the parent, −1 on failure. The
child gets a copy of the address space — lazily, via copy-on-write, so pages are
shared read-only until one side writes — plus copies of the file descriptors (pointing
at the same underlying files, so the file offset is shared). Differences: new PID, its
PPID is the parent, its own pending-signal set is cleared, resource usage counters
reset, and it doesn't inherit the parent's threads — only the calling thread survives
into the child, which is why fork in a multithreaded program is
hazardous.
08Zombie vs orphan, and which one is the problem?
Zombie: the child has exited but the parent hasn't
wait()ed, so the kernel keeps the PCB alive to hold the exit status. It
uses no CPU or memory beyond that entry — but it holds a PID, and accumulating them
exhausts the PID space. Orphan: the parent exited first, so the child
is re-parented to PID 1, which reaps it. Zombies are the leak; orphans are
harmless. Fix zombies by calling wait(), or setting
SIGCHLD to SIG_IGN so the kernel reaps automatically. In
containers this matters because the app is PID 1 and usually doesn't reap — hence
tini or --init.
09How many processes does this create? fork(); fork(); fork();
8 total, so 7 new ones. Each fork doubles the number
of processes: 1 → 2 → 4 → 8. In general n sequential forks give 2n
processes. The trap in the printing variant is that printf is buffered:
if stdout is a pipe or file rather than a terminal, the buffer is line-buffered vs
fully-buffered differently, and the un-flushed buffer is duplicated by
fork — so output can appear more times than you'd expect. Call
fflush before forking.
10Monolithic vs microkernel — which is better?
Neither, and the tradeoff is the answer. Monolithic runs drivers and file systems in kernel mode: no mode switches between subsystems so it's fast, but a buggy driver panics the machine. Microkernel puts them in user-space servers: a crashed driver is a restarted process, and the trusted computing base is small enough to formally verify (seL4), but every request becomes IPC and the performance cost historically killed adoption. Linux is monolithic-but-modular — loadable modules are a packaging convenience, not an isolation boundary, since a loaded module still runs in ring 0.
Threads & scheduling
11What do threads share and not share?
Shared: code, heap, globals and statics, open file
descriptors, working directory, PID, signal handlers.
Private: stack, registers including the program counter, thread ID,
signal mask, thread-local storage and errno. The shared heap is the entire
point — communication is a variable rather than IPC — and the entire danger: one
thread's out-of-bounds write corrupts another's data, and a segfault in any thread
kills all of them. That fault-isolation difference is why Chrome uses a process per
tab.
12When would you choose processes over threads?
When you need a real fault or security boundary: running untrusted or crash-prone code (browser tabs, plugin sandboxes, per-request isolation in some servers), or when you want independent failure so one crash doesn't take the service down. Also when the work is naturally independent and you'd rather avoid shared-memory bugs entirely — "don't share" is the cheapest concurrency strategy. The costs are higher memory, slower creation and switching, and needing real IPC to communicate.
13How many threads should a server use?
For CPU-bound work, roughly the core count — more just adds context switches and
cache thrashing without adding capacity. For I/O-bound work, cores × (1 + wait/compute),
so a service spending 90% of each request waiting can usefully run many times the core
count. Past a few thousand threads, thread-per-request stops working: each costs kernel
memory and a scheduler slot, and the switching dominates. At that point you move to an
event loop with epoll, or green threads. Always bound it with a pool —
unbounded thread creation under load is a self-inflicted outage.
14What actually happens in a context switch, and what does it cost?
Save the outgoing task's registers and PC into its PCB, run the scheduler, and if
it's a different process reload the page-table root (CR3), then restore
the incoming registers and return to user mode. The direct cost is 1–3 µs. The
real cost is indirect: the incoming task evicts the outgoing task's cache
lines, so when it resumes it runs cold, and rebuilding a working set can be 10–100×
the direct cost. TLB pressure and lost branch-predictor state add more. Thread-to-thread
within a process is cheaper — same page tables, no CR3 reload.
15Compute average waiting time for FCFS vs SJF on a given set.
The method, which is what they're checking: build the Gantt chart, read off
completion times, then TAT = CT − arrival and WT = TAT − burst.
For P1(0,7) P2(2,4) P3(4,1) P4(5,4): FCFS runs 7/11/12/16 giving waits 0/5/7/7,
average 4.75; non-preemptive SJF runs P1, then picks P3 over P2 at
t=7, giving waits 0/6/3/7, average 4.00; SRTF gives
3.00. Watch for idle gaps when nothing has arrived, and state your
tie-breaking convention for round robin.
16SJF gives the lowest average waiting time. Why doesn't everyone use it?
Two reasons. It needs the burst length in advance, which you never have — the best you can do is exponentially-weighted prediction from history. And it starves long jobs: a steady arrival of short jobs means a long one never runs. There's also a deeper point: average waiting time is the wrong metric for interactive systems. A user cares that their keystroke echoes in 50 ms, not that the mean across all jobs is minimal — which is why real interactive schedulers are round-robin-shaped despite worse averages.
17How does Linux CFS work?
It abandons priority queues. Each task has a virtual runtime — CPU
time consumed, divided by its weight (set from its nice value) — and CFS
keeps tasks in a red-black tree ordered by vruntime, always running the
leftmost (least-served) one. Fairness emerges from the data structure rather than from
ageing rules, and starvation is impossible: a starved task's vruntime
stops growing so it automatically becomes leftmost. Each nice step is
about 1.25× weight, so 5 levels ≈ 3× CPU share. Newer kernels use EEVDF, which adds an
explicit latency guarantee on top of the same fairness idea.
18What does load average actually measure?
On Linux, the number of tasks that are runnable plus those in
uninterruptible sleep (state D), exponentially averaged over 1, 5 and 15
minutes. The D-state inclusion is the trap: a load of 40 on an 8-core box might be 40
tasks competing for CPU, or 8 running and 32 stuck on a failing disk — completely
different problems with completely different fixes. So load average tells you something
is queued, not what. Always read it with CPU utilisation and I/O wait; vmstat's
r and b columns separate the two immediately.
Concurrency
19Show how count++ loses an update.
It compiles to three operations: load, add, store. Thread A loads 5. Before it stores, A is preempted; B loads 5, adds, stores 6. A resumes with its stale register value 5, adds, stores 6. Two increments produced one. Nothing crashes and no compiler warns — the number is just quietly wrong, and only for some interleavings, which is why it passes tests and fails under load. The fix is atomicity: a lock, or an atomic fetch-and-add.
20What three properties must a critical-section solution provide?
Mutual exclusion — at most one thread inside. Progress — if nobody's inside and someone wants in, someone gets in (no deadlock). Bounded waiting — a thread can be overtaken only a bounded number of times (no starvation). Plus an implicit fourth: no assumptions about relative thread speeds or the number of CPUs. Peterson's algorithm satisfies all three for two threads using only loads and stores, which proves it's possible in software — but it doesn't generalise cleanly and needs memory barriers on real hardware, so practice uses atomic instructions.
21Mutex vs semaphore.
The difference is ownership, not the count. A mutex is owned by the thread that locked it and only that thread may unlock it — which lets the runtime detect misuse, support recursive locking, and perform priority inheritance, temporarily boosting a low-priority holder so a waiting high-priority thread isn't blocked indefinitely. A semaphore has no owner: any thread may signal it, which is exactly why it works for signalling and counting and why it cannot offer priority inheritance. Rule: mutex for mutual exclusion, semaphore for counting resources or signalling between threads.
22Why must you wait on a condition variable inside a while loop?
Two independent reasons. Spurious wakeups: POSIX explicitly permits
cond_wait to return without a matching signal, because allowing it makes
implementations faster. Stolen wakeups: more importantly, even with a
real signal, another thread can be scheduled first and consume the item before you
re-acquire the mutex — so the condition is false again by the time you run. With
if, you'd proceed to dequeue from an empty queue. The while
re-checks and sleeps again.
23When is a spinlock better than a mutex?
When the expected wait is shorter than a context switch (~1–3 µs direct, tens of µs with cache effects), you're on a multiprocessor, and the critical section can't sleep. The multiprocessor condition matters: on a single core, spinning waits for a thread that cannot possibly run until you yield — it's pure waste and can deadlock with a non-preemptive kernel. Interrupt handlers must use spinlocks because they cannot sleep. In practice most implementations are adaptive: spin briefly, then sleep. Linux futexes do exactly this, keeping the uncontended path a pure userspace CAS with no syscall.
24Write the bounded-buffer solution.
Three primitives: empty = N, full = 0, and a mutex.
Producer: wait(empty); wait(m); insert; signal(m); signal(full);.
Consumer: wait(full); wait(m); remove; signal(m); signal(empty);. Two
things must be right. Order: acquire the counting semaphore
before the mutex — reversing them deadlocks, because a producer holding the
mutex blocks on a full buffer while the consumer that would drain it can't get the
mutex. And the counting semaphores do double duty: they enforce the bound and provide
the blocking, so there's no polling anywhere.
25Readers-writers: what's the tradeoff between the variants?
You can't have reader concurrency, no reader starvation, and no writer starvation all at once. Reader-priority (a reader joins whenever another is inside) starves writers under continuous read load. Writer-priority (a waiting writer blocks new readers) starves readers. Fair/queued uses a turnstile so requests are served FIFO and nobody starves, at some loss of read concurrency — this is what real libraries do. Worth adding: RWLocks often lose to a plain mutex in practice, because the shared reader counter is itself a contended cache line.
26How do you solve dining philosophers, and what's the transferable lesson?
Four options, each breaking a different Coffman condition: allow only N−1 at the table (semaphore of 4); global fork ordering — always pick up the lower-numbered fork first; asymmetry (odds take left first, evens right first); or acquire both forks atomically under one lock. The transferable one is global ordering, because it costs nothing at runtime and it's exactly what you do in production: if every thread acquires locks in the same total order, a cycle is impossible. Real multi-lock deadlocks are almost always fixed by documenting and enforcing an order.
27Deadlock, livelock, starvation — distinguish them.
Deadlock: a cycle of threads each waiting on the next; nobody executes and nobody ever will. Livelock: threads are executing and responding to each other, but no work completes — two people repeatedly stepping aside in a corridor. Retry-with-backoff loops cause it; randomised backoff fixes it. Starvation: the system makes progress, but one particular thread never gets scheduled or never acquires the resource. All three look like a hang from outside, and a cycle detector only finds the first.
28What is compare-and-swap and what is it for?
An atomic instruction: if the memory location holds the expected value, replace it with a new value; report whether it succeeded. It's the universal primitive — you can build locks, counters and lock-free data structures from it. The pattern is a retry loop: read the current value, compute the new one, CAS; if another thread intervened the CAS fails and you retry with fresh data, so no update is ever lost. The hardware guarantee is that no other core can observe or modify the location mid-instruction, implemented by locking the cache line.
29What's the ABA problem?
CAS checks whether a value is unchanged, not whether nothing
happened. You read pointer A; another thread pops A,
pushes B, then pushes a new node that the allocator places at
A's old address. Your CAS sees A, succeeds, and links into
memory that was freed and reused — corruption. Fixes: version-tagged pointers (compare
value and counter), hazard pointers, or epoch-based reclamation. It's
specifically a problem for pointer-based lock-free structures with memory reuse.
30Why isn't volatile enough for thread safety in C/C++?
volatile tells the compiler not to cache the value in a register and
not to elide the access — it was designed for memory-mapped hardware registers. It
provides no atomicity (a volatile int++ is still
load-add-store) and no memory barriers, so neither the compiler's nor
the CPU's reordering of surrounding accesses is constrained. Use
std::atomic with an explicit memory order instead. Note Java's
volatile is genuinely different — it does imply happens-before ordering —
which is a common source of confusion between the languages.
Deadlock
31State the four conditions and how you'd break each.
Mutual exclusion — break it with shareable resources: immutable
data, lock-free structures, per-thread copies. Often impossible.
Hold and wait — acquire everything at once, or release all before
requesting more; costs utilisation and can starve threads needing many resources.
No preemption — allow rollback with trylock and timeout;
risks livelock and needs safely-abortable work.
Circular wait — impose a total ordering on lock acquisition; costs
essentially nothing, which is why it's the practical answer. All four must hold
simultaneously, so breaking any one suffices.
32Does a cycle in the resource-allocation graph always mean deadlock?
Only when every resource type has a single instance. With multiple instances a cycle is necessary but not sufficient — another holder outside the cycle may release an instance and break it. So for single-instance resources, detection is just cycle detection; for multi-instance you need the full detection algorithm (essentially Banker's safety check run against current allocations rather than declared maximums).
33Run Banker's algorithm and explain what "safe" means.
Compute Need = Max − Allocation and Available. Then
repeatedly find a process whose Need ≤ Available, pretend it runs to
completion, and add its allocation back to Available. If you can retire
every process this way, the state is safe and you have a safe
sequence. Safe means a completion order exists; unsafe means no
guaranteed order exists, so deadlock is possible — not certain, since
processes may not request their stated maximum; deadlocked means it has
happened. Banker's refuses to enter unsafe states, which is deliberately
conservative.
34Why does nobody use Banker's algorithm?
It requires each process to declare its maximum future resource need in advance, which real programs cannot do. It assumes a fixed resource count, whereas real systems have processes and resources arriving and leaving constantly. And it's O(n²m) per request, which is far too slow for a hot path. Know it because it's examined; say why it's unused because that's what distinguishes understanding from memorisation.
35How do real operating systems handle deadlock?
They mostly don't — the "ostrich algorithm". Prevention costs utilisation, avoidance needs information nobody has, and detection would mean killing user processes, which is worse than a rare hang. So the kernel provides primitives and pushes correctness to applications. Databases make the opposite choice: they run a deadlock detector and abort a victim transaction, because they can roll back safely. Explaining why the two choices differ — recovery is free for a database and impossible for the OS — is the strong version of this answer.
36What's priority inversion, and how is it fixed?
A low-priority task holds a lock a high-priority task needs. A medium-priority task — needing nothing — preempts the low-priority holder. Now the high-priority task is effectively blocked by a medium-priority one that has no business delaying it. Fixes: priority inheritance, temporarily boosting the holder to the waiter's priority (which requires ownership, hence mutexes and not semaphores), or a priority ceiling, where any holder runs at the highest priority of anything that could take that lock. This nearly lost Mars Pathfinder in 1997; NASA enabled priority inheritance remotely.
Memory
37Internal vs external fragmentation, and how does paging relate?
Internal: memory allocated but unusable because the allocator rounded up — a 4100-byte request in 4 KB pages wastes 4092 bytes in the second page. Caused by fixed-size allocation. External: enough total free memory but split into pieces too small for any request — 100 MB free, can't allocate 60 MB. Caused by variable-size allocation. The key relationship: paging trades external for internal, and that's a good trade because internal waste is bounded (≤1 page per allocation) while external waste is unbounded and needs expensive compaction to fix.
38Translate a virtual address given a page size.
With 4 KB pages, 4 KB = 2¹², so the low 12 bits are the offset and the rest is the
page number. For 0x00004A3F in a 32-bit space: page = 0x4, offset =
0xA3F = 2623. Look up page 4 in the page table to get, say, frame 17. Physical address
= 17 × 4096 + 2623 = 72,255 = 0x00011A3F. Note the offset is
never translated — which is why page size determines the number of offset
bits, and why the low bits of the physical address match the virtual one.
39Why multi-level page tables?
A flat table for a 32-bit space with 4 KB pages needs 2²⁰ entries — at 4 bytes each that's 4 MB per process, mostly for address space nobody uses. 100 processes would spend 400 MB on page tables. A tree only allocates the branches actually in use, and real address spaces are extremely sparse, so a process touching 8 MB needs a handful of tables. x86-64 uses four levels for 48 bits of address space. The cost is four memory accesses per translation on a TLB miss — which is exactly why the TLB is load-bearing.
40What is the TLB and why is it essential rather than an optimisation?
It's a small, fully-associative hardware cache of virtual→physical translations, typically 64–1536 entries, checked in about a cycle. Without it, every memory access on x86-64 would need four page-table reads before the actual data read — five accesses instead of one. Because hit rates exceed 99% under normal locality, translation is effectively free. It's also why huge pages matter: TLB reach = entries × page size, so 1536 × 4 KB covers only 6 MB, while 1536 × 2 MB covers 3 GB — which is why databases and JVMs with large heaps enable them deliberately.
41Paging vs segmentation — why did paging win?
Segmentation divides memory into variable-size logical units (code, stack, one per
array) with a base and limit each, which makes protection and sharing natural. It lost
because variable sizes cause external fragmentation, curable only by
compaction or by refusing allocations while memory is technically free. Paging's fixed
frames make external fragmentation structurally impossible — any free frame fits any
page — at the cost of bounded internal fragmentation. The historical compromise was
segmentation with paging; in x86-64 segmentation is vestigial, surviving mainly as
FS/GS for thread-local storage.
42What happens on a page fault?
The MMU sees the present bit clear and traps into the kernel, suspending (not
aborting) the faulting instruction. The kernel checks whether the address is legal for
this process — if not, SIGSEGV. It finds a free frame, evicting a victim
if necessary and writing it out first if it's dirty. It loads the page from disk, swap,
or zeroes it for a fresh anonymous page, updates the page table and TLB, and
re-executes the faulting instruction, which now succeeds. That
resumability is why a page fault is recoverable and a segfault isn't.
43Minor vs major page fault, and why the difference is dramatic.
A minor fault is satisfied without disk — the page is already in RAM (in the page cache, shared with another process, or just needs a copy-on-write copy). Microseconds; common and harmless. A major fault must read from disk: 1–10 ms, roughly 10,000× a memory access. That ratio is why fault rates must be vanishingly small: with 100 ns memory and 8 ms faults, a fault rate of just 0.001 gives an effective access time of ~8 µs — 81× slower. For under 10% slowdown you need a rate below about one in a million.
44Trace FIFO, LRU and Optimal on a reference string.
For 7 0 1 2 0 3 0 4 2 3 0 3 2 with 3 frames: FIFO gives 15 faults, LRU
12, Optimal 9. The method matters more than the numbers — FIFO evicts by load order
regardless of use, LRU evicts by least-recent use, and OPT looks forward and evicts the
page needed furthest in the future. Always state that OPT is the unachievable benchmark
(it needs the future) and that LRU's value is how close it gets to OPT under locality.
Then add why real systems use Clock instead: exact LRU needs bookkeeping on every
single access.
45What is Belady's anomaly?
More frames causing more page faults. With FIFO and the string
1 2 3 4 1 2 5 1 2 3 4 5, 3 frames give 9 faults and 4 frames give 10. It
happens because FIFO's eviction order has no relationship to future use, so an extra
frame can change which pages happen to be resident in an unluckier way. Algorithms in
the stack class — where the pages held with N frames are always a
subset of those held with N+1 — cannot suffer it. LRU and OPT are stack algorithms;
FIFO and Clock are not.
46How does the Clock algorithm approximate LRU?
Keep frames in a circular list with a hand, and use the hardware reference bit the MMU sets on access. To evict: look at the frame under the hand. If its reference bit is 1, clear it and advance — that's the "second chance". If it's 0, evict it. So a page referenced since the hand last passed survives, and one that hasn't been is taken. It costs one bit per frame and zero work on a normal memory access, versus exact LRU's list update on every access — which is why it's what real kernels use.
47What is thrashing and how do you fix it?
The sum of the processes' working sets exceeds physical memory, so each process keeps
evicting a page it's about to need. The signature is CPU utilisation falling
while disk I/O saturates — and the trap is that a naive scheduler sees idle CPU
and admits more work, deepening it. The fix is to reduce the degree of
multiprogramming: suspend a process entirely so the rest have enough frames.
Counter-intuitive but correct — running fewer processes well beats running all of them
badly. Confirm on Linux with sustained non-zero si/so in
vmstat plus rising major faults.
48Explain copy-on-write.
fork must give the child a copy of a potentially huge address space,
and copying eagerly would be absurd — especially since the child usually
execs and discards it. So both processes map the same frames,
marked read-only. Reads are free and shared; the first write traps, the kernel copies
just that one page and marks both writable, then resumes. Cost becomes proportional to
pages modified, not pages owned. The same mechanism underlies shared libraries,
mmap, container layers and Redis's BGSAVE — and the gotcha is that a
write-heavy child can approach 2× memory as COW faults copy page after page.
Storage & I/O
49What's in an inode, and what's deliberately not?
Mode and permissions, uid/gid, size, timestamps, link count, and block pointers (12 direct, then single/double/triple indirect — or extents in modern file systems). What's not in it is the filename: names live in directories, which are files containing name→inode-number entries. That design is what makes hard links possible (two names, one inode), rename atomic (one directory-entry update), and delete-while-open safe (the link count hits zero but open handles keep the inode alive).
50Hard link vs symbolic link.
A hard link is another name for the same inode: it increments the link count, survives deletion of the original, cannot cross filesystems (inode numbers are filesystem-local), and cannot point to a directory (that would allow cycles that break tree traversal). A symlink is a small file containing a path string: it has its own inode, dangles if the target is deleted, can cross filesystems, and can point to directories. In short: hard link references the data, symlink references the name.
51Why does deleting a big log file not free disk space?
Because a running process still has it open. rm removes the directory
entry and drops the link count to zero, but the kernel keeps the inode and its blocks
alive until the last file descriptor closes. So du shows the space gone
while df still shows the disk full. Find it with lsof +L1
(files with link count 0), then either restart the holder or truncate through
/proc/PID/fd/N. This is one of the most common real-world Linux incidents
and a favourite question because it tests the inode model rather than a
definition.
52What does journaling guarantee — and what doesn't it?
It guarantees consistency: after a crash, replaying a few megabytes
of journal restores valid metadata, instead of fsck-ing the whole disk for
hours. It does not guarantee durability of your data — a
successful write() only reaches the page cache and may sit in RAM for
~30 seconds. Only fsync forces it to stable storage. In ordered
mode (the default) data is flushed before the metadata commits, so you never see an
inode pointing at stale blocks; in writeback mode you can get correct file
size with garbage contents.
53How do you write a file safely so a crash can't corrupt it?
Write to a temporary file in the same directory, fsync the file,
rename it over the target, then fsync the
directory. Rename is atomic — a single directory-entry update — so a reader
sees either the complete old file or the complete new one, never a partial write. The
directory fsync is the step people miss: without it the new directory entry itself may
not be durable and the file can disappear entirely. Also check fsync's
return value, since a writeback error may only be reported once.
54Which RAID level for a database, and why?
RAID 10. It has no parity write penalty — RAID 5 turns one logical
write into four I/Os (read old data, read old parity, write data, write parity), which
is brutal for random-write workloads like a database. RAID 10 also rebuilds fast (a
plain mirror copy) whereas rebuilding a large RAID 5 array reads every remaining disk
for hours, exactly when a second failure is most likely — and with single parity that's
total loss. The cost is 50% usable capacity. And say it unprompted: RAID is not
a backup; it replicates DROP TABLE faithfully.
55How do SSDs change the classic disk-scheduling answer?
They largely invalidate it. Elevator algorithms (SCAN/C-LOOK) exist to minimise
arm travel, and an SSD has no arm and no rotational latency, so reordering
mostly just adds latency — which is why Linux ships none/mq-deadline
for NVMe. What matters instead: SSDs can't overwrite in place (erase granularity is a
large block), so updates go to fresh pages and garbage collection later consolidates
them, causing write amplification and p99 latency spikes. Practical
consequences: keep free space so GC has room, prefer sequential writes, enable TRIM,
and remember that a full SSD is a slow SSD.
IPC & containers
56Which IPC mechanism would you choose, and why?
Pipes for a simple unidirectional byte stream between related processes. Unix domain sockets for a local bidirectional API — they skip the network stack and can pass file descriptors and credentials, which is why Docker and database sockets use them. TCP when it must cross machines. Shared memory when throughput matters, because there's no copy and no syscall per message — but it provides no synchronisation, so you supply your own semaphores and you've extended every race condition across process boundaries. Signals are notification only and carry no payload.
57Why is shared memory the fastest IPC?
Because the kernel isn't involved in each transfer. Every other mechanism copies the data into kernel space and out again — two copies plus a syscall per message. With shared memory the same physical frames are mapped into both address spaces, so a write by one process is immediately visible to the other with zero copying and zero syscalls. That's also precisely why it's the most dangerous: you get no framing, no ordering, and no mutual exclusion, so you must add your own synchronisation.
58Why can't SIGKILL be caught, and why can a process survive it?
SIGKILL (and SIGSTOP) are handled entirely by the kernel
and cannot be caught, blocked or ignored — deliberately, so there's always a way to
terminate a misbehaving process. No cleanup runs, no buffers flush. A process can still
appear to survive it if it's in uninterruptible sleep (state D),
usually stuck in a driver waiting on hardware: signals are only delivered when the
process leaves kernel mode, and it never does. The fix is the underlying device or
mount, or a reboot. Contrast SIGTERM, which is catchable — that's why you
send it first.
59What can a signal handler safely do?
Very little. A handler interrupts arbitrary code at an arbitrary instruction, so it
may only call async-signal-safe functions.
malloc and printf are not — if the interrupted code held the
allocator's lock, calling malloc in the handler deadlocks. The standard
pattern is to set a volatile sig_atomic_t flag (or write one byte to a
self-pipe / eventfd) and do the real work in the main loop, where you're back in normal
context. The self-pipe trick is how event loops integrate signals cleanly.
60What is a container, precisely?
A normal process on the host's kernel with three things applied: namespaces restricting what it can see (its own PID space, mounts, network interfaces, hostname, users, IPC objects), cgroups limiting what it can use (CPU, memory, I/O, PID count), and a union filesystem like OverlayFS giving it a root built from shared read-only image layers plus a writable top layer. There is no "container" object in the kernel. That's why it starts in milliseconds and ships megabytes — and why its isolation is weaker than a VM's, since a kernel vulnerability escapes every container on the box. For hostile multi-tenancy you'd use microVMs like Firecracker.
Every OS mechanism answers "who is blocked, and who runs instead?" Page faults, I/O, locks, syscalls, scheduling — all of them. If you can answer that question for any mechanism they name, plus state what the mechanism costs, you will pass this round even on a topic you revised badly.