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:
BufferedInputStreamexists to turn a million 1-byteread()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 — whySystem.nanoTime()is cheap. - Observability:
straceshows the syscall boundary of any process — the fastest way to answer 'what is this black-box binary actually doing?'.
// 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.