Exercises — System calls — user mode vs kernel mode, trap mechanism
Everything here reuses only what the parent built: the mode bit (a hardware flag saying "user" or "kernel"), the trap (the one legal instruction that flips that bit and jumps to a fixed kernel address), the syscall number (an index into the kernel's trusted table), and the cost model where each is a duration in CPU cycles. If a symbol appears below, it was earned above.
Level 1 — Recognition
Recall Solution 1.1
Recall the parent's three doors: trap = synchronous and intentional; exception = synchronous but accidental; interrupt = asynchronous, from outside hardware.
- (a) Trap — you deliberately executed
syscall. - (b) Exception (fault) — an instruction went wrong, not requested.
- (c) Interrupt — the keyboard is external hardware, unrelated to your current instruction.
- (d) Exception (fault) — synchronous (caused by this load instruction) but unintended. (See Interrupts and Exceptions and Virtual Memory and Page Tables.)
- (e) Interrupt — the timer chip is asynchronous hardware.
Recall Solution 1.2
- (a) False. Same CPU, same registers — only the mode bit differs. "Supervisor" is a state, not extra silicon.
- (b) True. The instruction exists in the instruction set; in user mode the hardware turns its execution into a fault instead of running it.
- (c) False. A syscall always does a mode switch (same process, into the kernel). It only sometimes triggers a context switch — e.g. if it blocks.
Level 2 — Application
Recall Solution 2.1
mov rax, 39 ; syscall number -> the index into the kernel's trusted table
syscall ; the trap: flips mode bit, jumps to fixed kernel entry
; rax now holds the returned PID
Why rax for the number? The ABI fixes rax as the number register so both sides agree without negotiation.
Why syscall and not call? A call stays in user mode and jumps to a user address; only syscall is allowed to flip the mode bit and land at the pre-registered handler.
Why does the answer arrive in rax? The same ABI fixes rax as the return register.
Recall Solution 2.2
Buffered: all 1000 formats are user-space work — they cross nothing. Only the single final write crosses. Boundary cost cycles.
Unbuffered: every printf ends in its own write, so crossings: cycles.
Ratio: more boundary cost. The formatting ( cycles) is identical in both — buffering changes only the count of traps, which is exactly the parent's 80/20 insight. (This is the seed of io_uring and Batched I/O.)
Recall Solution 2.3
B → E → C → F → A → D. Reason each hand-off: you must set up the request (B) before triggering it (E); the trap instruction only starts the atomic hardware transfer (C); the kernel can only run after it has control, and must validate before trusting (F); it computes and stashes the result (A); finally return-from-trap flips the mode back (D). See the figure below.

Level 3 — Analysis
Recall Solution 3.1
Speedup: As the constant becomes negligible: . So batching this workload can be at most 2.5× — the crossing was only 300 of the 500 cycles per call. Analysis point: batching helps a lot when dominates ; here the work is heavy, so the ceiling is modest.
Recall Solution 3.2
cycles. cycles. Speedup . Notice it is well below the ceiling of , because at small the single saved crossing is a smaller fraction of the total.

Recall Solution 3.3
The other terms (, , ) are paid inside the syscall. But while the kernel ran, it touched its own code and data, evicting your program's data from the CPU caches and its address translations from the TLB. After sysret, your code resumes but its next memory accesses now miss and must refetch from slower memory. That penalty is charged after the syscall returns, so a "cheap" syscall can silently slow down the code that follows it. This delayed, invisible cost is the strongest argument for reducing syscall count.
Level 4 — Synthesis
Recall Solution 4.1
- (a) Mode switch only — same process, user → kernel via the trap.
- (b) Context switch — the kernel voluntarily hands the CPU to a different process (see Process Scheduling). Your process stays in kernel mode, sleeping; another process starts running. This is why blocking I/O doesn't freeze the machine.
- (c) Neither for your process directly — the interrupt is a mode switch for whatever process was running, and its handler just flips a flag ("runnable"); your process isn't executing yet.
- (d) Both — a context switch back into your process, and it finishes in kernel mode then does a mode switch out to user as
readreturns. Synthesis point: one logicalreadcall spans multiple mode switches and multiple context switches, spread across time and across other processes. A syscall is a story, not a single instant.
Recall Solution 4.2
Why fixed: the whole protection wall relies on user code entering the kernel only at a point where validation happens first. If user space supplied the entry address, a malicious program could point it past the validation code — for example straight into the routine that writes a disk sector, skipping the "is this pointer legal?" checks.
What would break: the invariant "no privileged action runs without a check" collapses; the mode bit would still flip to kernel, but now with attacker-chosen code executing at full privilege. So the kernel registers one entry address at boot (the trap handler), and the syscall instruction hard-wires the jump there — user code cannot influence where it lands, only what number it requests. This is the same principle as ring 0 being unreachable except through guarded gates.
Level 5 — Mastery
Recall Solution 5.1
(a) Fixed overhead cycles, so (b) Overhead equals work when cycles. (c) With :
- Unbatched: cycles.
- Batched: cycles. Speedup . Batching pays one overhead instead of eight.
Recall Solution 5.2
- (a) General-protection fault (an exception, not a trap). The mode bit says "user," the silicon refuses
HLT, and control goes to the kernel's fault handler — which typically kills the process with a signal. No privileged action occurs. - (b) The kernel's validation step (step F) sees the index is outside the table bounds and returns an error (
-ENOSYSinrax), without dispatching. This is exactly why the number must be validated before use. - (c) The number is fine, so the kernel dispatches, but when it tries to touch
bufit hits an unmapped page → the kernel's access faults, is caught, and the syscall returns-EFAULT. The wall protected the kernel from a bad user pointer. - (d) There is no mode switch to make (already kernel), but the crossing overhead of a full user→kernel trap is avoided; in practice the kernel calls its service function directly rather than issuing
syscall. The key invariant — validation before privileged work — still holds. - (e) You still pay the entire fixed overhead () even though . This is the sharpest proof that the crossing itself costs, independent of the work — the whole motivation for avoiding needless syscalls.
Recall Solution 5.3
Empty syscall cost cycles. Useful syscall cost cycles. Number of empty syscalls fitting in one useful one: . So eleven pointless boundary crossings cost as much as one real, heavy syscall. Interpretation: overhead is not "free rounding error" — a program that sprays tiny syscalls can burn its budget on nothing but doorways.
Recall One-line self-check before you leave
Trap vs fault vs interrupt? ::: Trap = synchronous + intentional; fault = synchronous + accidental; interrupt = asynchronous. Why a syscall number instead of an address? ::: The kernel trusts only its own table index, never a user-supplied jump target. Mode switch vs context switch? ::: Mode switch stays in one process (privilege changes); context switch swaps processes. Where does batching's speedup ceiling come from? ::: The ratio of crossing overhead to real work — bounded, not infinite.