Operating Systems

Processes, scheduling, sync, deadlock, memory, files and disks — in simple words, with worked numbers for GATE-style questions. Print this page for a PDF.

Part 1 — Foundations

1. What an Operating System Really Is

In plain words

An operating system (OS) is the program that runs the computer for everyone else. It sits between your apps and the hardware.

Your app never talks to the disk, the screen or the network card directly. It asks the OS, and the OS does it safely.

Think of a hotel manager. Guests (apps) don't walk into the kitchen or the boiler room. They ask the front desk. The manager shares rooms, staff and hot water fairly, and stops one guest from barging into another's room.

The OS has two big jobs

  • Resource manager — it shares the CPU, memory, disk and devices between many programs, fairly and safely.
  • Extended machine — it hides messy hardware behind simple ideas. You say "open file notes.txt"; you never say "read sector 88123 from platter 2".

What it manages, one line each

AreaWhat the OS does
ProcessesStarts, pauses, switches and stops running programs
CPUDecides which program runs next, and for how long (scheduling)
MemoryGives each program its own space and stops them reading each other's
FilesTurns raw disk blocks into named files and folders
Devices (I/O)Talks to keyboards, disks and network cards through drivers
SecurityUsers, passwords, permissions — who may do what

Kinds of OS you may be asked to name

  • Batch — jobs are collected and run one after another, no user interaction. Old mainframes.
  • Multiprogramming — several programs sit in memory. When one waits for disk, the CPU runs another. Goal: keep the CPU busy.
  • Time-sharing (multitasking) — switch between programs so fast that every user feels the computer is theirs. Goal: quick response.
  • Real-time (RTOS) — must respond within a fixed deadline. Car airbags, pacemakers. Hard real-time: missing a deadline is a failure. Soft: missing it is just bad (video drops a frame).
  • Distributed — many machines work together and look like one system.
Multiprogramming vs multitasking is a favourite trick question. Multiprogramming switches when a program waits (aim: CPU never idle). Multitasking also switches on a timer, even if nobody is waiting (aim: everyone gets a quick turn). Multitasking is multiprogramming plus time slices.

2. User Mode, Kernel Mode & System Calls

In plain words

The kernel is the core of the OS — the part that is always in memory and has full control of the hardware.

The CPU itself has (at least) two modes. In kernel mode any instruction is allowed. In user mode the dangerous ones (talking to devices, changing memory maps) are blocked.

Your apps run in user mode. When they need something only the kernel can do, they make a system call — a formal request to the kernel.

A bank. Customers (user mode) can't walk into the vault. They fill in a slip at the counter (system call). The cashier (kernel) checks it, goes into the vault, and hands back the result.

USER MODE — restricted Browser Your app Editor mode boundary read(fd, buf, n) trap → switch to kernel return result, back to user mode KERNEL MODE — full access scheduler · memory manager · file system · drivers Hardware — CPU, RAM, disk, network card
A system call is the only door from user mode into the kernel. The CPU switches mode on the way in and switches back on the way out.

What happens during a system call

  1. Your code calls a library function, e.g. printf, which calls write().
  2. The library puts the system call number and arguments in registers.
  3. It runs a special trap instruction (syscall on x86-64). The CPU jumps into the kernel and switches to kernel mode.
  4. The kernel checks the request (is this file yours?), does the work, and puts the result in a register.
  5. The CPU switches back to user mode and your code carries on.

Common system calls by group

GroupLinux examples
Process controlfork, exec, exit, wait, kill
File handlingopen, read, write, close, lseek
Device handlingioctl, read, write
Informationgetpid, time, uname
Communicationpipe, socket, shmget, mmap
Why two modes at all? Protection. If any app could run any instruction, one buggy program could wipe the disk or read another app's passwords from memory. The mode bit means a crash in your app kills only your app, not the whole machine.

System calls are slow-ish compared to a normal function call, because of the mode switch and checks. That's why libraries buffer: printf collects text and makes one write() instead of one per character.

3. Kernel Designs

In plain words

The question is: how much of the OS lives inside the kernel?

A monolithic kernel puts almost everything inside — fast, but one bad driver can crash it all. A microkernel keeps only the bare minimum inside and runs the rest as normal programs — safer, but slower because parts must keep sending messages to each other.

Monolithic is one big open-plan office: quick to shout across, but one fire spreads everywhere. Microkernel is separate small offices that pass notes: slower, but a fire stays in one room.

MONOLITHIC MICROKERNEL HYBRID user kernel apps file system device drivers networking memory mgmt scheduler · IPC apps FS server driver · net servers IPC · scheduler basic memory small kernel, services talk by messages apps some services microkernel core + FS, drivers kept in for speed Linux, older Unix MINIX 3, QNX, seL4 Windows NT, macOS (XNU)
The shaded part runs in kernel mode. More inside means faster calls but a bigger blast radius when something breaks.
MonolithicMicrokernelHybrid
Inside the kernelNearly everythingOnly IPC, scheduling, basic memoryCore plus speed-critical services
SpeedFastest — plain function callsSlower — message passing between partsClose to monolithic
ReliabilityA driver bug can crash the OSA crashed driver can be restartedIn between
Size of trusted codeMillions of linesTiny — easier to verifyLarge

Linux is monolithic but modular. You can load and unload kernel modules (drivers) at runtime with modprobe, so you don't rebuild the kernel for a new device. They still run in kernel mode, though — a bad module can still crash it.

4. Booting Up

In plain words

Booting is how a machine goes from "power on" to "login screen". Each stage is a small program whose only job is to load the next, bigger one.

Like a relay race: firmware hands the baton to the bootloader, the bootloader to the kernel, the kernel to the first user program.

  1. Power on → firmware. The CPU starts running code stored on a chip on the motherboard: BIOS (old) or UEFI (modern).
  2. POST (Power-On Self-Test). Firmware checks RAM, CPU and devices are present and working.
  3. Find the boot device. Firmware looks at disks in a set order. BIOS reads the first sector (the MBR, 512 bytes); UEFI reads a bootloader file from a special EFI partition.
  4. Bootloader (e.g. GRUB) runs. It lets you pick an OS, then loads the kernel and a small temporary file system (initramfs) into memory.
  5. Kernel starts. It sets up memory management, detects hardware, loads drivers and mounts the real root file system.
  6. First process. The kernel starts init — today usually systemd — as process ID 1. It starts every other service: networking, logging, the login screen.
BIOS vs UEFI in one line: UEFI is the modern replacement — it supports disks over 2 TB (via GPT partitions), boots faster, and supports Secure Boot, which refuses to run a bootloader that isn't signed. Good to know, rarely asked in depth.
Part 2 — Processes & Threads

5. Programs, Processes & the PCB

In plain words

A program is a file on disk — just instructions sitting there, doing nothing.

A process is a program that is running. It has memory, open files, a current position in the code, and a state.

Open Chrome twice and you get one program, two processes.

A recipe in a book is a program. You actually cooking it — with your own pans, ingredients and "I'm on step 4" — is a process. Two people can cook the same recipe at once.

What a process looks like in memory

high address low address 0 Stack grows down ↓ free space Heap grows up ↑ Data globals & statics Text (code) read-only instructions local variables, function calls malloc / new — memory you ask for int count = 0; the compiled program
Stack and heap grow towards each other into the free gap. Deep recursion overflows the stack; forgetting to free memory slowly fills the heap.

The PCB — the process's ID card

The OS keeps one Process Control Block (PCB) for every process. It's a record of everything needed to pause the process and later resume it exactly where it left off.

FieldWhat it holds
PIDProcess ID — a unique number
StateNew, ready, running, waiting, terminated
Program counterAddress of the next instruction to run
CPU registersSaved values of the registers when it was paused
Scheduling infoPriority, time used, which queue it is in
Memory infoPointer to its page table, memory limits
Open filesList of files and sockets it has open
Parent / childrenWho created it, whom it created

In Linux the PCB is a C struct called task_struct. All of them are linked into the kernel's process table.

6. Process States

In plain words

A process doesn't run non-stop. It moves between a few states: waiting for its turn on the CPU, actually running, or stuck waiting for something slow like the disk.

A queue at a single ATM. You join (new), wait in line (ready), use the machine (running). If the machine asks for your card PIN and you go hunting for it, you step aside (waiting) and rejoin the line when ready.

New Ready Running Terminated Waiting admitted dispatch interrupt / time slice over exit needs I/O or waits for event I/O done / event happened One CPU core runs only ONE process at a time. Many can be Ready or Waiting.
Note there is no arrow from Waiting straight to Running. A process that finishes waiting must queue up in Ready again.
StateMeaning
NewBeing created — PCB set up, memory being given
ReadyHas everything it needs except the CPU. Sitting in the ready queue
RunningIts instructions are executing on a CPU right now
Waiting (Blocked)Can't continue until something happens — disk read, key press, a lock
TerminatedFinished. The OS cleans up once the parent collects its exit status

Suspended states (the 7-state model)

If RAM gets full, the OS can move a whole process out to disk — this is called swapping out. That adds two more states: Ready-Suspended and Blocked-Suspended. Same meaning as before, but the process is on disk instead of in RAM.

The three schedulers

  • Long-term scheduler (job scheduler) — decides which new jobs are let into memory. Controls the degree of multiprogramming (how many processes are in memory).
  • Short-term scheduler (CPU scheduler) — picks the next ready process to run. Runs very often, every few milliseconds, so it must be fast.
  • Medium-term scheduler — swaps processes out to disk and back in, to free memory.
CPU-bound vs I/O-bound: a CPU-bound process mostly computes (video encoding). An I/O-bound one mostly waits (a web server reading files). A good long-term scheduler lets in a mix of both, so neither the CPU nor the disks sit idle.

7. Threads

In plain words

A thread is one path of execution inside a process. A process can have several threads running at once.

Threads of the same process share the code, global data, heap and open files. Each thread has its own stack, registers and program counter.

A process is a kitchen. Threads are cooks in that kitchen. They share the fridge, the stove and the recipe book, but each cook has their own hands and knows which step they're on.

SINGLE-THREADED PROCESS code · data · heap open files registers PC stack MULTITHREADED PROCESS code · data · heap · open files SHARED by all threads registers PC stack registers PC stack registers PC stack each thread: its OWN stack, registers, program counter
Sharing memory is what makes threads cheap and fast to talk to each other — and also what makes them dangerous (see race conditions, section 15).

