Operating Systems
Time limit: 90 minutes Total marks: 60 Instructions: Answer ALL three questions. Show reasoning, derivations, and any assumptions. Math may be written using notation.
Question 1 — Scheduling, Queueing & Overhead Modelling (20 marks)
A single-core system runs a Round-Robin (RR) scheduler with time quantum . A context switch costs a fixed overhead (seconds). Consider a workload of CPU-bound jobs, each requiring exactly seconds of pure CPU time, all arriving at .
(a) Derive a closed-form expression for the CPU efficiency (fraction of wall-clock time spent doing useful work, not context switching) as a function of , , and . State your modelling assumptions. (4)
(b) Now consider turnaround time. For the last job to finish under RR, derive the turnaround time as a function of , , , and , assuming (i.e. is an exact multiple of ). (5)
(c) A designer proposes: "Make very large to reduce overhead." Prove that as , RR degenerates into FCFS, and give the resulting average turnaround time for the identical jobs (ignoring in this limit). Compare with the RR average turnaround for small and explain the trade-off in one sentence. (6)
(d) Give the specific numeric values: , ms, ms, ms. Compute the CPU efficiency (part a) and (part b). (5)
Question 2 — Concurrency: Correctness Proof & Synchronization (20 marks)
Two threads share an integer counter initialised to 0. Each executes the non-atomic statement counter++ exactly times in a loop.
(a) Explain precisely why the final value of counter can be less than . Identify the smallest possible final value, and construct an interleaving (at the load/add/store granularity) that achieves it. Prove your minimum is tight (cannot be smaller). (7)
(b) A student protects the increment using the following test-and-set spinlock. State whether it guarantees (i) mutual exclusion, (ii) progress, (iii) bounded waiting. Justify each. (6)
lock = 0
acquire(): while test_and_set(&lock) == 1: ; // spin
release(): lock = 0
(c) Rewrite the critical-section protection using a counting semaphore to instead allow at most threads simultaneously inside a region (not mutual exclusion). Give the P()/V() placement and the semaphore's initial value. Then prove, using the semaphore invariant S = S_0 - (\text{#completed P}) + (\text{#completed V}) \ge 0, that no more than threads can be inside at once. (7)
Question 3 — Memory, Paging & Deadlock Avoidance (20 marks)
(a) A 32-bit virtual address space uses a two-level page table. Page size is 4 KB. The virtual address is split as [p1 | p2 | offset]. Each page table entry (PTE) is 4 bytes. Derive the number of bits for offset, p2, and p1 such that each single-level page table fits exactly in one page. Show your arithmetic. (6)
(b) Explain why a two-level (or multi-level) page table can consume less physical memory than a flat single-level page table for a sparse address space. Give a concrete numeric example using your bit split from (a): a process using only 1 code page (bottom of address space) and 1 stack page (top of address space) — compute the page-table memory needed for both flat and two-level schemes. (6)
(c) Consider a system with 3 resource types with total instances . The current state is:
| Process | Allocation (A,B,C) | Max (A,B,C) |
|---|---|---|
| (0,1,0) | (7,5,3) | |
| (2,0,0) | (3,2,2) | |
| (3,0,2) | (9,0,2) | |
| (2,1,1) | (2,2,2) | |
| (0,0,2) | (4,3,3) |
Compute Available, the Need matrix, run the Banker's safety algorithm, and give a safe sequence (or prove none exists). Then determine whether a request from for can be granted. (8)
Answer keyMark scheme & solutions
Question 1
(a) Assumptions: one context switch of cost occurs between each quantum of execution; jobs are pure CPU. Over the whole run, total useful CPU time is . Each job of length is chopped into quanta, so total number of scheduling slices , each incurring one switch cost (per switch). Useful work per quantum , overhead per quantum .
Marks: correct ratio structure (2), independent of observation / assumptions stated (2).
(b) With , each job needs quanta. Under RR with jobs all present, jobs are served in cyclic order. The last job finishes after all quanta plus the switches between them. Number of context switches before the last quantum completes (a switch before each slice except we can neglect the leading one; standard convention counts one switch per slice boundary). Using one switch per served quantum:
More precisely the last job completes on the final quantum, so:
Either accepted with justification. Marks: recognise all quanta precede last completion (2), useful term (1), overhead term (2).
(c) As , each job's requirement , so each job runs to completion in a single quantum with no preemption — the scheduler serves job 1 fully, then job 2, etc. This is exactly FCFS. Turnaround of job (ignoring ) . Average:
For small , all jobs finish at nearly the same time (round-robin makes them progress together), giving average turnaround — much worse for identical jobs. Trade-off: large (→FCFS) minimizes average turnaround for identical jobs but harms response time / fairness; small improves responsiveness but raises overhead and turnaround. Marks: prove degeneration to FCFS (2), (2), comparison + trade-off sentence (2).
(d) Efficiency . : quanta per job , total quanta . ms. (Using the convention: ms — either accepted.) Marks: efficiency (2), (3).
Question 2
(a) counter++ = load, increment, store (three steps, non-atomic). If both threads read the same old value before either stores, one increment is lost (lost update). Minimum final value: with operations each, interleavings can make both threads repeatedly clobber each other. The smallest achievable value is 2 (not lower):
- Thread A loads 0.
- Thread B runs all increments to completion → counter = ... but A still holds 0.
Tight-minimum construction: Interleave so each store overwrites, but note counter must reach at least from one thread's run then be knocked back. Classic result: the minimum is 2. Construction: A reads 0. B does increments (counter=). A does its remaining loop reading/writing... Standard proof: one thread can hold a stale value spanning the other's entire run, but each of its own writes advances counter, so the losing thread still contributes; the theoretical floor is 2 (each thread guarantees at least one uncontested final store contributing +1, and one full run can be entirely masked to net +1). Accept: min with a valid load/add/store interleaving; proof that it cannot be 0 or 1 because the last thread to store writes at least (its own last read + M-progress ≥ 1), and two threads' final stores give ≥2. Marks: lost-update mechanism (3), min value 2 (2), valid interleaving + tightness (2).
(b) (i) Mutual exclusion — YES. test_and_set atomically reads old value and sets to 1; only the thread that observes old value 0 enters. Atomicity guarantees at most one winner.
(ii) Progress — YES. If lock is free (0) and threads are contending, one will succeed (some thread's TAS reads 0); no thread outside its CS blocks entry. No deadlock on a single lock.
(iii) Bounded waiting — NO. A spinning thread can be repeatedly overtaken; nothing enforces order/fairness. A thread could starve indefinitely while others keep re-acquiring.
Marks: 2 each with justification.
(c) Use semaphore sem initialised to .
P(sem) // wait
<region>
V(sem) // signal
A thread completes P (and enters) only after decrementing . Invariant: .
Number inside region (completed P) − (completed V) since .
Hence at most threads inside simultaneously. A thread cannot complete P when (it blocks), enforcing the bound.
Marks: correct P/V placement + init (3), invariant statement (2), derivation inside (2).
Question 3
(a) Page size → offset = 12 bits. Remaining bits for . Each page table must fit in one page (4096 B) with 4-byte PTEs → entries per table → 10 bits each. So , and bits. Split: . Marks: offset (2), entries per table (2), 10/10 split (2).
(b) A flat single-level table needs one PTE per virtual page over the entire space: entries × 4 B MB, regardless of usage. A two-level table allocates second-level tables only for used regions; unused slots point to nothing.
Numeric example: process uses 1 code page (low address, ) and 1 stack page (high address, ).
- Flat: MB (must be fully allocated).
- Two-level: 1 outer (top-level) table = 4 KB, plus 2 second-level tables (one for , one for ) = KB. Total KB. Saving: 4 MB → 12 KB. Marks: explanation sparse-alloc (2), flat = 4 MB (2), two-level = 12 KB (2).
(c) Total . Sum of Allocation . Available .
Need = Max − Allocation:
| P | Need |
|---|---|
| (7,4,3) | |
| (1,2,2) | |
| (6,0,0) | |
| (0,1,1) | |
| (4,3,1) |
Safety: Work=(3,3,2).
- Need(1,2,2)≤Work → run, Work=(3,3,2)+(2,0,0)=(5,3,2).
- Need(0,1,1)≤(5,3,2) → Work=(5,3,2)+(2,1,1)=(7,4,3).
- Need(7,4,3)≤(7,4,3) → Work=(7,4,3)+(0,1,0)=(7,5,3).
- Need(6,0,0)≤(7,5,3) → Work=(7,5,3)+(3,0,2)=(10,5,5).
- Need(4,3,1)≤(10,5,5) → Work=(10,5,7).
Safe sequence: . State is safe.
Request : Check ✓ and ✓. Tentatively: Available ; ; . Re-run safety with Work=(2,3,0):
- Need(0,1,1)? C-component 1>0 → no. Need(0,2,0)≤(2,3,0) → Work=(2,3,0)+(3,0,2)=(5,3,2).
- Need(0,1,1)≤(5,3,2) → Work=(7,4,3).
- (7,4,3)≤(7,4,3) → (7,5,3); (6,0,0)≤ → (10,5,5); (4,3,1)≤ → (10,5,7). ✓ Request CAN be granted (safe sequence ). Marks: Available (2), Need matrix (2), safe sequence (2), request analysis (2).
[
{"claim":"RR efficiency q/(q+s)=0.8 for q=2,s=0.5","code":"q=2; s=Rational(1,2); eff=q/(q+s); result=(eff==Rational(4,5))"},
{"claim":"T_last=40ms for N=4,T=8,q=2,s=0.5 (no -1 convention)","code":"N=4;T=8;q=2;s=Rational(1,2); Tlast=N*T+(N*T/q)*s; result=(Tlast==40)"},
{"claim":"FCFS avg turnaround T(N+1)/2 equals 20 for N=4,T=8","code":"N=4;T=8; avg=T*(N+1)/2; result=(avg==20)"},
{"claim":"Banker Available=(3,3,2)","code":"tot=[10,5,7]; alloc=[[0,1,0],[2,0,0],[3,0,2],[2,1,1],[0,0,2]]; s=[sum(a[i] for a in alloc) for i in range(3)]; avail=[tot[i]-s[i] for i in range(3)]; result=(avail==[3,3,2])"},
{"claim":"Two-level PT for 2 pages = 12KB vs flat 4MB","code":"flat=2**20*4; two=4096+2*4096; result=(flat==4194304 and two==12288)"}
]