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.
| Responsibility | Kernel | User space |
|---|---|---|
| Memory mapping and protection | Owns page tables | Requests mappings |
| CPU sharing | Schedules threads | Yields and blocks |
| Device access | Drivers and interrupts | Uses files and ioctls |
| File systems | Implements and caches | Reads and writes via descriptors |
| Networking | Stacks and sockets | Uses the socket API |
| Process isolation | Enforces | Cannot 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| Operation | Rough cost | Note |
|---|---|---|
| Function call | 1-2 ns | No privilege change |
| System call (simple) | 100-500 ns | Trap plus entry and exit work |
| System call under Spectre mitigations | Noticeably more | Mitigations add overhead to every entry |
| Context switch | 1-10 us | Scheduler plus TLB effects |
| Read from page cache | Hundreds of ns | One system call per read |
| Read from disk | Microseconds to milliseconds | Dominated 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 lsBatching 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
- The application calls a C library function such as
fopenorsend. - The library may serve it entirely in user space, as buffered I/O does for a small read.
- If it needs the kernel, the library places the syscall number and arguments in registers and executes the trap instruction.
- The CPU switches privilege level, saves state and vectors to the kernel entry point.
- The kernel validates arguments, checks permissions, performs the work, and sets a return value.
- 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.Related
Monitoring and troubleshooting Inter-process communication
Last refreshed 2026-09-18.