Process vs thread

ProcessThread
MemoryOwn separate address spaceShares the process's memory
Creation costHeavy — new memory map, PCBLight — just a stack and registers
Context switchSlower — memory map changes, TLB flushedFaster — same memory map
Talking to othersNeeds IPC (pipes, sockets…)Just read/write shared variables
If one crashesOthers are unaffectedUsually the whole process dies
ExampleEach Chrome tab is a processA game's render thread and audio thread

User-level vs kernel-level threads

  • User-level threads are managed by a library inside your program. The kernel doesn't know they exist — it sees one process. Switching is very fast (no system call). But if one thread makes a blocking system call, the whole process blocks, and they can't run on two cores at once.
  • Kernel-level threads are known to and scheduled by the OS. One can block while others run, and they can use many cores. Creating and switching them costs a bit more.

Multithreading models (how user threads map to kernel threads)

ModelHowCatch
Many-to-oneMany user threads on one kernel threadOne blocking call blocks all; no true parallelism
One-to-oneEach user thread gets its own kernel threadToo many threads strains the kernel. Used by Linux and Windows
Many-to-manyM user threads spread over N kernel threadsFlexible but complex. Go's goroutines work like this
Concurrency vs parallelism: concurrency means dealing with many tasks at once (switching between them, even on one core). Parallelism means doing many at the same instant, which needs several cores. One cook juggling three dishes is concurrent; three cooks are parallel.

8. Context Switching

In plain words

A context switch is the CPU putting one process down and picking another up.

The OS saves the current process's registers and program counter into its PCB, then loads the next process's saved values from its PCB.

Like putting a bookmark in one book before picking up another. The bookmark (PCB) lets you continue from the exact line later.

Process A running
   │  timer interrupt / system call / I/O wait
   ▼
Kernel: save A's registers + PC  → PCB(A)
        A.state = ready (or waiting)
        scheduler picks B
        load B's registers + PC  ← PCB(B)
        switch memory map to B's
   ▼
Process B running

Why it matters

  • It's pure overhead. While switching, no useful work is done. Typically a few microseconds.
  • The hidden cost is bigger: the new process finds the CPU caches and the TLB (section 23) full of the old process's data, so it runs slowly for a while.
  • Switching between threads of the same process is cheaper — the memory map stays the same.

What triggers a switch

  • A timer interrupt — the process used up its time slice.
  • The process makes a blocking system call (reads a file, waits on a lock).
  • A higher-priority process becomes ready (in preemptive scheduling).
  • The process exits.
Mode switch ≠ context switch. A system call switches user → kernel mode, but the same process is still running. A context switch changes which process runs. Every context switch involves the kernel, but not every trip into the kernel causes a context switch.

9. fork, exec, wait — Zombies & Orphans

In plain words

In Unix, new processes are made in two steps. fork() makes a near-exact copy of the current process. exec() then replaces that copy's program with a new one.

The parent can call wait() to pause until a child finishes and collect its exit code.

fork is photocopying yourself. exec is the copy then changing clothes and walking off to a different job. wait is the original waiting at the door for the copy to come home and report.

#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();              // ONE call, returns TWICE

    if (pid < 0) {
        perror("fork failed");
    } else if (pid == 0) {           // child sees 0
        execlp("ls", "ls", "-l", NULL);   // child becomes "ls"
        perror("exec failed");       // only reached if exec fails
    } else {                         // parent sees the child's PID
        int status;
        wait(&status);               // block until child exits
        printf("child done\n");
    }
}

Key facts about fork

  • It returns 0 in the child, the child's PID in the parent, and -1 on failure.
  • The child gets a copy of the parent's memory, open files and registers. After fork, changes in one don't affect the other.
  • Copying all memory would be slow, so Linux uses copy-on-write: both share the same pages until one of them writes, and only then is that page copied.

The classic counting question

fork();
fork();
fork();
printf("hi\n");

Each fork doubles the number of processes. After 3 forks: 2³ = 8 processes, so "hi" prints 8 times. Child processes created = 2ⁿ − 1 = 7. This exact question appears in GATE and campus tests.

Zombie and orphan

ZombieOrphan
WhatChild has finished, but the parent hasn't called wait() yetParent has finished, but the child is still running
Why it existsThe OS keeps its PCB entry so the parent can read the exit codeParent crashed or exited early
What happensSits in the process table as <defunct> (state Z)Adopted by init/systemd (PID 1), which waits for it
Harmful?One is harmless. Thousands fill the process table and new processes can't startUsually harmless
FixParent must call wait() or handle SIGCHLDNothing needed
You can't kill a zombie — it's already dead. kill -9 does nothing. You fix it by making the parent call wait(), or by killing the parent so init adopts and reaps the zombie.
Part 3 — CPU Scheduling

10. Scheduling Basics

In plain words

Many processes are ready to run, but a core can run only one at a time. CPU scheduling is the rule the OS uses to pick who goes next.

The part that actually hands the CPU over is the dispatcher. The time it takes is the dispatch latency.

A doctor's clinic with one doctor. Do you see patients in arrival order? Quickest cases first? Emergencies first? Ten minutes each, then back of the line? Each rule is a scheduling algorithm, and each is unfair to someone.

Preemptive vs non-preemptive

  • Non-preemptive — once a process gets the CPU, it keeps it until it finishes or waits for I/O. Simple, but one long job blocks everyone.
  • Preemptive — the OS can take the CPU away mid-run (timer, or a more important process arrives). All modern desktop and server OSes do this.

The words every numerical uses

TermMeaningFormula
Arrival time (AT)When the process enters the ready queuegiven
Burst time (BT)CPU time it needsgiven
Completion time (CT)When it finishesread from the Gantt chart
Turnaround time (TAT)Total time from arriving to finishingTAT = CT − AT
Waiting time (WT)Time spent sitting in the ready queueWT = TAT − BT
Response time (RT)Time until it first gets the CPURT = first run − AT

What a good scheduler aims for

  • Maximise: CPU utilisation (keep it busy) and throughput (jobs finished per second).
  • Minimise: turnaround time, waiting time and response time.
  • Be fair: nobody waits forever (no starvation).
These goals fight each other. Short time slices give quick response but more context switches, which lowers throughput. Interactive systems (your laptop) favour response time; batch systems (overnight payroll) favour throughput.

11. FCFS, SJF & SRTF

FCFS — First Come, First Served

In plain words

Run processes in the order they arrive, each to the end. A plain queue. Non-preemptive.

A railway ticket counter. Fair in a sense — but if the person ahead is booking tickets for 40 relatives, everyone waits.

  • Easy to write, no starvation.
  • Suffers from the convoy effect: one long CPU-bound job makes many short jobs wait behind it, so the average waiting time is high.

SJF — Shortest Job First

In plain words

When the CPU is free, pick the ready process with the smallest burst time. Non-preemptive version.

SJF gives the lowest possible average waiting time of any algorithm. That's provable and a common one-line answer.

  • Catch 1: the OS can't know the future burst time. Real systems guess it from past bursts using an average that weights recent bursts more (exponential averaging: τ(n+1) = α·t(n) + (1−α)·τ(n)).
  • Catch 2: long jobs can starve if short ones keep arriving.

SRTF — Shortest Remaining Time First

The preemptive version of SJF. When a new process arrives, compare its burst with the remaining time of the running one. If the newcomer is shorter, switch to it. Even better average waiting time than SJF, at the cost of more switches.

Tie-breaking rule for numericals: when two processes have equal burst time, pick the one that arrived first (FCFS). State your assumption in the exam — examiners accept it.

12. Priority, Round Robin & Multilevel Queues

Priority scheduling

In plain words

Each process gets a priority number. The highest priority runs first. Can be preemptive or not.

SJF is actually a special case: priority = shortest burst.

  • Problem — starvation: a low-priority process may wait forever if high-priority ones keep coming. (Legend says when MIT shut down an old IBM 7094 in 1973, they found a low-priority job submitted in 1967 still waiting.)
  • Fix — aging: slowly raise the priority of a process the longer it waits. Eventually it becomes important enough to run.
  • Watch the convention: in many textbooks and in Linux nice values, a smaller number means higher priority. The question will say — read it.

Round Robin (RR)

In plain words

Each process gets a fixed slice of time called the time quantum (say 4 ms). If it isn't finished when the slice ends, it goes to the back of the ready queue. Preemptive FCFS.

Kids sharing one swing: everyone gets 2 minutes, then back of the line. Nobody waits too long, but a lot of time goes on getting on and off.

  • Designed for time-sharing. Gives good response time; no starvation.
  • Quantum too large → it behaves like FCFS.
  • Quantum too small → most time is wasted on context switches.
  • Rule of thumb: about 80% of CPU bursts should finish within one quantum. Typical values: 10–100 ms.
  • With n processes and quantum q, no process waits more than (n − 1) × q before its next turn.

Multilevel queue

Split the ready queue into several queues by type — e.g. system, interactive, batch. Each queue has its own algorithm (RR for interactive, FCFS for batch). The queues themselves have fixed priority: batch runs only when the higher queues are empty. A process stays in its queue forever, so low queues can starve.

Multilevel feedback queue (MLFQ)

Like multilevel queue, but processes move between queues based on how they behave:

Queue 0  (highest)  RR, quantum 8 ms   ← every new process starts here
Queue 1             RR, quantum 16 ms  ← used its full 8 ms? move down
Queue 2  (lowest)   FCFS               ← still CPU-hungry? move down again

