System Calls: The Bridge Between User Space and Kernel Space

Aayush Shukla's avatar
Aayush Shukla
May 21, 2025 7 min read

Introduction

This post lays a foundation for a lot of future content. Understanding system calls is crucial for grasping how operating systems work. Read this to get a solid grasp of how your applications interact with the kernel. I’ve tried to keep it simple and straightforward, so you can focus on the core concepts without getting lost in technical jargon, while keeping it interesting and engaging.

In our previous posts, we’ve explored how your operating system manages multiple processes and decides which one gets CPU time next. But there’s something fundamental we haven’t addressed yet: How do these processes actually request services from the operating system? How does your web browser ask for a network connection, or your word processor save a file to disk?

The answer lies in system calls — the essential interface between your applications and the powerful, privileged world of the operating system kernel.

Table of Contents

The Boundary Between Two Worlds

There is a very cool video on this whole concept by Core Dumped. I highly recommend watching it for a more visual explanation of the concepts discussed in this post. It also covers some advanced topics that we will discuss in future posts.

Modern operating systems maintain a strict separation between two operational modes:

  • User Mode: Where your everyday applications run, with limited privileges and restricted access to hardware
  • Kernel Mode: Where the operating system core operates, with full access to hardware, memory, and system settings

This separation isn’t just a theoretical construct — it’s enforced by your CPU hardware with a special mode bit that determines whether code can execute privileged instructions.

System calls are the formal, controlled crossing points between these two worlds. They’re like border checkpoints where applications can request specific, pre-approved services from the kernel without getting full access to its privileged domain.

Why This Border Exists: The Protection Problem

You might wonder: why not just let applications directly access whatever they need? The answer becomes obvious when you consider what would happen:

  • Your music player could accidentally overwrite your tax documents in memory
  • A buggy game could crash your entire system by writing bad values to hardware registers
  • A malicious application could read sensitive data from other applications
  • Any program could monopolize your CPU, network, or disk

The user/kernel boundary is one of the most important security and stability mechanisms in your computer. Without it, one misbehaving program could bring down your entire system or compromise all your data.

System Calls: What They Actually Do

These system calls are for the Unix/Linux operating system. The Windows operating system has its own set of system calls, which are different from the Unix/Linux ones. However, the concepts are similar.

System calls provide a way for processes to request specific kernel services without giving them unrestricted access. Common categories include:

1. Process Control

  • Create/terminate processes (fork, exit)
  • Load/execute programs (exec)
  • Get/set process attributes (getpid, nice)
  • Wait for events (wait, sleep)
  • Allocate/free memory (malloc, free)

2. File Management

  • Create/delete files (open, close)
  • Open/close files (open, close)
  • Read/write files (read, write)
  • Change file attributes (chmod, chown)

3. Device Management

  • Request/release devices (ioctl)
  • Read/write to devices (read, write)
  • Get/set device attributes (ioctl)

4. Information Maintenance

  • Get/set time (time, settime)
  • Get/set system data (gethostname, sethostname)
  • Get/set process, file, or device attributes

5. Communication

  • Create/delete communication connections (socket, close)
  • Send/receive messages (send, recv)
  • Transfer status information (stat, fcntl)

6. Protection

  • Get/set file permissions (chmod, umask)
  • Get/set user IDs (getuid, setuid)

The Journey of a System Call

Let’s trace what happens during a typical system call, using the example of a program saving a file:

  1. Your Application Makes the Call: Your word processor calls write(file_descriptor, buffer, bytes_to_write)

  2. Library Wrapper Prepares Parameters: The C library function packages your parameters and places them in the right registers or on the stack

  3. Trap Instruction Executed: A special CPU instruction (like syscall on x86-64, svc on ARM) triggers the mode switch from user to kernel mode

  4. Kernel Mode Transition: The CPU switches the mode bit and jumps to a predefined handler address in the kernel

  5. System Call Handler Activates: The kernel’s system call dispatcher checks the system call number and verifies the parameters

  6. Kernel Service Execution: The appropriate kernel function is called to perform the actual work (writing data to disk)

  7. Return Preparation: Results and any error codes are prepared for return to user mode

  8. Mode Switch Back: The kernel executes a special instruction to return to user mode

  9. Application Continues: Your word processor receives the result (number of bytes written or an error code)

All of this happens in microseconds, but it’s a critical boundary crossing with significant security implications.

System Calls in the Wild

We will have a separate post on how to make system calls in C, but for now, let’s focus on the concept of system calls and how they work. In the separate post, we will cover the fork(), exec(), and wait() system calls in detail, along with examples of how to use them in C. We will also cover the read() and write() system calls, which are used for file I/O.

Let’s observe some common system calls you’re using right now without realizing it:

Web Browsing

When you open a web page, your browser makes system calls to:

  • socket() to create a network connection
  • connect() to connect to the web server
  • read() and write() to exchange data
  • close() to end the connection

Taking a Screenshot

When you capture your screen, the process involves:

  • ioctl() to communicate with the display driver
  • read() to get the pixel data
  • open() and write() to save the image

Playing Music

Your music player uses:

  • open() to access the audio file
  • read() to get audio data
  • ioctl() to configure audio hardware
  • write() to send audio data to the sound system

See It For Yourself: System Call Tracing

Want to witness these system calls in action? Most operating systems provide tools to trace them:

On Linux: strace

Open a terminal and type:

strace -c firefox

This will launch Firefox and count all the system calls it makes.

On macOS: dtruss (requires disabling System Integrity Protection)

sudo dtruss -c safari

On Windows: Process Monitor from Sysinternals

This graphical tool can filter and display system calls made by any process.

System Call Overhead: The Hidden Cost

Every system call involves a context switch between user and kernel mode, which isn’t free:

  1. Mode switching: CPU must flush pipelines and caches
  2. Parameter validation: Kernel must verify all parameters for security
  3. Memory copying: Data often must be copied between user and kernel space
  4. Scheduling impacts: System calls can trigger scheduling decisions

This is why high-performance applications try to minimize system calls by:

  • Batching operations when possible
  • Using memory-mapped files instead of read/write calls
  • Employing larger buffers to reduce call frequency

System call interfaces continue to evolve:

1. System Call Batching

Modern operating systems allow batching multiple system calls to reduce mode-switching overhead (like Linux’s io_submit and Windows’ I/O completion ports).

2. Asynchronous System Calls

Rather than blocking until an operation completes, asynchronous calls let applications continue running while the kernel processes the request.

3. Extended APIs

More sophisticated interfaces like Linux’s epoll and Windows’ IOCP provide efficient ways to monitor multiple I/O sources.

4. Containerization Extensions

System calls related to containerization and isolation (like Linux’s namespaces) enable new patterns of application deployment.

Security Implications: System Call Filtering

System calls are increasingly recognized as a security boundary. Modern approaches include:

  • Seccomp-BPF on Linux: Allows filtering which system calls a process can make
  • Pledge and Unveil on OpenBSD: Lets processes voluntarily restrict their system call capabilities
  • System Call Disable Policy on macOS: Limits the system calls available to applications

This “least privilege” approach means processes can only use the system calls they genuinely need, reducing the potential damage from security breaches.

Coming Up Next

Now that we understand how applications communicate with the kernel, our next post will explore how the outside world gets the kernel’s attention through Interrupts. We’ll see why your keyboard input gets priority over background tasks, and how hardware events trigger immediate responses in the operating system.