System Calls in Practice: Building a Small Syscall Library

Building an Operating System Kernel: Concurrency, I/O & Synchronization

Chapter 9 · System Calls in Practice: Building a Small Syscall Library

Every mechanism this course has built — the VFS, the disk driver, real processes — has been called directly, as ordinary Python objects. This chapter puts one real boundary in front of all of it: a genuine syscall trap, reusing Course 1's own InterruptTable, through which every one of these calls must now pass. Then it builds the two syscalls that make a process able to launch other processes at all.

Finding 1: Real read/write, Dispatched Through a Genuine Interrupt Trap

class Syscalls: def invoke(self, pid, call, *args): # the ONE real entry point -- 'user-level' code never calls # kernel internals or the VFS directly return self.interrupts.dispatch(SYSCALL_INTERRUPT, pid, call, *args) def _trap(self, pid, call, *args): method = getattr(self, f'_do_{call}') return method(pid, *args)
Verified directly — open/write/read, correctly round-tripped through the real trap
Every call — open('/greeting'), write(fd, 0, b'HELLO SYSCALL'), read(fd, 0, 13) — went through syscalls.invoke(), which dispatches a real interrupt via Course 1's own InterruptTable. User-level code never touched the VFS or kernel directly. Read-back: b'HELLO SYSCALL', exactly correct.

Finding 2: fork() — A Genuinely Independent Child

Verified directly — a copied, not shared, file descriptor table
A parent opens a file and writes to it, then forks. The child correctly inherits the parent's own open file descriptor table — but as a genuine copy, not a shared reference. The child then opens a second file of its own; the parent's own table is completely unaffected. Real, verified independence, not superficial resemblance.

Finding 3: exec() — Same Identity, Different Program

Verified directly — the PID and PCB object are unchanged; the program is completely replaced
A process's own PID and real PCB object, checked directly before and after exec(): identical (is, not just equal). But its own scheduled program: completely replaced, with zero trace of the old one. This is genuinely the same process — real exec() semantics: same identity, entirely different code.

Finding 4: fork() Then exec() — The Classic Pattern, Chained

Verified directly — a genuinely new task launched from an existing running process
A parent forks a child, then exec()s the child — not itself — with an entirely different program. The parent's own program stays completely untouched throughout. Two syscalls composed together doing something neither does alone: launching a genuinely new task from an existing running process, the real pattern every Unix shell uses to run a command.

Where This Connects

This chapter's findingWhat it connects to
The syscall trap itselfCourse 1 Chapter 6's own InterruptTable and Syscall class — the exact same "never expose internals directly" boundary, reused directly
open/read/writeChapter 8's own VFS — the syscall library never touches a backend directly, only ever through the VFS's own uniform interface
fork() creating a real, new processCourse 1 Chapter 4's own Kernel.create_process() — reused unchanged for the actual process creation
Every syscall composing correctly with the othersoskernel1's own capstone — the same test this entire course has been building toward: do the independently-verified pieces actually work together

Hands-On Exercises

Exercise 1

Have one process open and write to a file, then have a completely different process attempt to read() using the exact same fd number, without ever calling open() itself. Confirm it fails, and explain why fd numbers aren't shared across processes.

📄 View solution
Exercise 2

Fork a process, then fork the resulting child to create a grandchild. Confirm the grandchild correctly inherits both the original file and anything the direct parent opened afterward, and explain why it inherits from its own parent rather than some fixed original ancestor.

📄 View solution
Exercise 3

Open a file, then call exec() on the same process. Confirm whether the previously-opened file descriptor still works afterward, and explain honestly what this implementation does and doesn't model about real exec() semantics.

📄 View solution

Chapter 9 Quick Reference

  • Syscalls: one real entry point, invoke(), dispatching a genuine interrupt — no direct kernel/VFS access from user-level code
  • Verified Finding 1: open/read/write correctly round-trip through the real trap and the VFS underneath
  • Verified Finding 2: fork() produces a genuinely independent child — a copied fd table, not a shared one
  • Verified Finding 3: exec() preserves a process's own identity exactly while completely replacing its program
  • Verified Finding 4: fork()+exec() chained together launches a genuinely new task from an existing process — the real Unix pattern
  • Golden rule: a syscall boundary is only real if user-level code has no path around it — every mechanism this course built stays correctly isolated behind one single trap
  • Next chapter: Capstone — every subsystem from both courses wired together into one small, multitasking OS running real concurrent programs