Gives up the CPU early (I/O)  → stays high (it's interactive)
Waits too long in a low queue → moved up (aging, prevents starvation)

MLFQ learns which processes are interactive without being told. It's the most general scheduler and the basis of real ones. Linux today uses CFS / EEVDF, which tracks how much CPU time each process has had and always runs the one that has had the least, weighted by priority.

All algorithms at a glance

AlgorithmPreemptive?Starvation?Good atWeakness
FCFSNoNoSimplicityConvoy effect
SJFNoYesLowest avg waiting (non-preemptive)Needs burst time in advance
SRTFYesYesLowest avg waiting overallMany switches; needs burst time
PriorityEitherYes (fix: aging)Important work firstStarvation
Round RobinYesNoResponse time, fairnessHigher avg waiting; quantum tuning
MLFQYesNo (with aging)Adapts to behaviourMany settings to tune

13. Worked Example — Gantt Charts

In plain words

A Gantt chart is a timeline showing which process ran when. Draw it first, read off completion times, then use the formulas. That's the whole method for every scheduling numerical.

Four processes. Times in milliseconds.

ProcessArrival (AT)Burst (BT)
P108
P214
P329
P435

FCFS

| P1      | P2   | P3        | P4    |
0         8      12          21      26

        CT    TAT = CT−AT    WT = TAT−BT
P1       8     8 − 0 =  8     8 − 8 =  0
P2      12    12 − 1 = 11    11 − 4 =  7
P3      21    21 − 2 = 19    19 − 9 = 10
P4      26    26 − 3 = 23    23 − 5 = 18
                  total 61         total 35
Average TAT = 61 / 4 = 15.25     Average WT = 35 / 4 = 8.75

SJF (non-preemptive)

At time 0 only P1 is here, so it runs to 8. At 8, P2 (4), P3 (9) and P4 (5) are all waiting. Shortest first: P2, then P4, then P3.

| P1      | P2   | P4    | P3        |
0         8      12      17          26

        CT    TAT    WT
P1       8     8     0
P2      12    11     7
P3      26    24    15
P4      17    14     9
Average TAT = 57 / 4 = 14.25     Average WT = 31 / 4 = 7.75

SRTF (preemptive SJF) and Round Robin (quantum = 4)

SRTF — average waiting 6.5 P1 P2 P4 P1 P3 0 1 5 10 17 26 ROUND ROBIN, q = 4 — average waiting 11.75 P1 P2 P3 P4 P1 P3 P4 P3 0 4 8 12 16 20 24 25 26 P1 is shaded in both charts — notice it gets split. SRTF wins on waiting time. RR wins on response time: every process starts within 9 ms.
Read completion times straight off the chart: in SRTF, P2 ends at 5, P4 at 10, P1 at 17, P3 at 26.

SRTF step by step. At 0, P1 starts (needs 8). At 1, P2 arrives needing 4 — less than P1's remaining 7, so P2 takes over. At 2 and 3, P3 (9) and P4 (5) arrive, but P2's remaining time is smaller, so it keeps running to 5. At 5 the remaining times are P1 = 7, P3 = 9, P4 = 5 → P4 runs to 10. Then P1 (7) to 17, then P3 (9) to 26.

SRTF    CT    TAT    WT
P1      17    17     9
P2       5     4     0
P3      26    24    15
P4      10     7     2
Average TAT = 52 / 4 = 13.00     Average WT = 26 / 4 = 6.50

Round Robin step by step. P1 runs 0–4 (4 left). By then P2, P3, P4 have arrived and joined the queue in order; P1 goes behind them. Queue: P2, P3, P4, P1. P2 runs 4–8 and finishes. P3 runs 8–12 (5 left). P4 runs 12–16 (1 left). P1 runs 16–20 and finishes. P3 runs 20–24 (1 left). P4 runs 24–25 and finishes. P3 runs 25–26 and finishes.

RR q=4  CT    TAT    WT    Response (first run − AT)
P1      20    20    12     0 − 0 = 0
P2       8     7     3     4 − 1 = 3
P3      26    24    15     8 − 2 = 6
P4      25    22    17    12 − 3 = 9
Average TAT = 73 / 4 = 18.25     Average WT = 47 / 4 = 11.75
Average response = 18 / 4 = 4.5
AlgorithmAvg turnaroundAvg waiting
FCFS15.258.75
SJF14.257.75
SRTF13.006.50 (best)
RR (q = 4)18.2511.75
Exam convention for RR: when a new process arrives at the same moment the running one's quantum ends, put the new arrival in the queue first, then the preempted process. Different orders give different answers — state which you used. Also check whether the question counts context-switch time; here it's zero.
Part 4 — Concurrency & Synchronisation

14. Inter-Process Communication (IPC)

In plain words

Processes have separate memory on purpose, so they can't just read each other's variables. IPC is the set of tools the OS gives them to share data anyway.

There are two basic styles: shared memory (both look at the same piece of RAM) and message passing (send copies of data through the kernel).

Shared memory is a whiteboard in a shared room — fast, but people must take turns writing. Message passing is sending letters through the post office — slower, but tidy, and nobody scribbles over anyone else.

MethodHow it worksUse it for
PipeOne-way byte stream between related processes (parent → child). ls | grep txt is a pipeChaining commands
Named pipe (FIFO)A pipe with a file name, so unrelated processes can use itSimple local hand-off
Message queueThe kernel keeps a list of messages; processes add and remove themStructured messages, ordering
Shared memoryThe same RAM pages are mapped into both processesFastest — big data, no copying
SignalA tiny notification with just a number, e.g. SIGINT (Ctrl+C)"Stop", "reload", "child exited"
SocketTwo-way endpoint. Works on the same machine or across the networkClient–server, anything networked
int fd[2];
pipe(fd);                 // fd[0] = read end, fd[1] = write end
if (fork() == 0) {        // child
    close(fd[1]);
    read(fd[0], buf, sizeof buf);
} else {                  // parent
    close(fd[0]);
    write(fd[1], "hello", 5);
}

Shared memory vs message passing

Shared memoryMessage passing
SpeedFastest — after setup, no kernel involvedSlower — every message goes through the kernel
SynchronisationYour problem — you need locks or semaphoresBuilt in — the kernel orders messages
Across machines?NoYes (sockets)

15. Race Conditions & the Critical Section

In plain words

A race condition happens when two threads use the same data at the same time, and the result depends on who happens to go first. Sometimes it works, sometimes it doesn't.

The critical section is the piece of code that touches the shared data. The fix is to make sure only one thread at a time can be inside it.

Two people edit the same Google Sheet cell offline. Both read 100, both add 50, both save 150. The right answer was 200. One update was simply lost.

Why count++ isn't safe

It looks like one step, but the CPU does three:

count++   is really:   1. load  count into register
                       2. add   1 to register
                       3. store register into count

count = 5. Two threads, interleaved badly:
  T1: load  → reg1 = 5
  T2: load  → reg2 = 5        ← reads before T1 has stored
  T1: add   → reg1 = 6
  T2: add   → reg2 = 6
  T1: store → count = 6
  T2: store → count = 6       ← should be 7. One increment lost.

Three rules every solution must satisfy

  1. Mutual exclusion — if one thread is inside the critical section, no other can be.
  2. Progress — if nobody is inside and some want to enter, one of them must be allowed in soon. Threads not interested can't block the decision.
  3. Bounded waiting — after a thread asks to enter, there's a limit on how many times others can go ahead of it. No one waits forever.

Every critical-section answer is checked against these three. In GATE, a lot of "which of these is correct" questions are really "which rule does this code break".

do {
    entry section      // ask permission
    critical section   // touch shared data
    exit section       // give up permission
    remainder section  // everything else
} while (true);

Peterson's solution (two threads, pure software)

bool flag[2] = {false, false};   // flag[i]: "I want to enter"
int  turn;                       // whose turn it is to go first

// Code for thread i  (the other thread is j = 1 - i)
flag[i] = true;          // I want in
turn = j;                // but you can go first
while (flag[j] && turn == j)
    ;                    // wait while you want in AND it's your turn
/* critical section */
flag[i] = false;         // I'm done

It's polite: each thread says "I want in, but after you". If both want in at once, turn can hold only one value, so exactly one gets through. It satisfies all three rules.

Peterson's is a teaching tool. On modern CPUs it can fail, because the CPU and compiler may reorder the writes to flag and turn. Real systems use hardware atomic instructions instead.

Hardware help: atomic instructions

An atomic operation happens in one unbreakable step — no other thread can see it half-done.

// test_and_set: return the old value and set it to true, in ONE step
bool lock = false;

while (test_and_set(&lock))
    ;                    // spin until the old value was false
/* critical section */
lock = false;

Others: compare-and-swap (CAS) — "if the value is still X, change it to Y" — and atomic increment. All locks are built on these.

A lock that waits in a loop like this is a spinlock. Spinning wastes CPU, so it's only good when the wait will be very short and you have several cores. This kind of waiting is called busy waiting.

16. Mutexes & Semaphores

In plain words

A mutex (mutual exclusion lock) is a simple lock with one key. Lock it before the critical section, unlock it after. Only the thread that locked it should unlock it.

A semaphore is a counter with two safe operations: wait (take one, or sleep if none are left) and signal (give one back, waking a sleeper).

A mutex is the single key to a shop's restroom. A counting semaphore is a parking lot with a "spaces free: 3" sign — cars enter while the number is above zero and wait at the gate when it hits zero.

pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&m);
count++;                       // safe now
pthread_mutex_unlock(&m);

Semaphore operations

