What an operating system does

Kernel versus user space, privilege rings, what a system call really costs, and how an ordinary library call becomes a trap into kernel code.

Two worlds and a guarded boundary

The kernel runs with full access to hardware, memory and devices. Ordinary programs run in user space with none of it, and reach the kernel only through defined entry points. That separation is the whole reason one crashing program does not take the machine down.

ResponsibilityKernelUser space
Memory mapping and protectionOwns page tablesRequests mappings
CPU sharingSchedules threadsYields and blocks
Device accessDrivers and interruptsUses files and ioctls
File systemsImplements and cachesReads and writes via descriptors
NetworkingStacks and socketsUses the socket API
Process isolationEnforcesCannot bypass
  • On x86-64 the CPU has four privilege rings; the kernel uses ring 0 and applications ring 3.
  • A trap or a hardware interrupt is what transfers control, and the CPU switches stacks as part of it.
  • Modern kernels support unprivileged user-space device access and user-space network stacks, which move work out of the kernel rather than changing the model.
  • A monolithic kernel keeps drivers inside the kernel; a microkernel pushes services into user space for isolation at the cost of more messages.

What a system call costs

#include <unistd.h>

// a library call that becomes a system call
ssize_t n = write(1, "hello\n", 6);

// under the hood on x86-64 Linux:
//   rax = 1            (the syscall number for write)
//   rdi = 1, rsi = buf, rdx = 6
//   syscall            (the instruction that traps into the kernel)
//   the return value arrives in rax, and errno is set on failure
OperationRough costNote
Function call1-2 nsNo privilege change
System call (simple)100-500 nsTrap plus entry and exit work
System call under Spectre mitigationsNoticeably moreMitigations add overhead to every entry
Context switch1-10 usScheduler plus TLB effects
Read from page cacheHundreds of nsOne system call per read
Read from diskMicroseconds to millisecondsDominated by the device
# count the system calls a program makes
strace -c ls >/dev/null
# % time     seconds  usecs/call     calls    errors syscall
#  ...
#           0.001234          12      1000           read

# see them as they happen, with arguments
strace -f -e trace=openat,read,write -o /tmp/trace.txt ls

Batching matters. A thousand read calls of one byte each cost far more than one call of a thousand bytes, because the trap is the cost, not the copying.

From library call to kernel work

  1. The application calls a C library function such as fopen or send.
  2. The library may serve it entirely in user space, as buffered I/O does for a small read.
  3. If it needs the kernel, the library places the syscall number and arguments in registers and executes the trap instruction.
  4. The CPU switches privilege level, saves state and vectors to the kernel entry point.
  5. The kernel validates arguments, checks permissions, performs the work, and sets a return value.
  6. Control returns to user space with the result, and the library maps an error code into errno.
💡
The libc function and the system call are not the same thing. printf is not write, and malloc is usually not mmap. Code that treats them as interchangeable will be surprised by buffering, by the absence of a syscall, or by both.

FAQ

Do all system calls cost the same?
No. A simple getpid is far cheaper than a network operation that blocks, waits for a device and wakes another process. The trap is the floor, not the total.
How do I see what a program is asking the kernel for?
Use strace on Linux, dtruss on macOS or Process Monitor on Windows. Starting with strace -c gives a summary of the calls that dominate.

Monitoring and troubleshooting Inter-process communication

Last refreshed 2026-09-18.