The surprising truth about Linux syscalls is that the kernel doesn’t actually "run" your userspace code; it merely facilitates its execution and handles its requests.

Let’s see this in action. Imagine we want to write a file.

# On the host, create a file
echo "hello world" > my_file.txt

# Now, let's trace the syscalls involved when a program reads this file
strace cat my_file.txt

You’ll see a flurry of system calls: openat, read, write, close, and many others. Each of these is a request from your userspace program (like cat) to the Linux kernel.

At its core, the Linux kernel is a privileged piece of software that manages your system’s hardware and resources. Userspace programs, on the other hand, run in a less privileged mode. They can’t directly access hardware or critical system data. To do anything that requires this level of access – like reading a file, creating a process, or allocating memory – userspace programs must ask the kernel for help. This "asking" is done through system calls, or syscalls.

Think of syscalls as a well-defined API between userspace and the kernel. Your program doesn’t know how the kernel manages the file system on disk, but it knows it can ask the kernel to read a certain number of bytes from a specific file descriptor. The kernel, with its privileged access, performs the actual operation and returns the result (or an error) back to your program.

The interface for making these calls is surprisingly consistent across different programs. When you call a C library function like read() or write(), that library function is often just a wrapper. It sets up specific registers with the syscall number and arguments, then triggers a special instruction (like syscall on x86-64 or int 0x80 on older architectures) that causes a switch from userspace mode to kernel mode. The kernel then looks up the syscall number, executes the corresponding kernel function, and returns control back to userspace, placing the result in another register.

The set of available syscalls defines the boundary and the capabilities of what userspace programs can do. For example, fork() creates a new process by duplicating the current one. execve() replaces the current process image with a new program. mmap() maps files or devices into memory. Each syscall is a specific, documented way for the kernel to expose a particular service.

The kernel doesn’t just expose raw functionality; it also provides structured interfaces for interacting with devices and kernel features. This includes things like procfs (/proc) and sysfs (/sys). These are virtual file systems that the kernel populates with information about running processes, kernel parameters, and hardware devices. Reading from a file in /proc/meminfo or writing to a file in /sys/class/net/eth0/speed are also mediated by syscalls, allowing userspace to query and control kernel state.

A common misconception is that the kernel manages your program’s memory directly. In reality, the kernel manages virtual memory spaces. When your program asks for memory (e.g., via malloc, which eventually uses brk or mmap syscalls), the kernel sets up page tables that map your program’s virtual addresses to physical RAM. The kernel doesn’t know or care what your program does with that memory; it only ensures that your program can only access memory within its allocated virtual address space and that it’s properly translated to physical addresses.

The true power of the syscall interface lies in its abstraction and protection. It allows a vast ecosystem of userspace applications to run reliably and securely on top of a single, shared kernel. The kernel enforces resource limits, manages concurrency, and prevents one misbehaving program from crashing the entire system.

The next logical step after understanding how programs request services from the kernel is to explore how programs communicate with each other, a mechanism deeply tied to these kernel-provided interfaces.

Want structured learning?

Take the full Linux & Systems Programming course →