Interview Prep Zoneby CuriouserLabs

Question 5 of 5 · senior level

What is a system call? Walk through what happens between user mode and kernel mode.

🎤 Say this first

User code can't touch hardware, other processes' memory, or privileged CPU state — it runs in user mode (an unprivileged CPU ring). A system call is the controlled gate into kernel mode: the process puts the syscall number + args in registers and executes a trap instruction (syscall); the CPU switches to the kernel's entry point with kernel privileges, validates the arguments, does the work, and returns to user mode with a result. It's a function call across a privilege boundary — same thread, no context switch, but 10–100× the cost of a normal call, which is why syscall-heavy code batches.

The full picture

  • Why the boundary exists: protection is enforced by the CPU (ring 0 vs ring 3), not convention — user code literally cannot execute privileged instructions or address kernel memory.
  • Cost anatomy: mode transition + register save + Spectre/Meltdown mitigations (KPTI made syscalls measurably slower in 2018 — real production regressions) — roughly 100ns–1µs vs ~1ns for a call.
  • Batching as the universal answer: BufferedInputStream exists to turn a million 1-byte read() syscalls into thousands of 8KB ones; epoll batches readiness; io_uring batches submission and completion via shared-memory rings — the modern endpoint of syscall-avoidance.
  • vDSO: the kernel maps a tiny library into every process so hot read-only calls (gettimeofday, clock_gettime) don't trap at all — why System.nanoTime() is cheap.
  • Observability: strace shows the syscall boundary of any process — the fastest way to answer 'what is this black-box binary actually doing?'.
The boundary, visible from Java
// Every one of these crosses into the kernel:
FileInputStream in = new FileInputStream("data.bin");   // open(2)
in.read();                                              // read(2) — 1 byte, full syscall!
socket.getOutputStream().write(buf);                    // write(2)

// Which is why the idiom is:
var buffered = new BufferedInputStream(in, 8192);       // 8192 fewer traps
// and why NIO, epoll-based Netty, and io_uring exist at all.

🔄 Likely follow-up questions

  • What did Meltdown/KPTI change about syscall cost?
  • How does io_uring reduce syscalls to near zero for I/O-heavy workloads?
  • Where do library calls end and syscalls begin — what does strace actually show?