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
- Why This Border Exists: The Protection Problem
- System Calls: What They Actually Do
- The Journey of a System Call
- System Calls in the Wild
- See It For Yourself: System Call Tracing
- System Call Overhead: The Hidden Cost
- Modern Trends in System Call Design
- Coming Up Next
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:
-
Your Application Makes the Call: Your word processor calls
write(file_descriptor, buffer, bytes_to_write)
-
Library Wrapper Prepares Parameters: The C library function packages your parameters and places them in the right registers or on the stack
-
Trap Instruction Executed: A special CPU instruction (like
syscall
on x86-64,svc
on ARM) triggers the mode switch from user to kernel mode -
Kernel Mode Transition: The CPU switches the mode bit and jumps to a predefined handler address in the kernel
-
System Call Handler Activates: The kernel’s system call dispatcher checks the system call number and verifies the parameters
-
Kernel Service Execution: The appropriate kernel function is called to perform the actual work (writing data to disk)
-
Return Preparation: Results and any error codes are prepared for return to user mode
-
Mode Switch Back: The kernel executes a special instruction to return to user mode
-
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()
, andwait()
system calls in detail, along with examples of how to use them in C. We will also cover theread()
andwrite()
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 connectionconnect()
to connect to the web serverread()
andwrite()
to exchange dataclose()
to end the connection
Taking a Screenshot
When you capture your screen, the process involves:
ioctl()
to communicate with the display driverread()
to get the pixel dataopen()
andwrite()
to save the image
Playing Music
Your music player uses:
open()
to access the audio fileread()
to get audio dataioctl()
to configure audio hardwarewrite()
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:
- Mode switching: CPU must flush pipelines and caches
- Parameter validation: Kernel must verify all parameters for security
- Memory copying: Data often must be copied between user and kernel space
- 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
Modern Trends in System Call Design
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.