wait(S)   also called P(S), down(S)       signal(S)  also called V(S), up(S)
{                                          {
    S = S - 1;                                 S = S + 1;
    if (S < 0)                                 if (S <= 0)
        put this thread to sleep                   wake one sleeping thread
        on S's queue;                      }
}

Both run atomically. A negative S tells you how many threads are asleep on it.

The old textbook version is simpler but busy-waits: wait(S) { while (S <= 0); S--; }. The version above sleeps instead of spinning, so it doesn't waste CPU.

Two kinds

  • Binary semaphore — value only 0 or 1. Works like a lock.
  • Counting semaphore — any value. Starts at the number of identical resources (e.g. 5 database connections). Each user does wait to take one and signal to return it.

A classic GATE-type question

A counting semaphore starts at 7. Then 20 P operations and 15 V operations are done. Final value? 7 − 20 + 15 = 2. (Each P subtracts 1, each V adds 1.)

Mutex vs semaphore

MutexSemaphore
Is a…LockCounter (signalling tool)
OwnershipYes — only the locker unlocksNo — any thread can signal
ValuesLocked / unlocked0 … N
Typical useProtect a critical sectionLimit access to N resources, or signal "data is ready"
Order matters. If T1 does wait(A); wait(B); and T2 does wait(B); wait(A);, they can each grab one and wait forever for the other — a deadlock (section 19). Always take multiple locks in the same order everywhere.

17. Monitors & Condition Variables

In plain words

Semaphores are easy to misuse — swap one wait and signal and you get a deadlock or a race that's hard to find.

A monitor is a safer, higher-level tool: an object whose methods automatically run one at a time. The language handles the locking for you.

A condition variable lets a thread inside the monitor sleep until something becomes true ("buffer not empty") and be woken when another thread changes it.

A monitor is a room with one door and a guard who lets in one person at a time. A condition variable is a bench inside where you can sit and nap until someone taps you saying "your item is ready" — and while you nap, you let others in.

Condition variable operations

  • wait(cond) — release the monitor lock and sleep. When woken, take the lock back before continuing.
  • signal(cond) — wake one sleeping thread (if none sleep, nothing happens — the signal is lost).
  • broadcast(cond) — wake all sleeping threads.
// Java: every object is a monitor
class Counter {
    private int value = 0;
    public synchronized void increment() { value++; }   // one thread at a time
}

// Waiting for a condition — always in a loop
synchronized (queue) {
    while (queue.isEmpty())    // NOT "if" — re-check after waking
        queue.wait();
    item = queue.remove();
}
Always wait inside a while, never an if. When you wake up, another thread may already have taken the item, and threads can also wake for no reason at all (a "spurious wakeup"). Re-check the condition every time.

Semaphore signal vs condition signal: a semaphore remembers — signal adds 1 even if nobody is waiting. A condition variable forgets — signal with no waiters does nothing. A favourite interview distinction.

18. Classic Synchronisation Problems

In plain words

Three famous puzzles. Each one stands for a pattern you meet in real code. Know the setup and the semaphore solution for each.

1. Producer–consumer (bounded buffer)

Producers put items into a buffer of size N; consumers take them out. Producers must wait when it's full, consumers when it's empty. This is every job queue, print queue and message queue.

semaphore mutex = 1;   // protects the buffer itself
semaphore empty = N;   // free slots
semaphore full  = 0;   // filled slots

Producer:                       Consumer:
  while (true) {                  while (true) {
    item = produce();               wait(full);    // any item?
    wait(empty);   // any space?    wait(mutex);
    wait(mutex);                    item = remove();
    insert(item);                   signal(mutex);
    signal(mutex);                  signal(empty); // one more free slot
    signal(full);  // one more item consume(item);
  }                               }
The trap: swap the producer's wait(empty) and wait(mutex). If the buffer is full, the producer grabs the mutex and then sleeps waiting for space. The consumer can't get the mutex to make space. Deadlock. Wait on the counting semaphore first, then the mutex.

2. Readers–writers

Many threads may read shared data at once, but a writer needs it all to itself. Think of a database row or a config file.

semaphore rw_mutex = 1;   // writers (and the first reader) take this
semaphore mutex    = 1;   // protects read_count
int read_count = 0;

Writer:                       Reader:
  wait(rw_mutex);               wait(mutex);
  /* write */                   read_count++;
  signal(rw_mutex);             if (read_count == 1) wait(rw_mutex);  // first reader locks writers out
                                signal(mutex);
                                /* read */
                                wait(mutex);
                                read_count--;
                                if (read_count == 0) signal(rw_mutex); // last reader lets writers in
                                signal(mutex);

This is the "readers-preference" version: as long as readers keep arriving, a writer can starve. A writers-preference version fixes that but can starve readers. Real code uses a read-write lock (pthread_rwlock_t, Java's ReentrantReadWriteLock).

3. Dining philosophers

Five philosophers sit round a table with one chopstick between each pair. To eat, a philosopher needs both neighbouring chopsticks. It models processes needing several shared resources at once.

semaphore chopstick[5] = {1,1,1,1,1};

philosopher(i):
  wait(chopstick[i]);             // left
  wait(chopstick[(i + 1) % 5]);   // right
  eat();
  signal(chopstick[i]);
  signal(chopstick[(i + 1) % 5]);
  think();

Problem: if all five pick up their left chopstick at the same moment, everyone waits forever for the right one — deadlock.

Fixes:

  • Allow only 4 philosophers at the table at once (a counting semaphore set to 4).
  • Asymmetric: odd-numbered philosophers pick left first, even-numbered pick right first. This breaks the circle.
  • Pick up both or neither — check both inside a critical section.
  • Number the chopsticks and always pick the lower number first (resource ordering).
Part 5 — Deadlock

19. Deadlock & Its Four Conditions

In plain words

A deadlock is when a group of processes are all stuck, each waiting for something another one in the group is holding. Nobody can move, ever.

Two cars meet on a narrow one-lane bridge from opposite ends. Each needs the other to reverse. Neither will. They sit there forever.

The four Coffman conditions — all four must hold

  1. Mutual exclusion — a resource can be used by only one process at a time (a printer, a lock).
  2. Hold and wait — a process holds at least one resource while waiting for another.
  3. No preemption — you can't forcibly take a resource away; the holder must give it up.
  4. Circular wait — P1 waits for P2, P2 waits for P3, … and the last waits for P1. A closed loop.

These are necessary conditions: if even one is missing, deadlock is impossible. That's the key to preventing it — break any one.

Resource allocation graph (RAG)

A picture of who holds what and who wants what. Circles are processes, squares are resource types, and dots inside a square are instances (e.g. 2 printers = 2 dots).

P1 P2 R2 R1 request: P1 wants R2 assigned: P2 holds R2 request: P2 wants R1 assigned: P1 holds R1 cycle → deadlock Arrow from process to resource = request. Arrow from resource to process = assignment.
Each process holds one resource and waits for the other's. With one instance per resource, this cycle is a guaranteed deadlock.

Reading a RAG

  • No cycle → no deadlock. Always.
  • Cycle, and every resource has one instance → deadlock. Certain.
  • Cycle, but some resources have several instances → maybe deadlock. Another process outside the cycle might release an instance and break it.

Four ways to handle deadlock

StrategyIdea
PreventionDesign the system so one of the four conditions can never hold
AvoidanceCheck each request in advance; only grant it if the system stays safe (Banker's)
Detection & recoveryLet it happen, notice it, then fix it
Ignore it"Ostrich algorithm" — assume it's rare and reboot if it happens. What Windows and Linux do for most resources

20. Prevention, Avoidance & Banker's Algorithm

Prevention — break one condition

ConditionHow to break itDownside
Mutual exclusionMake resources shareable (read-only files)Impossible for printers, locks
Hold and waitAsk for everything at the start, or release all before asking againPoor resource use; starvation
No preemptionIf you can't get what you want, release what you holdOnly works for things you can save and restore (CPU, memory)
Circular waitNumber every resource; always request in increasing orderNeed to know the order — but this is the most practical one

Lock ordering in code is exactly the circular-wait fix: if every thread takes lockA before lockB, a loop can't form.

Avoidance — the safe-state idea

In plain words

Each process declares up front the maximum it might ever need. Before granting any request, the OS asks: "If I say yes, is there still some order in which every process can get its maximum and finish?"

If yes, the state is safe and the request is granted. If no, the process waits — even though the resource is free right now.

A bank manager with limited cash. Every customer has a credit limit. She only lends money if, afterwards, she could still satisfy every customer's full limit in some order as they repay. That's literally why it's called the Banker's algorithm.

  • Safe state → no deadlock is possible.
  • Unsafe state → deadlock may happen (not must). Unsafe ≠ deadlocked.

Banker's algorithm — the data

Available[j]     = free instances of resource j right now
Max[i][j]        = most of resource j process i will ever need
Allocation[i][j] = what process i holds now
Need[i][j]       = Max[i][j] − Allocation[i][j]     ← compute this first

Worked example

5 processes, 3 resource types. Total instances: A = 10, B = 5, C = 7.

ProcessAllocation (A B C)Max (A B C)Need = Max − Alloc
P00 1 07 5 37 4 3
P12 0 03 2 21 2 2
P23 0 29 0 26 0 0
P32 1 12 2 20 1 1
P40 0 24 3 34 3 1

Total allocated = (7, 2, 5). So Available = (10, 5, 7) − (7, 2, 5) = (3, 3, 2).

Safety check: start with Work = Available. Repeatedly find a process whose Need ≤ Work. Let it "finish" and return its Allocation to Work.

Work = (3,3,2)
P0  need (7,4,3) ≤ (3,3,2)?  no — skip for now
P1  need (1,2,2) ≤ (3,3,2)?  YES → Work = (3,3,2) + (2,0,0) = (5,3,2)
P2  need (6,0,0) ≤ (5,3,2)?  no
P3  need (0,1,1) ≤ (5,3,2)?  YES → Work = (5,3,2) + (2,1,1) = (7,4,3)
P4  need (4,3,1) ≤ (7,4,3)?  YES → Work = (7,4,3) + (0,0,2) = (7,4,5)
P0  need (7,4,3) ≤ (7,4,5)?  YES → Work = (7,4,5) + (0,1,0) = (7,5,5)
P2  need (6,0,0) ≤ (7,5,5)?  YES → Work = (7,5,5) + (3,0,2) = (10,5,7)

All finished → SAFE.  Safe sequence: <P1, P3, P4, P0, P2>

Sanity check: the final Work (10, 5, 7) equals the total — every instance came back. If your final Work doesn't match the total, you've made an arithmetic slip.

Should a new request be granted?

P1 asks for (1, 0, 2).

  1. Is it ≤ P1's Need (1, 2, 2)? Yes.
  2. Is it ≤ Available (3, 3, 2)? Yes.
  3. Pretend to grant it: Available = (2, 3, 0), P1 Allocation = (3, 0, 2), P1 Need = (0, 2, 0).
  4. Run the safety check on that pretend state: P1 (0,2,0) ≤ (2,3,0) → Work (5,3,2); P3 → (7,4,3); P4 → (7,4,5); P0 → (7,5,5); P2 → (10,5,7). Safe.
  5. So the request is granted. If it had been unsafe, P1 would wait and the pretend state would be thrown away.
A safe sequence is not unique. <P1, P3, P0, P2, P4> also works here. In exams, any valid order earns the marks — but options in MCQs may list one that fails, so check each step.

Why it isn't used much in real OSes: processes rarely know their maximum needs, the number of processes changes all the time, and running the check on every request is slow (about m × n² steps for m resource types and n processes).

21. Detection, Recovery, Livelock & Starvation

Detection

  • One instance per resource: simplify the RAG into a wait-for graph (just processes; an edge P1 → P2 means "P1 waits for P2"). Any cycle = deadlock.
  • Several instances: run a Banker's-style check using current requests instead of maximum needs. Processes that can't finish are deadlocked.
  • How often to check? Every request is expensive. Common choice: every so often, or when CPU use suddenly drops.

Recovery

  • Kill processes — kill all deadlocked ones (simple, wasteful), or kill one at a time until the cycle breaks (pick the cheapest victim: lowest priority, least work done).
  • Preempt resources — take a resource from one process and give it to another. The victim must be rolled back to a safe earlier point, often restarted.
  • Make sure the same process isn't picked as victim every time, or it starves.

Databases do this all the time: they detect a deadlock between two transactions and abort one, which your app sees as an error to retry.

Deadlock vs livelock vs starvation

What's happeningEveryday picture
DeadlockProcesses are blocked, doing nothing, waiting on each otherTwo cars stuck nose to nose on a bridge
LivelockProcesses keep running and changing state, but make no progressTwo people in a corridor both step left, then both step right, forever
StarvationOne process never gets a resource because others are always chosen firstA low-priority patient who keeps getting bumped by emergencies

Deadlock always involves a cycle of waiting. Starvation doesn't — the system as a whole is making progress, just not for one unlucky process. Aging fixes starvation; random back-off (wait a random time before retrying) fixes livelock.

Part 6 — Memory Management

22. Memory Basics & Fragmentation

In plain words

Every program thinks it has memory starting at address 0. That's its logical (or virtual) address. The real location in RAM chip is the physical address.

A piece of hardware in the CPU, the MMU (Memory Management Unit), translates logical addresses to physical ones on every single memory access.

Room numbers in a hotel. Your key card says "room 12" (logical). The hotel knows room 12 is on floor 3, east wing (physical). You never need to know the building's layout.

Address binding — when is the real address decided?

WhenHowCan the program move in RAM?
Compile timeCompiler writes real addresses into the codeNo — must load at that exact spot
Load timeAddresses fixed when the program is loadedOnly by reloading it
Execution timeMMU translates on every accessYes — used by all modern OSes

The simplest MMU: base and limit registers

physical = base + logical          if logical < limit
                                   else → trap: "segmentation fault"

base = 14000, limit = 3000
logical 346   → 14000 + 346 = 14346   ✓
logical 3500  → 3500 ≥ 3000 → trap    ✗ (outside its memory)

The limit check is what stops one process reading another's memory. Only the kernel can change base and limit.

Contiguous allocation

The oldest approach: each process gets one continuous block of RAM. As processes come and go, free memory becomes scattered gaps called holes. Where do you put a new process?

  • First fit — the first hole big enough. Fast.
  • Best fit — the smallest hole big enough. Leaves tiny useless leftovers.
  • Worst fit — the biggest hole. Leaves a big, still-useful leftover — in theory.

Classic example. Holes (in order): 100, 500, 200, 300, 600 KB. Processes arrive: 212, 417, 112, 426 KB.

ProcessFirst fitBest fitWorst fit
212500 → 288 left300 → 88 left600 → 388 left
417600 → 183 left500 → 83 left500 → 83 left
112288 → 176 left200 → 88 left388 → 276 left
426must wait — no hole ≥ 426600 → 174 leftmust wait

Here best fit placed all four. In practice, first fit and best fit do about equally well, and both beat worst fit. First fit is usually preferred because it's faster.

Fragmentation — wasted memory

Internal fragmentationExternal fragmentation
WhatWasted space inside a block you were givenEnough free memory in total, but split into gaps too small to use
ExampleNeed 18 KB, get a fixed 20 KB block → 2 KB wastedThree free gaps of 50 KB; a 120 KB process can't fit
Caused byFixed-size blocks (fixed partitions, paging)Variable-size blocks (contiguous allocation, segmentation)
FixSmaller blocksCompaction (slide everything together — slow), or paging

Picture it: internal is a 5-seater car carrying 3 people — 2 seats wasted inside. External: a car park with five single empty spaces scattered about, but you're towing a trailer and need two together.

23. Paging & the TLB

In plain words

Paging cuts a process's logical memory into equal-size pieces called pages, and physical RAM into same-size slots called frames. Any page can go in any free frame.

Because pages don't have to sit next to each other, external fragmentation disappears. Each process has a page table that records which frame holds each page.

A book whose pages are stored in whatever pigeonholes are free, plus an index card saying "page 1 → hole 7, page 2 → hole 3…". You can still read it in order by following the card.

Splitting an address

A logical address is split into a page number (p) and an offset (d) — how far into the page. With page size 2ⁿ, the lowest n bits are the offset.

page number p = logical address ÷ page size   (integer division)
offset      d = logical address mod page size
physical      = frame number × page size + d
offset d is copied unchanged CPU p = 2 d = 452 logical address 2500 TLB (fast cache) page 2 → ? checked first hit miss Page table (in RAM) 0 → 1 1 → 4 2 → 5 3 → 7 f = 5 d = 452 physical address RAM frame 4 frame 5 ← here frame 6 Page size 1024 B. Physical = 5 × 1024 + 452 = 5572. Only the page number changes; the offset never does.
The TLB is checked first. Only on a miss does the CPU read the page table from RAM — which costs an extra memory access.

The numbers in the diagram. Page size 1 KB = 1024 bytes. Logical address 2500 → p = 2500 ÷ 1024 = 2, d = 2500 − 2048 = 452. Page table says page 2 is in frame 5. Physical = 5 × 1024 + 452 = 5572.

Counting bits (very common in GATE)

32-bit logical address, 4 KB pages (4 KB = 2^12 bytes)
  offset bits      = 12
  page-number bits = 32 − 12 = 20      → 2^20 ≈ 1 million pages
  page table size  = 2^20 entries × 4 bytes each = 4 MB  — per process!

Physical memory 1 GB = 2^30, frame size 4 KB = 2^12
  number of frames = 2^30 / 2^12 = 2^18   → frame number needs 18 bits

Paging still has internal fragmentation: a process needing 4097 bytes with 4 KB pages uses 2 pages and wastes 4095 bytes in the last one. On average, half a page per process.

The TLB — making paging fast

With a page table in RAM, every memory access would need two accesses: one for the page table, one for the data. That halves speed.

The TLB (Translation Lookaside Buffer) is a tiny, very fast cache inside the CPU holding recent page → frame translations. Thanks to locality (programs keep using the same few pages), hit rates are usually above 99%.

Effective Access Time (EAT)
  TLB lookup = 20 ns, memory access = 100 ns, TLB hit ratio = 80%

  hit:  20 + 100       = 120 ns   (TLB, then data)
  miss: 20 + 100 + 100 = 220 ns   (TLB, page table, then data)

  EAT = 0.80 × 120 + 0.20 × 220 = 96 + 44 = 140 ns
  Without a TLB it would be 200 ns; perfect hits would give 120 ns.
Context switches and the TLB: the TLB holds one process's translations. On a switch it must be flushed, or each entry tagged with an ASID (address-space ID) so entries from different processes can live side by side. This is part of why switching processes costs more than switching threads.

24. Multi-level Page Tables & Segmentation

Why one big page table doesn't work

We just saw a 32-bit system needs a 4 MB page table per process, and it must be contiguous. 100 processes = 400 MB of page tables, mostly describing memory nobody uses. On 64-bit systems a flat table would be impossibly huge.

Two-level (hierarchical) paging

In plain words

Page the page table. Split the page number into two parts. The first indexes an outer table, which points to a smaller inner table, which gives the frame.

Inner tables for unused regions are simply never created. That's the saving.

A phone book split by first letter. You don't print the "X" section at all if nobody's name starts with X.

32-bit address, 4 KB pages, 4-byte entries:

| p1 (10 bits) | p2 (10 bits) | offset (12 bits) |
   outer index    inner index    within the page

outer table: 2^10 entries × 4 B = 4 KB  (fits exactly in one page)
each inner table: also 4 KB, created only if needed

64-bit x86 uses 4 levels (9 + 9 + 9 + 9 + 12 bits = 48-bit addresses), newer chips 5. The cost: a TLB miss now needs several memory reads — which is why the TLB matters so much.

Other options worth naming: inverted page table — one entry per physical frame instead of per page, so its size depends on RAM, not on how many processes there are (slower lookup, needs hashing). Hashed page table — hash the page number into a table; common for large address spaces.

Segmentation

In plain words

Segmentation divides memory the way a programmer thinks about it: a code segment, a data segment, a stack segment — each a different size.

A logical address is (segment number, offset). A segment table stores each segment's base and limit.

Segment table:   seg   base    limit
                  0    1400    1000
                  1    6300     400
                  2    4300     400

(2, 53)   → 53 < 400  → physical = 4300 + 53 = 4353   ✓
(2, 500)  → 500 ≥ 400 → trap (segmentation fault)     ✗
(0, 999)  → 999 < 1000 → 1400 + 999 = 2399           ✓
PagingSegmentation
Block sizeFixed (pages)Variable (segments)
Split byHardware, blindlyProgram's logical parts
FragmentationInternal onlyExternal only
Programmer sees it?NoYes
ProtectionPer pagePer segment — natural (code read-only, data writable)

Segmented paging combines them: split into segments, then page each segment. Gets segmentation's logical view without external fragmentation. x86 historically did this; modern 64-bit OSes mostly use plain paging and keep segments flat.

"Segmentation fault" is the name for accessing memory you're not allowed to — it comes from this limit check. On today's paged systems it usually means touching a page that isn't mapped (e.g. dereferencing a NULL pointer).

25. Virtual Memory & Page Faults

In plain words

Virtual memory lets a program use more memory than the RAM actually has. Only the pages being used right now sit in RAM; the rest wait on disk.

With demand paging, a page is loaded only when the program first touches it — not before.

A small study desk and a big bookshelf. You keep only the books you're reading on the desk. When you need another, you fetch it from the shelf, putting one back if the desk is full.

What you get from it

  • Programs can be bigger than RAM.
  • More programs fit in memory at once, since each only keeps its active pages.
  • Faster start-up — you don't load the whole program first.
  • Easy sharing (shared libraries mapped into many processes) and copy-on-write after fork.

The valid bit

Each page-table entry has a valid/invalid bit. Valid = the page is in RAM. Invalid = it's on disk (or not part of the process at all). Other bits worth knowing: dirty (modified since loading — must be written back before eviction), referenced (used recently — helps replacement), and protection (read/write/execute).

What happens on a page fault

A page fault is the trap the MMU raises when a process touches a page marked invalid.

  1. The CPU traps into the kernel.
  2. The kernel checks: is this address legal for the process? If not → kill it (segfault).
  3. If legal, find a free frame. If none is free, pick a victim page to evict (section 26). If the victim is dirty, write it to disk first.
  4. Schedule a disk read to bring the page into the frame. The process waits meanwhile; others run.
  5. Update the page table: set the frame number and mark it valid.
  6. Restart the instruction that faulted. This time it works.

How much a page fault costs

Memory access = 200 ns,  page fault service = 8 ms = 8,000,000 ns
Page fault rate p

EAT = (1 − p) × 200 + p × 8,000,000

If p = 1/1000:
EAT = 0.999 × 200 + 8,000
    = 199.8 + 8,000 ≈ 8,200 ns   → about 40× slower than normal!

To stay within 10% of 200 ns (EAT ≤ 220):
  200 + p × 7,999,800 ≤ 220  →  p ≤ 0.0000025   (fewer than 1 in 400,000)

That's the whole point of the next two sections: a disk is around 100,000× slower than RAM, so page faults must be very rare.

Minor vs major faults: a minor (soft) fault is when the page is already in RAM somewhere (e.g. a shared library another process loaded) and just needs mapping — cheap. A major (hard) fault needs a disk read — expensive. /usr/bin/time -v on Linux shows both counts.

26. Page Replacement

In plain words

When RAM is full and a new page is needed, the OS must throw one page out. The page replacement algorithm decides which.

The goal is to evict the page you won't need for the longest time, so you cause the fewest future faults.

The algorithms

  • FIFO — evict the page that has been in memory the longest. Simple queue. But the oldest page might be the most used one.
  • Optimal (OPT / Belady's MIN) — evict the page that will next be used furthest in the future. Gives the fewest faults possible. Impossible in practice (needs the future) — used as a yardstick to measure others.
  • LRU (Least Recently Used) — evict the page not used for the longest time. Uses the past to guess the future. Very good, but tracking exact order on every access is costly.
  • Clock (Second Chance) — a cheap LRU approximation. Pages sit in a circle with a reference bit. The "hand" moves round: bit = 1 → clear it and skip (second chance); bit = 0 → evict. Real OSes use variants of this.
  • LFU / MFU — least / most frequently used. Rarely used; mentioned for completeness.

Worked example — 3 frames

Reference string (the order pages are used): 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1

Each cell shows the frames after that reference. F = page fault, blank = hit.

#RefFIFOLRUOptimal
177 – – F7 – – F7 – – F
207 0 – F7 0 – F7 0 – F
317 0 1 F7 0 1 F7 0 1 F
422 0 1 F2 0 1 F2 0 1 F
502 0 12 0 12 0 1
632 3 1 F2 0 3 F2 0 3 F
702 3 0 F2 0 32 0 3
844 3 0 F4 0 3 F2 4 3 F
924 2 0 F4 0 2 F2 4 3
1034 2 3 F4 3 2 F2 4 3
1100 2 3 F0 3 2 F2 0 3 F
1230 2 30 3 22 0 3
1320 2 30 3 22 0 3
1410 1 3 F1 3 2 F2 0 1 F
1520 1 2 F1 3 22 0 1
1600 1 21 0 2 F2 0 1
1710 1 21 0 22 0 1
1877 1 2 F1 0 7 F7 0 1 F
1907 0 2 F1 0 77 0 1
2017 0 1 F1 0 77 0 1
Faults15129
FIFO    = 15 faults
LRU     = 12 faults
Optimal =  9 faults     (no algorithm can do better on this string)

Hit ratio for LRU = (20 − 12) / 20 = 40%

How to do it fast in an exam. FIFO: keep a queue, evict the front. LRU: look backwards from the current position; the page whose last use is furthest back goes. Optimal: look forwards; the page whose next use is furthest away (or never) goes.

Belady's anomaly

In plain words

You'd expect more frames to always mean fewer faults. With FIFO, that's not guaranteed — adding a frame can increase faults. That surprise is Belady's anomaly.

Reference string: 1 2 3 4 1 2 5 1 2 3 4 5   (FIFO)

3 frames:  1F 2F 3F 4F 1F 2F 5F 1  2  3F 4F 5     → 9 faults
4 frames:  1F 2F 3F 4F 1  2  5F 1F 2F 3F 4F 5F    → 10 faults  (more!)

LRU and Optimal never suffer from it. They are stack algorithms: the set of pages kept with n frames is always a subset of the set kept with n + 1 frames. FIFO doesn't have that property.

27. Thrashing & the Working Set

In plain words

Thrashing is when the system spends more time swapping pages in and out than actually running programs. The disk light is solid on, and nothing gets done.

It happens when processes don't have enough frames for the pages they're actively using. Every page brought in pushes out a page that's needed a moment later.

A tiny desk with ten books you need at once. You keep putting one book back to fetch another, then immediately need the first again. You spend the whole evening walking to the shelf and read nothing.

The vicious circle

too many processes → each has too few frames → lots of page faults
→ processes wait on disk → CPU use drops
→ OS thinks "CPU is idle, let's admit more processes"
→ even fewer frames each → even more faults …   (collapse)

On a graph of CPU use vs number of processes, use rises, peaks, then falls off a cliff. That cliff is thrashing.

Locality and the working set

Locality of reference: at any moment, a program uses a small group of pages heavily (the current function, its data). That group is its locality, and it shifts over time.

The working set is the set of pages a process used in its last Δ references (Δ is a window, e.g. the last 10,000 accesses).

Δ = 5, recent references: … 2 6 1 5 7 7 7 7 5 1 | now
working set over the last 5 refs (7 7 7 5 1) = {1, 5, 7}  → size 3

D = sum of all processes' working-set sizes
If D > total frames → thrashing is coming → suspend a process
If D is well below   → room to admit another process

Other fixes

  • Page-fault frequency (PFF) — set an upper and lower fault rate. Above the upper → give the process more frames. Below the lower → take some away.
  • Reduce multiprogramming — swap out or suspend some processes.
  • Local replacement — a process can only evict its own pages, so one thrashing process can't drag others down.
  • And the honest real-world one: add more RAM.
Part 7 — Files, Disks & I/O

28. File Systems

In plain words

A disk only knows numbered blocks (say 4 KB each). A file system turns those blocks into named files inside folders, and remembers which blocks belong to which file.

A library. The shelves are blocks. The catalogue — title, author, which shelf — is the file system. Without the catalogue the books are still there, but you'd never find one.

What a file has

  • Data — the content.
  • Metadata — size, owner, permissions, timestamps (created, modified, accessed), and where its blocks are.
  • Operations: create, open, read, write, seek (jump to a position), close, delete.

When you open a file, the OS returns a small number called a file descriptor (fd). Every later read/write uses that number. 0, 1 and 2 are always stdin, stdout and stderr.

Directory structures

  • Single-level — all files in one folder. Names clash quickly.
  • Two-level — one folder per user.
  • Tree — folders within folders. What you use every day. Paths are absolute (/home/ada/notes.txt) or relative (../notes.txt).
  • Acyclic graph — a tree plus links, so one file can appear in two folders.

How a file's blocks are stored — allocation methods

MethodHowGoodBad
ContiguousFile takes blocks next to each other; store start + lengthFastest reads; easy random accessExternal fragmentation; hard to grow a file
LinkedEach block holds a pointer to the nextNo fragmentation; files grow easilyRandom access is slow (follow the chain); one broken pointer loses the rest
Linked with FATThe pointers are kept together in a table (File Allocation Table)Table can be cached in RAMTable gets big on large disks
IndexedEach file has an index block listing all its block numbersFast random access; no external fragmentationIndex block overhead, even for tiny files

Inodes (Unix's indexed scheme)

In Unix/Linux every file has an inode — a record with all the metadata except the name, plus pointers to the data blocks. The name lives in the directory, which is just a list of name → inode number pairs.

inode
├── owner, permissions, size, timestamps, link count
├── 12 direct pointers        → data blocks
├── 1 single indirect pointer → a block full of pointers → data blocks
├── 1 double indirect pointer → pointers → pointers → data
└── 1 triple indirect pointer → three levels deep

Block 4 KB, pointer 4 B → one block holds 1024 pointers
  direct:          12 × 4 KB          = 48 KB
  single indirect: 1024 × 4 KB        = 4 MB
  double indirect: 1024² × 4 KB       = 4 GB
  triple indirect: 1024³ × 4 KB       = 4 TB

Small files (most files) are reached in one step through direct pointers. Huge files are still possible through the indirect ones. GATE often asks for the max file size — add up all four lines.

Hard links vs soft links

Hard linkSoft (symbolic) link
What it isAnother name pointing to the same inodeA tiny file that contains a path to another file
Createln a.txt b.txtln -s a.txt b.txt
Delete the originalData survives — the inode lives until its link count hits 0Link breaks ("dangling")
Across file systems?No — inode numbers are per file systemYes
To directories?Not allowed (could create loops)Allowed

Picture it: a hard link is a second front door into the same house. A soft link is a note on a door saying "the house is at 12 Baker Street" — if the house is demolished, the note still exists but leads nowhere.

Journaling — surviving a crash

Creating a file touches several places: the inode, the directory, the free-block list. If power fails halfway, the file system is left inconsistent. Old systems ran a slow full check (fsck) on reboot.

A journaling file system first writes a note of what it's about to do into a log (the journal), then does it, then marks the note done. After a crash it just replays or discards unfinished notes — seconds, not hours. Same idea as a database's write-ahead log. Examples: ext4, NTFS, XFS. (Newer ones like Btrfs, ZFS and Apple's APFS use copy-on-write instead: never overwrite, write new and switch the pointer.)

29. Disk Scheduling

In plain words

On a spinning hard disk, a read/write head must physically move to the right track (cylinder). This movement — the seek — is the slowest part of a disk access.

When many requests are waiting, the order you serve them in changes how far the head travels. Disk scheduling picks a good order.

A lift in a tall building. If it went to floors strictly in the order buttons were pressed, it would zigzag wildly. Instead it goes up collecting everyone, then comes down. That's literally the SCAN algorithm — also called the elevator algorithm.

Disk access time

access time = seek time + rotational latency + transfer time
              (move arm)   (wait for sector     (read the data)
                            to spin under head)

Average rotational latency = half a rotation.
7200 RPM → 7200/60 = 120 rotations per second → 1 rotation = 8.33 ms
         → average latency = 4.17 ms

The algorithms

  • FCFS — in order of arrival. Fair, but lots of back-and-forth.
  • SSTF (Shortest Seek Time First) — always go to the nearest request. Much less movement, but far-away requests can starve.
  • SCAN (elevator) — move in one direction to the end of the disk, serving requests on the way, then reverse.
  • C-SCAN (circular SCAN) — move in one direction to the end, then jump back to the start without serving, and sweep again. More even waiting times.
  • LOOK / C-LOOK — like SCAN / C-SCAN, but only go as far as the last request, not the physical end.

Worked example

Cylinders 0–199. Head at 53, moving towards higher numbers. Queue: 98, 183, 37, 122, 14, 124, 65, 67.

FCFS:   53 → 98 → 183 → 37 → 122 → 14 → 124 → 65 → 67
        45 + 85 + 146 + 85 + 108 + 110 + 59 + 2           = 640

SSTF:   53 → 65 → 67 → 37 → 14 → 98 → 122 → 124 → 183
        12 + 2 + 30 + 23 + 84 + 24 + 2 + 59               = 236

SCAN:   53 → 65 → 67 → 98 → 122 → 124 → 183 → 199 → 37 → 14
        (199 − 53) + (199 − 14) = 146 + 185               = 331

C-SCAN: 53 → … → 183 → 199 → jump to 0 → 14 → 37
        146 + 199 (jump) + 37                             = 382
        (183 if the question says not to count the jump)

LOOK:   53 → 65 → 67 → 98 → 122 → 124 → 183 → 37 → 14
        (183 − 53) + (183 − 14) = 130 + 169               = 299

C-LOOK: 53 → … → 183 → jump to 14 → 37
        130 + 169 (jump) + 23                             = 322
        (153 if the jump isn't counted)
0 14 37 53 65,67 98 122,124 183 199 start +12 +2 30 23 84 — long trip back 24 2 59 SSTF total = 12 + 2 + 30 + 23 + 84 + 24 + 2 + 59 = 236 cylinders
Time runs downwards. SSTF chases whatever is nearest, which is efficient here — but a steady stream of requests near the head could leave 183 waiting indefinitely.
AlgorithmTotal head movementStarvation?
FCFS640No
SSTF236Yes
SCAN331No
C-SCAN382 (183 without the jump)No — most uniform wait
LOOK299No
C-LOOK322 (153 without the jump)No
Read the direction carefully. If the head were moving towards 0 instead, SCAN would be 53 → 37 → 14 → 0 → 65 → … → 183, total 53 + 183 = 236. Direction changes every SCAN/LOOK answer. Also: SSDs have no moving head, so seek order barely matters — Linux uses simple schedulers (none or mq-deadline) for them.

30. I/O: Interrupts, Polling, DMA & Buffering

In plain words

Devices (keyboard, disk, network card) are far slower than the CPU. The question is how the CPU finds out a device is ready, and how data gets moved without wasting the CPU's time.

Polling vs interrupts

PollingInterrupts
HowCPU keeps asking the device "done yet?" in a loopDevice signals the CPU when it's done
PictureA child asking "are we there yet?" every minuteThe driver tells you when you've arrived
Good whenDevice is very fast or almost always readyEvents are rare or slow (keyboard, disk)
CostWastes CPU if the device is slowEach interrupt has overhead; a flood can swamp the CPU

What happens on an interrupt

  1. The device raises a signal on an interrupt line.
  2. The CPU finishes its current instruction, saves where it was, and jumps into kernel mode.
  3. It looks up the interrupt vector table to find the right handler (ISR, interrupt service routine) for that device.
  4. The handler does the minimum quickly (e.g. copy the key pressed) and schedules the rest for later.
  5. The CPU restores what it saved and carries on.

Hardware interrupts come from devices. Software interrupts / traps come from programs — system calls, or errors like divide-by-zero. Fast network cards switch between interrupts and polling depending on load (Linux's NAPI does this).

DMA — Direct Memory Access

In plain words

Copying a 1 MB file from disk byte by byte through the CPU would waste millions of CPU cycles. A DMA controller copies data between the device and RAM by itself.

The CPU just says "move this block from the disk to this RAM address", goes off to do other work, and gets one interrupt when the whole transfer is done.

Instead of carrying every box from the lorry yourself, you hire a mover, tell them where it goes, and they ring you when it's finished.

Buffering, caching and spooling

  • Buffer — a temporary holding area for data in transit, to smooth out speed differences. Video players buffer a few seconds ahead. Double buffering: fill one buffer while the other is being used.
  • Cache — a fast copy of data kept for reuse. The OS page cache keeps recently read file blocks in RAM, which is why opening a file the second time is instant.
  • Spooling — a queue on disk for a device that can serve only one job at a time. The printer spooler lets ten people "print" at once; jobs are written to disk and fed to the printer one by one. (SPOOL = Simultaneous Peripheral Operations On-Line.)

Blocking vs non-blocking I/O

Blocking: read() waits until data arrives — the thread sleeps. Non-blocking: read() returns straight away, with data or "nothing yet". Asynchronous: you ask, carry on, and get notified when done. Node.js is built on non-blocking I/O plus an event loop (using epoll on Linux), which is how one thread handles thousands of connections.

Part 8 — In Practice

31. Linux Essentials for Developers

In plain words

Everything above shows up the moment you run a server. These are the commands and ideas you'll actually use — and that interviewers ask about to see if you've touched a real machine.

Looking at processes

ps aux                 # every process: user, PID, CPU%, MEM%, state, command
ps -ef --forest        # with parent–child tree
top / htop             # live view, sorted by CPU
pgrep node             # PIDs of processes named node
pstree -p              # tree of processes
lsof -i :3000          # which process is using port 3000
free -h                # RAM and swap usage
vmstat 1               # memory, swapping, CPU every second
strace -p 1234         # watch the system calls a process makes

The STAT column in ps: R running/ready, S sleeping (waiting, can be woken), D uninterruptible sleep (usually stuck on disk I/O), Z zombie, T stopped. These map straight onto the process states in section 6.

Background jobs

node server.js &       # run in background
jobs                   # list background jobs
fg %1                  # bring job 1 to foreground
Ctrl+Z, then bg        # pause, then resume in background
nohup node server.js & # keep running after you log out
nice -n 10 ./backup.sh # start with lower priority (higher nice = nicer to others)
renice -n 5 -p 1234    # change priority of a running process

Signals

A signal is a small message the OS delivers to a process. The process can catch most of them and run its own handler.

SignalNo.Sent byCan be caught?Meaning
SIGINT2Ctrl+CYesInterrupt from keyboard
SIGTERM15kill PID (default)Yes"Please shut down" — clean up and exit
SIGKILL9kill -9 PIDNoKilled immediately by the kernel
SIGHUP1Terminal closedYesOften used to mean "reload config"
SIGTSTP20Ctrl+ZYesPolite pause from the terminal
SIGSTOP / SIGCONT19 / 18kill -STOP / fg, kill -CONTSTOP: NoForced pause / resume
SIGCHLD17KernelYesA child process ended — time to wait()
SIGSEGV11KernelYes (rarely wise)Invalid memory access

(Signal numbers shown are for Linux on x86/ARM.)

SIGTERM vs SIGKILL — always try SIGTERM first. SIGTERM lets the app finish current requests, close database connections and flush logs. SIGKILL can't be caught, so the app gets no chance to clean up — half-written files, lost data. Docker and Kubernetes do exactly this: send SIGTERM, wait (10 s in Docker, 30 s in Kubernetes by default), then SIGKILL. Your Node app should handle process.on("SIGTERM", …) for a graceful shutdown.

File permissions

$ ls -l deploy.sh
-rwxr-x---  1 ada  devs  512 Oct 6 10:00 deploy.sh
│└┬┘└┬┘└┬┘
│ │  │  └── others: --- (nothing)
│ │  └───── group:  r-x (read, execute)
│ └──────── owner:  rwx (read, write, execute)
└────────── type: - file, d directory, l symlink

r = 4, w = 2, x = 1  → add them up per group
rwx = 7, r-x = 5, --- = 0   →  chmod 750 deploy.sh

chmod 644 file    # rw-r--r--  normal file
chmod 755 dir     # rwxr-xr-x  normal directory / script
chmod +x run.sh   # add execute for everyone
chown ada:devs f  # change owner and group

On a directory, r means list its contents, w means create/delete files in it, and x means enter it (cd). That's why folders need x.

Root (user ID 0) bypasses permission checks. Run services as a normal user, and use sudo only for the one command that needs it.

Everything is a file in Unix: devices (/dev/sda), process info (/proc/1234/status), even sockets and pipes use file descriptors. That's why the same read/write calls work on all of them — and why "too many open files" is an error you'll hit on busy servers (raise it with ulimit -n).

32. Virtual Machines vs Containers

In plain words

A virtual machine (VM) pretends to be a whole computer. It runs its own full operating system on fake hardware provided by a hypervisor.

A container is just a normal process (or group) on the host that the kernel walls off, so it sees its own files, network and process list. All containers share the host's kernel.

VMs are separate houses on one plot — each with its own plumbing and wiring. Containers are flats in one building — private front doors, but shared foundations, water mains and electrics.

VIRTUAL MACHINES each boots a full OS — GBs, slower start App A App B App C libs libs libs Guest OS Guest OS Guest OS Hypervisor Hardware CONTAINERS share the host kernel — MBs, start in seconds App A App B App C libs libs libs container runtime (Docker / containerd) Host OS kernel — ONE, shared by all Hardware
The shaded layer is the kernel. VMs have one per app; containers share one. That single difference explains every row of the table below.
Virtual machineContainer
Isolates atHardware levelOS (process) level
Own kernel?Yes — full guest OSNo — shares the host's
SizeGBsMBs
Start timeTens of seconds to minutesUnder a second to a few seconds
Run a different OS?Yes — Windows VM on a Linux hostNo — Linux containers need a Linux kernel
Security isolationStrongerWeaker — a kernel bug can affect all containers
Typical useCloud servers (EC2), running other OSesPackaging and deploying apps, microservices

Hypervisor types

  • Type 1 (bare-metal) — runs directly on the hardware. VMware ESXi, Xen, Microsoft Hyper-V, KVM (part of Linux). Used in data centres and clouds.
  • Type 2 (hosted) — runs as an app on a normal OS. VirtualBox, VMware Workstation. Used on laptops.

What makes a container possible (Linux kernel features)

  • Namespaces — give a process its own view of things: its own process IDs (it thinks it's PID 1), network interfaces, mount points, hostname, users.
  • cgroups (control groups) — limit how much CPU, memory and I/O a group of processes may use. docker run --memory=512m sets a cgroup limit.
  • Union / overlay file systems — image layers stacked on top of each other, shared between containers, with a thin writable layer on top.
Docker on a Mac or Windows actually runs a small Linux VM behind the scenes, because containers need a Linux kernel. And in the cloud, your containers usually run inside VMs — the two are used together, not instead of each other.
Revision

Interview Q&A — quick answers

Basics

  • What is an OS? The software that manages hardware and shares it safely between programs, and gives programs simple abstractions like files and processes.
  • What is the kernel? The core of the OS that always stays in memory and runs in kernel mode with full hardware access.
  • User mode vs kernel mode? User mode blocks privileged instructions; kernel mode allows everything. Apps run in user mode and enter the kernel only through system calls, interrupts or faults.
  • What is a system call? A controlled request from a program to the kernel (open, read, fork). It uses a trap instruction to switch to kernel mode and back.
  • Monolithic vs microkernel? Monolithic keeps all services in the kernel (fast, less isolated — Linux). Microkernel keeps only the bare minimum and runs the rest as user-space servers (safer, slower — QNX, MINIX).
  • Multiprogramming vs multitasking? Multiprogramming switches when a process waits, to keep the CPU busy. Multitasking also switches on a timer, to keep response times low.

Processes & threads

  • Program vs process? A program is a passive file on disk; a process is a running instance with its own memory, registers and state.
  • What is a PCB? The kernel's record of a process — PID, state, program counter, registers, memory info, open files — used to pause and resume it.
  • Process states? New, Ready, Running, Waiting, Terminated. Waiting goes back to Ready, never straight to Running.
  • Process vs thread? Processes have separate memory; threads in a process share code, data, heap and files, but each has its own stack, registers and PC. Threads are cheaper to create and switch.
  • What is a context switch? Saving one process's CPU state to its PCB and loading another's. Pure overhead, plus cache and TLB warm-up cost.
  • What does fork return? 0 in the child, the child's PID in the parent, −1 on failure. n forks in a row give 2ⁿ processes.
  • Zombie vs orphan? Zombie: child finished but parent hasn't called wait(). Orphan: parent finished first; init adopts the child.
  • User vs kernel threads? User threads are managed by a library and invisible to the kernel (fast, but one block blocks all). Kernel threads are scheduled by the OS and can use multiple cores.

Scheduling

  • Preemptive vs non-preemptive? Preemptive can take the CPU away mid-run; non-preemptive waits for the process to finish or block.
  • Which algorithm gives minimum average waiting time? SJF (non-preemptive) / SRTF (preemptive) — provably optimal, but needs burst times.
  • What is the convoy effect? In FCFS, short processes stuck behind one long process.
  • How do you prevent starvation in priority scheduling? Aging — gradually raise the priority of waiting processes.
  • Effect of time quantum in RR? Too large → becomes FCFS. Too small → time wasted on context switches.
  • TAT and WT formulas? TAT = CT − AT. WT = TAT − BT. RT = first run − AT.
  • What is MLFQ? Multiple queues with different priorities and quanta; processes move down if CPU-hungry and up if they wait long. Learns process behaviour.

Synchronisation

  • What is a race condition? When the result depends on the timing of threads accessing shared data, e.g. two threads doing count++.
  • Critical section requirements? Mutual exclusion, progress, bounded waiting.
  • Mutex vs semaphore? A mutex is a lock with an owner (only the locker unlocks). A semaphore is a counter any thread can signal; counting semaphores manage N resources.
  • Binary semaphore vs mutex? Similar values (0/1), but a mutex has ownership and a binary semaphore doesn't — so a semaphore can be used for signalling between threads.
  • What is busy waiting / a spinlock? Looping until a lock frees up. Wastes CPU; only worth it for very short waits on multi-core machines.
  • What is a monitor? A language-level construct whose methods run one at a time automatically, with condition variables for waiting. Java's synchronized.
  • Why wait in a while loop? Spurious wakeups and other threads acting first — the condition must be re-checked after waking.
  • Producer–consumer semaphores? mutex = 1, empty = N, full = 0. Wait on empty/full before mutex, or it deadlocks.
  • How to avoid deadlock in dining philosophers? Allow at most 4 at the table, make one philosopher pick up in reverse order, or pick up both chopsticks atomically.

Deadlock

  • Four necessary conditions? Mutual exclusion, hold and wait, no preemption, circular wait. Break any one to prevent deadlock.
  • Most practical prevention? Break circular wait by always acquiring resources (locks) in a fixed global order.
  • Safe vs unsafe state? Safe: some order exists in which every process can finish. Unsafe means deadlock is possible, not certain.
  • What does Banker's algorithm do? Grants a request only if the resulting state is still safe. Need = Max − Allocation.
  • Cycle in a RAG always means deadlock? Only if each resource has a single instance. With multiple instances, a cycle means possible deadlock.
  • Deadlock vs starvation vs livelock? Deadlock: all stuck waiting on each other. Starvation: one process keeps being passed over. Livelock: processes keep reacting to each other but make no progress.

Memory

  • Logical vs physical address? Logical is what the program uses; physical is the real RAM location. The MMU translates at run time.
  • Internal vs external fragmentation? Internal: unused space inside an allocated block. External: free memory split into gaps too small to use. Paging removes external; segmentation removes internal.
  • What is paging? Splitting logical memory into fixed-size pages and RAM into frames, mapped by a per-process page table.
  • What is the TLB? A small fast cache of recent page-to-frame translations, avoiding an extra memory access per lookup.
  • Why multi-level page tables? A flat table is huge (4 MB per process on 32-bit); multi-level tables only create the parts actually used.
  • What is virtual memory? Letting processes use more memory than physical RAM by keeping only active pages in RAM and the rest on disk.
  • What is a page fault? A trap when a process touches a page not in RAM; the OS loads it from disk and restarts the instruction.
  • Best page replacement algorithm? Optimal in theory (needs the future). LRU in practice, usually approximated by the Clock algorithm.
  • What is Belady's anomaly? With FIFO, more frames can cause more page faults. LRU and Optimal are immune.
  • What is thrashing? The system spends most of its time paging rather than working, because processes don't have enough frames for their working sets.

Files, disks & I/O

  • What is an inode? A Unix record of a file's metadata and block pointers — everything except its name.
  • Hard link vs soft link? Hard link: another name for the same inode (survives deleting the original). Soft link: a file holding a path (breaks if the target goes).
  • What is journaling? Logging intended changes before making them, so the file system can recover quickly after a crash.
  • Which disk algorithm minimises seek but can starve? SSTF. SCAN/C-SCAN avoid starvation; C-SCAN gives the most uniform wait.
  • What is DMA? A controller that moves data between a device and RAM without the CPU, interrupting once when the transfer is complete.
  • Polling vs interrupts? Polling: CPU repeatedly checks the device. Interrupts: device notifies the CPU when ready.
  • What is spooling? Queueing jobs on disk for a device that handles one at a time, like a printer.

Practical

  • SIGTERM vs SIGKILL? SIGTERM (15) asks politely and can be handled for a clean shutdown. SIGKILL (9) can't be caught — the kernel kills the process immediately.
  • What does chmod 755 mean? Owner rwx, group r-x, others r-x.
  • VM vs container? A VM virtualises hardware and runs its own OS kernel. A container is an isolated process sharing the host kernel, using namespaces and cgroups — lighter and faster, less isolated.
  • What are namespaces and cgroups? Namespaces control what a process can see; cgroups control how much it can use.

Learning Path

  1. The big picture — what the kernel does, user vs kernel mode, system calls. Run strace ls and look at the calls it makes.
  2. Processes — states, PCB, fork/exec/wait. Write a small C program that forks, and solve a few "how many times does this print" puzzles.
  3. Threads — write a program where two threads increment a shared counter a million times. See the wrong answer. Fix it with a mutex.
  4. Scheduling numericals — practise FCFS, SJF, SRTF, Priority and RR until drawing the Gantt chart is automatic. Always double-check by summing.
  5. Synchronisation — semaphores, then producer–consumer, readers–writers and dining philosophers from memory.
  6. Deadlock — the four conditions, RAGs, and at least three Banker's problems end to end.
  7. Memory — address splitting and bit-counting, TLB effective access time, then paging vs segmentation.
  8. Virtual memory — page faults and the three replacement algorithms on paper. Reproduce Belady's anomaly yourself.
  9. Files and disks — inodes and max file size, links, and disk scheduling numericals in both directions.
  10. Hands-on Linux — ps, top, kill, chmod, signals. Write a Node server that shuts down gracefully on SIGTERM.
  11. Containers — run a Docker container, then look at its processes from the host with ps. Seeing it as "just a process" makes namespaces click.
  12. Then — previous-year GATE OS questions, topic by topic. They repeat patterns heavily.
The habit that matters most: for every concept, ask "what problem was this invented to solve?" Paging exists because of fragmentation. TLBs exist because paging doubled memory accesses. Multi-level tables exist because flat tables got too big. OS ideas form a chain of fixes — learn the chain and you can rebuild any answer from scratch in an interview.