Module 1 · Unit 1 · Lesson 4 of 5
System Calls
The mechanism by which a program requests a service from the kernel — and the hardware-supported boundary that separates user space from kernel space.
01 Introduction
Lesson 1.3 catalogued the services an operating system provides. This lesson examines the interface through which those services are requested: the system call. System calls are the only legitimate way for a user-space program to ask the kernel to do something on its behalf.
The system call is more than a function call. It involves a deliberate, hardware-mediated transition from the unprivileged mode in which applications run to the privileged mode in which the kernel runs. This transition — sometimes called a mode switch, trap, or context switch in the loose sense — is central to how operating systems enforce protection and isolation.
Understanding system calls is essential for two reasons. First, they
are the mechanism through which every program exercises OS
services, so knowing them is knowing the OS's actual interface.
Second, system-call overhead is a real performance factor, and
tools like strace make that overhead visible.
02 Learning objectives
By the end of this lesson you will be able to
- Explain system calls.
- Distinguish APIs from system calls.
- Explain user mode and kernel mode.
- Explain traps and interrupts.
- Use Linux tools to observe system calls.
This lesson contributes to U1.4, U1.3, and course learning outcome CLO1. It also prepares the ground for CLO6.
03 Required reading
- Operating System Concepts — Chapter 2, Section 2.3, System Calls.
-
OSTEP — Chapter 4, The Abstraction: The
Process. The
fork,exec, andwaitexamples are direct illustrations of system-call programming. -
Linux man pages:
man 2 syscall,man 2 read,man 2 write,man 1 strace.
04 Core content
4.1 What a system call is
A system call is a request from a user-space program to the operating-system kernel to perform a privileged operation on the program's behalf. The operation might be reading a file, creating a process, allocating memory, sending a packet, or querying the current time. Whatever the operation, it requires the kernel's authority.
A user-space program cannot perform these operations itself for a simple reason: the hardware does not allow it. The CPU has at least two privilege levels — user mode and kernel mode — and instructions that manipulate device registers, alter page tables, or configure interrupts are available only in kernel mode. To reach kernel mode, the program must ask the kernel to switch for it. The system call is that request.
4.2 User mode and kernel mode
The distinction between user mode and kernel mode is enforced by the CPU. Modern processors have at least two privilege levels, sometimes called rings:
| Level | Name | What runs there | Privileged instructions |
|---|---|---|---|
| Ring 0 | Kernel mode | The kernel, its device drivers, loadable modules | Available |
| Ring 1 | Historically for device drivers; rarely used on modern systems | Available | |
| Ring 2 | Historically for device drivers; rarely used on modern systems | Available | |
| Ring 3 | User mode | Applications, libraries, services | Blocked — causes a trap if attempted |
In practice, almost everything runs either in ring 0 or ring 3. The intermediate rings exist for historical reasons and are not meaningfully used by mainstream operating systems today.
Two consequences follow from this design:
- A bug in user-space code can only damage that process; it cannot corrupt the kernel.
- Kernel code is trusted absolutely. A bug in the kernel can bring down the entire machine or compromise security.
4.3 APIs versus system calls
Most programs never issue a system call directly. Instead, they call a function in a library — usually the C standard library, libc — which issues the system call internally. The interface the programmer sees is the API; the interface the kernel sees is the system call.
The two are not the same. Consider two very different cases:
- One-to-one:
read() -
The libc wrapper for
readis essentially a thin veneer: it moves the arguments into the correct registers and executes the trap instruction. One API call maps to one system call. - Many-to-many:
printf() -
The
printfAPI can be implemented with very different system-call patterns depending on buffering. On the first call with buffered output,printfmay issue no system call at all. Later, when the buffer fills or is flushed, it issues a singlewrite. A program that callsprintfa thousand times may issue only a handful of system calls.
Why the distinction matters
The C library provides a portable, convenient interface that abstracts over kernel differences. The same program can compile and run on Linux, macOS, and FreeBSD, even though the underlying system calls differ between them. Knowing the difference also matters for performance: a library call that does not touch the kernel is orders of magnitude cheaper than one that does.
4.4 Traps and interrupts
The mechanism that switches the CPU from user mode to kernel mode is
called a trap (or, on some architectures, a
software interrupt or supervisor call). A trap is
a synchronous event caused by the currently executing instruction.
The syscall instruction on x86-64 is the canonical
example: executing it transfers control to a fixed entry point in
the kernel and raises the privilege level to ring 0.
A hardware interrupt differs in one crucial respect: it is asynchronous. It is caused by an external device — a timer, a disk controller, a network card — and it can occur at any time, not just at a specific instruction. Interrupts also enter the kernel and raise the privilege level, but they are not initiated by the running program.
| Characteristic | Trap (software) | Interrupt (hardware) |
|---|---|---|
| Cause | Executing a specific instruction | An external device signals the CPU |
| Timing | Synchronous — happens at a known instruction | Asynchronous — can happen at any time |
| Initiated by | The running program | A device |
| Typical use | System calls, faults, breakpoints | Timer ticks, I/O completion, network packets |
| Handler entry | Kernel trap handler, dispatched by syscall number | Kernel interrupt handler, dispatched by IRQ number |
Both mechanisms share the essential property of switching the CPU into kernel mode, saving the interrupted context, and vectoring to a handler. Both return to user mode when the handler completes. The distinction between them is one of origin and timing, not of privilege level.
4.5 The system-call sequence in detail
It is worth tracing a single system call end to end. Suppose a
program calls read(fd, buf, 4096).
The numbered steps in the figure correspond to what actually happens on a modern Linux system. Three details are worth emphasising:
-
The syscall number matters. The kernel does not
know which system call was requested from the trap instruction
alone. The caller places the syscall number in a designated
register (for example,
raxon x86-64), and the entry point uses it to index a table of handlers. -
Arguments pass in registers, not on the stack.
Stack switching is part of the trap sequence, so arguments are
passed via specific registers. On x86-64 Linux the first six
arguments go in
rdi,rsi,rdx,r10,r8, andr9. -
Return values encode errors. Linux system calls
return a non-negative value on success; on error they return
-errno. The libc wrapper translates this into the familiar-1return plus a globalerrnovariable.
4.6 Categories of system call
System calls group naturally by the service they provide. The categories mirror Lesson 1.3.
| Category | Representative calls |
|---|---|
| Process control | fork, execve, exit, wait4, kill, clone |
| File management | open, close, read, write, lseek, stat, unlink |
| Device management | ioctl, mmap, read, write |
| Information maintenance | getpid, alarm, sleep, time, sysinfo |
| Communications | pipe, socket, connect, sendto, recvfrom |
| Protection | chmod, chown, umask, setuid, capset |
4.7 Observing system calls on Linux
Linux provides several tools for observing the system-call interface at runtime. Two of them are essential.
strace- Traces the system calls made by a program. Each call is printed with its arguments and its return value. This is the single most useful tool for understanding what a program actually does at the kernel boundary.
ltrace-
Traces library calls rather than system calls. Useful for seeing
what happens above the syscall boundary — for example, how many
printfcalls a program makes before a singlewritereaches the kernel.
Two strace invocations to remember:
# Summary of system calls by count and time
strace -c ./myprogram
# Full trace with timestamps
strace -tt -T ./myprogram
The -c flag produces a summary table; the
-tt and -T flags in the second command add
timestamps and per-call durations. Reading these outputs is a skill
you will use throughout the course.
05 Worked example
Read and interpret the output of a real system-call trace.
Trace of a small file-reading program
Command:
strace -tt -e trace=openat,read,write,close cat /etc/hostname
Sample output (abbreviated):
12:30:41.112 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
12:30:41.113 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
12:30:41.115 openat(AT_FDCWD, "/etc/hostname", O_RDONLY) = 3
12:30:41.115 read(3, "myhost.example.local\n", 131072) = 21
12:30:41.116 write(1, "myhost.example.local\n", 21) = 21
12:30:41.116 read(3, "", 131072) = 0
12:30:41.116 close(3) = 0
Reading the trace line by line:
-
openat(AT_FDCWD, "/etc/ld.so.cache", ...) = 3— the dynamic linker opens the library cache. The return value3is a file descriptor. -
The second
openatloadslibc. Again, a new file descriptor is returned. -
The third
openatopens the requested file,/etc/hostname, also returning descriptor 3 (the earlier descriptors were closed by the linker once they were no longer needed). -
read(3, "myhost.example.local\n", 131072) = 21— the kernel returns 21 bytes into the buffer, even though the buffer is 131072 bytes long. This is normal:readreturns as soon as any data is available. -
write(1, "myhost.example.local\n", 21) = 21— the program writes the same 21 bytes to descriptor 1, standard output. -
The second
readreturns 0, which means end of file. -
close(3) = 0— the descriptor is released.
What the trace reveals
Running cat is more than reading a file. The dynamic
linker runs first, several files are opened, and the program
issues exactly five of the traced system calls in its own right.
This kind of insight is impossible to get from source code alone
when libraries are involved.
06 Interactive activity
Activity · observe, compare, explain
Perform the following in your Linux VM.
-
Establish a baseline. Run
strace -c /bin/true. Note the total number of system calls and the top three by count. -
Compare with a real command. Run
strace -c ls /usr/bin. How many more system calls were issued? Which category dominates? -
See buffering. Compile a program that calls
printfin a loop 1000 times, each on a separate line, writing to a file. Run it understrace -c. Count thewritecalls. How does the count compare with 1000? -
Trace a block. Run
time cat /dev/zero | head -c 100000000 > /dev/null. Then run it again understrace -c. Note any difference in wall-clock time. - Reflect. Which system call dominated in each trace? What does that tell you about the workload?
07 Knowledge check
Check your understanding
Question 1
A program calls printf("hello\n") once and then
exits. How many write system calls will it issue?
Explain.
Show answer
printf call writes into a
library buffer; no system call occurs yet. When the program
exits, the library's atexit handlers flush the
buffer with a single write. If output is a
terminal, libc uses line buffering, so printf
issues a write immediately on seeing the newline —
still one call, but for a different reason.
Question 2
Explain the difference between a trap and an interrupt. Give one example of each.
Show answer
syscall instruction on x86-64, which requests a
kernel service from a user program, is a trap. An
interrupt is an asynchronous, hardware-
initiated event caused by an external device. A timer tick
that preempts the running process is an interrupt.
Question 3
Why does the operating system place the system-call number in a register rather than passing it as an ordinary function argument?
Show answer
rax carries the system-call number and the
remaining registers carry the arguments.
08 Unit discussion
Discussion prompt
System-call overhead is real: on a typical modern CPU, a system
call costs hundreds to thousands of cycles. Applications in
performance-sensitive domains — databases, network servers,
trading systems — go to considerable lengths to reduce it, using
mechanisms such as io_uring, mmap, or
kernel bypass.
Identify one application domain where reducing system-call overhead is worth substantial engineering effort, and one where it is not. Justify your choices with reference to the workload characteristics.
Post your response to the Unit 1 discussion forum and reply to at least two classmates. Supports CLO1 and CLO6.
09 Practice exercise
-
Read the wrapper. Find the libc source for
read. On a typical Linux system it is implemented in assembly, in a file such assysdeps/unix/sysv/linux/x86_64/syscall.S. How many instructions does it take? -
Estimate syscall overhead. Write a small C
program that issues
getpid()one million times and measures the elapsed time. On a modern Linux system agetpidcall is a few hundred nanoseconds. What does that tell you about the cost of a typical system call? -
Compare buffered and unbuffered I/O. Write a
program that writes 1 MB to a file using
write()in 1-byte increments. Time it. Then write the same amount usingfwrite. Time it. Compare the two, and explain the difference in terms of system calls. -
Trace a fork. Write a program that calls
fork(). Trace it withstrace -f -e trace=clone,execve,wait4. What underlying system call doesforkuse on Linux?
10 Summary
- A system call is a request from a user-space program to the kernel to perform a privileged operation.
- The distinction between user mode and kernel mode is enforced by the CPU and underpins all OS protection.
- Library APIs are not the same as system calls: one API call may map to zero, one, or many system calls.
- A trap is a synchronous software-initiated mode switch; an interrupt is an asynchronous hardware-initiated one. Both enter the kernel at privileged level.
- The system-call sequence involves saving the user context, dispatching on the syscall number, performing the operation, and returning via a return-from-trap instruction.
-
straceandltracemake the system-call interface observable, and reading their output is a core skill for the rest of the course.
Looking ahead
Lesson 1.5 considers how the kernel that handles these system calls is organised internally — the major architectural alternatives and their trade-offs.
11 References
- Silberschatz, A., Galvin, P. B., & Gagne, G. Operating System Concepts, Chapter 2.
- Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. Operating Systems: Three Easy Pieces, Chapter 4.
- Love, R. Linux System Programming, Chapter 1.
- Linux man-pages:
man 2 syscall,man 1 strace.