- Distinguish processes from threads and explain a race condition
- Compute the average waiting time for FCFS, SJF and Round Robin
- Translate a virtual address to a physical one and count page faults
- Apply the four deadlock conditions and the banker's algorithm
Your laptop runs a browser, music, a messenger and an antivirus at the same time, yet it has only a few processor cores and limited memory. Every program believes the whole computer is its own — and the operating system (OS) creates that illusion. The OS shares out processor time, gives each program its own memory, organises files on disk and keeps programs from locking each other up.
Processes, threads and scheduling
A process is a program in execution: it has its own virtual address space (code, data, heap), open files and at least one thread. A thread is a sequence of execution inside a process: it has its own stack, registers and PC but shares the process's memory with the other threads. Threads are cheaper to create and switch than processes, but shared memory needs synchronisation.
Two threads each increment a shared counter = 5. Each does three steps: read (tmp = counter), add (tmp + 1), write (counter = tmp). What is the result if the scheduler interleaves them as: A read, B read, A add, A write, B add, B write?
Show solutionHide solution
A: tmpA = 6, counter = 6. B: tmpB = 6, counter = 6.
After two increments we have 6 instead of 7 — one update was “lost”. The result depends on the interleaving: this is a race condition. The fix: protect the critical section with a mutex (lock) so that read–add–write becomes indivisible.
Ready processes wait in a queue, and the scheduler decides which one gets the processor. FCFS — first come, first served; SJF — shortest job first (it minimises the average waiting time but needs to know job lengths in advance); Round Robin — each process gets a time quantum q, then goes to the back of the queue. Every switch costs a context switch: saving and restoring registers.
- Waverage waiting time
- Cᵢcompletion time of process i
- Aᵢarrival time
- BᵢCPU burst length
Processes P1 = 10, P2 = 4, P3 = 2 ms arrive at time 0 in this order. Find the average waiting time for FCFS, SJF and Round Robin (q = 3).
Show solutionHide solution
SJF: P3 0–2, P2 2–6, P1 6–16 → waits 0, 2, 6 → W = 8/3 ≈ 2.67 ms.
RR, q = 3: P1 0–3, P2 3–6, P3 6–8 (done), P1 8–11, P2 11–12 (done), P1 12–16 (done).
Wait = completion − burst: P1 16 − 10 = 6, P2 12 − 4 = 8, P3 8 − 2 = 6 → W = 20/3 ≈ 6.67 ms.
RR loses to SJF on waiting, but every process starts getting service within 6 ms — which is what interactive systems care about.
from collections import deque
def fcfs(bursts):
t, waits = 0, []
for b in bursts:
waits.append(t)
t += b
return waits
def round_robin(bursts, q):
rem, t, done = list(bursts), 0, [0] * len(bursts)
queue = deque(range(len(bursts)))
while queue:
i = queue.popleft()
run = min(q, rem[i])
t += run
rem[i] -= run
if rem[i]:
queue.append(i)
else:
done[i] = t
return [done[i] - bursts[i] for i in range(len(bursts))]
jobs = [10, 4, 2]
for name, w in [('FCFS', fcfs(jobs)), ('SJF', fcfs(sorted(jobs))), ('RR q=3', round_robin(jobs, 3))]:
print(name, w, 'average wait:', round(sum(w) / len(w), 2))▸ Expected output
FCFS [0, 10, 14] average wait: 8.0 SJF [0, 2, 6] average wait: 2.67 RR q=3 [6, 8, 6] average wait: 6.67
Virtual memory and paging
Each process sees its own virtual address space starting at 0. Memory is split into equal pages (e.g. 4 KB) and physical RAM into equal frames; each process's page table maps page → frame. The hardware (MMU) does the translation and keeps recent translations in the TLB cache. If a page is not in RAM, a page fault occurs and the OS loads it from disk — so programs can use more memory than physical RAM and cannot see each other's memory.
- VA, PAvirtual and physical address
- Ppage size, bytes
- p, dpage number and offset within the page
When P = 2ᵏ the division just splits the bits: with 4 KB = 2¹² pages the low 12 bits are the offset and the rest is the page number.
a) Page size 4 KB, VA = 0x3A7C, page table maps 3 → 7. What is the physical address?
b) A RAM access takes 100 ns, handling a page fault takes 8 ms, fault probability p = 0.001. What is the effective access time?
Show solutionHide solution
PA = 7 · 4096 + 2684 = 28,672 + 2684 = 31,356 = 0x7A7C. In hex only the page digit changed: 0x3A7C → 0x7A7C.
b) EAT = (1 − p) · 100 + p · 8,000,000 ns = 99.9 + 8000 ≈ 8100 ns ≈ 8.1 µs — one fault per thousand accesses makes memory 81 times slower!
When RAM is full the OS must choose which page to evict to disk. FIFO evicts the page loaded earliest, LRU the one unused for the longest time. The theoretical ideal OPT evicts the page needed furthest in the future, but the future is unknown, so it serves only as a benchmark.
def faults(refs, frames, policy):
mem, count = [], 0
for p in refs:
if p in mem:
if policy == 'LRU':
mem.remove(p)
mem.append(p)
continue
count += 1
if len(mem) == frames:
mem.pop(0)
mem.append(p)
return count
refs = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5]
for frames in [3, 4]:
print(frames, 'frames: FIFO', faults(refs, frames, 'FIFO'), '| LRU', faults(refs, frames, 'LRU'))
page_size, va = 4096, 0x3A7C
page, offset = divmod(va, page_size)
page_table = {3: 7}
pa = page_table[page] * page_size + offset
print('page', page, 'offset', offset, '-> physical', pa, hex(pa))▸ Expected output
3 frames: FIFO 9 | LRU 10 4 frames: FIFO 10 | LRU 8 page 3 offset 2684 -> physical 31356 0x7a7c
File systems
A disk is divided into fixed-size blocks (usually 4 KB). In Unix file systems (ext4) each file's inode stores its size, owner, permissions, timestamps and pointers to the data blocks; a directory is just a “name → inode number” table. The classic ext2/ext3 inode has 12 direct pointers plus single, double and triple indirect pointers: with 4 KB blocks and 4-byte pointers, the direct blocks cover 48 KB, the single indirect block another 1024 · 4 KB = 4 MB, the double 4 GB and the triple 4 TB. Journaling (ext4, NTFS) first writes changes to a journal so the file system survives a power cut. In FAT32 a single file must be smaller than 4 GB.
Deadlock
Thread A holds lock 1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1 — neither can move. Deadlock is possible when Coffman's four conditions all hold: mutual exclusion (a resource is used by one at a time), hold and wait, no preemption (resources are not taken away) and circular wait. Breaking any one is enough; the most practical way is to always acquire locks in the same global order, which rules out circular wait.
A system has 10 identical resources. P0: max 7, holds 3; P1: max 3, holds 1; P2: max 6, holds 3. Is the state safe? If P0 asks for 2 more, can the request be granted?
Show solutionHide solution
P1: 2 ≤ 3 → finishes and returns → free 4. P2: 3 ≤ 4 → free 7. P0: 4 ≤ 7 → free 10.
The safe sequence P1, P2, P0 exists → the state is safe.
If P0 gets 2 more: P0 holds 5, needs 2, free 1. Nobody's need is ≤ 1 (P0 2, P1 2, P2 3) → an unsafe state, so the request must wait. Unsafe does not yet mean deadlock, but the OS will not take the risk.
Key points
- A process has its own address space; its threads share memory, so shared data must be protected by locks.
- SJF minimises average waiting, Round Robin shortens response time; wait = completion − arrival − burst.
- Virtual address = page + offset; PA = frame · P + d; page faults sharply increase the effective access time.
- LRU is usually better than FIFO; FIFO can suffer from Belady's anomaly.
- Deadlock needs all four conditions; a fixed lock order and the banker's algorithm prevent it.
Check yourself
10 questions. Every correct answer earns XP.