Linux syscalls

What is a syscall?

System calls allow the kernel to carefully expose certain key pieces of functionality to user programs, such as accessing the file system, creating and destroying processes, communicating with other processes, and allocating more memory.

A system call is a C procedure call, the change of mode is achieved by special instructions hidden inside:

  • trap instruction: enter kernel mode.
  • return-from-trap instruction: back to user program in user mode.

Commonly Used Syscalls

Early Unix systems exposed around twenty calls, but there are over 400+ system calls in the modern Linux kernel. The exact number varies by architecture (x86_64 has about 350).

Tier 1: The "Everyday" Calls (Highest Frequency)

These syscalls account for 90% of total system activity. Most are called thousands of times per second in nearly every application.

Category High-Frequency Syscalls Description
File I/O read, write, close, fstat, lseek The core of all data movement and file descriptor management.
Memory mmap, munmap, mprotect, brk Handling process memory, loading libraries, and heap allocation.
Polling poll, select, epoll_wait, futex Waiting for events on file descriptors or managing thread synchronization.
Signals rt_sigprocmask, rt_sigaction, rt_sigreturn Managing software interrupts and signal handling.
Info getpid, gettimeofday, clock_gettime Fetching basic process IDs and timestamps.

Note: gettimeofday and clock_gettime are often optimized via vDSO, allowing them to run in user-space without a full kernel transition.

Tier 2: The "Setup & Control" Calls (Medium Frequency)

These are called frequently but usually during initialization, periodic checks, or process transitions.

Category Common Syscalls Description
Process execve, clone (fork), wait4, exit_group Starting, managing, and terminating processes/threads.
File Meta openat, stat, access, getdents64 Opening files and listing directory contents.
Network socket, connect, sendto, recvfrom, bind Standard BSD socket operations for internet/local communication.
Descriptor fcntl, ioctl, dup2, pipe Fine-tuning file descriptors and inter-process communication.
Permissions getuid, geteuid, getgid, capget Checking user identities and process capabilities.

Tier 3: The "Utility" Calls (Lower Frequency)

These are used by specific tools (like ls, chmod, or top) or during specific system events.

Category Utility Syscalls Description
FileSystem mkdir, chdir, rename, unlink, chmod, mount Altering the file system structure or permissions.
Time nanosleep, alarm, setitimer Putting processes to sleep for specific intervals.
System Info uname, sysinfo, getrusage Retrieving kernel versions and resource usage stats.
Identity setuid, setgid, setpriority, setsid Changing process priority or user ownership.

Tier 4: The "Niche" Calls (Rare Frequency)

These are highly specialized and rarely seen in standard application code.

Category Specialized Syscalls Description
Security ptrace, seccomp, bpf, keyctl Debugging (strace/gdb), sandboxing, and eBPF tracing.
Modules init_module, delete_module, finit_module Loading and unloading Linux kernel drivers.
System reboot, swapon, kexec_load, quotactl Low-level system maintenance and power management.
IPC shmget, semop, msgget Legacy System V Inter-Process Communication.

Summary: The Overall "Top 10" Ranking

While rankings fluctuate, research (e.g., SysPop) and profiling tools consistently show this top-heavy distribution for general-purpose computing:

  1. read / write: (The absolute kings of syscalls)
  2. futex: (Constant thread synchronization in modern apps)
  3. mmap / munmap: (Memory allocation happens constantly)
  4. fstat: (Apps check file sizes/types before doing anything)
  5. poll / epoll_wait: (The heartbeat of all networked apps/web servers)
  6. rt_sigprocmask: (Standard overhead for signal safety)
  7. close: (Every open must eventually close)
  8. getpid: (Extremely cheap, used for logging and identification)
  9. ioctl: (The "junk drawer" for device-specific commands)
  10. lseek: (Moving the cursor inside files)

How to see this on your own machine

You can see exactly which syscalls your system is using right now by running this command:

# Traces the entire system for 10 seconds and summarizes frequency
sudo perf stat -e 'syscalls:sys_enter_*' -a sleep 10

Or for a specific program:

# Count syscalls for the 'ls' command
strace -c ls

seccomp

Secure computing mode (seccomp): Any system calls not on the list are disallowed.

It can be used to sandbox the privileges of a process, restricting the calls it is able to make from userspace into the kernel.

Are all Linux syscalls aligned with POSIX?

No. Many Linux system calls are Linux-specific extensions or variations that are not defined by the POSIX standard.

Linux implements POSIX system calls to ensure compatibility with Unix-like operating systems, but kernel developers frequently introduce non-POSIX syscalls to optimize performance, expose Linux-specific hardware/kernel features, or handle specialized system administration tasks.

Non-POSIX Compliant Linux Syscalls vs. POSIX Alternatives

Non-POSIX Linux Syscall POSIX Equivalent / Standard Alternative Primary Purpose / Advantage over POSIX
epoll_create / epoll_wait select(), poll() Scalable O(1) event notifications for high-concurrency network servers (POSIX select/poll are O(N)).
io_uring_setup / enter aio_read(), aio_write() High-performance, zero-copy, asynchronous I/O submission ring buffers.
clone() fork(), pthread_create() Low-level execution context creation that allows precise sharing of memory, namespace, file descriptors, and IPC state (used to build Linux containers).
unshare() / setns() (No POSIX equivalent) Manipulates Linux process namespaces (PID, mount, network, user namespaces) for container sandboxing (Docker, LXC).
pivot_root() chroot() Changes the root mount point of a mount namespace securely for containers.
eventfd() / signalfd() Pipes / sigaction() Exposes OS event notifications and signals directly as readable file descriptors for integration into event loops.
memfd_create() shm_open() Creates anonymous, memory-backed file descriptors directly in RAM without mounting a tmpfs file system.
copy_file_range() / sendfile() read() / write() loops Zero-copy kernel-space file copying without transferring data through user space buffers.
bpf() (No POSIX equivalent) Loads and executes eBPF bytecode in the kernel for network tracing, security filtering, and observability.
getrandom() /dev/urandom / getentropy() Obtains high-entropy random bytes directly from the kernel RNG without opening file descriptors.

Why Linux Has Non-POSIX Syscalls

  1. Performance & Scalability: Standard POSIX APIs (like select()) were designed decades ago and do not scale efficiently to modern multi-threaded applications handling hundreds of thousands of concurrent connections or high-throughput I/O.
  2. Containerization & Isolation: Features like Linux Namespaces, Cgroups, and pivot_root() are fundamental to how Linux isolates processes, but they fall outside standard POSIX process boundaries.
  3. Hardware & Kernel Features: Linux innovations such as eBPF, io_uring, landlock security, and seccomp require custom syscall entrypoints to interact directly with internal kernel subsystems.

Standard Compliance in Practice

To write portable code across Unix systems (Linux, macOS, FreeBSD), developers restrict system call usage to POSIX APIs (often using the standard C library, glibc).

When targeting maximum performance on Linux, software often uses feature macros (#ifdef __linux__) or wrapper libraries (like libuv) to automatically prefer Linux-native syscalls (epoll, io_uring, getrandom) while falling back to POSIX equivalents on non-Linux platforms.

Learning Roadmap

To build a well-rounded foundation in POSIX systems programming, you should focus on the core system calls that manage process lifecycles, memory, file descriptors, multiplexing I/O, and inter-process communication (IPC).

Process Lifecycle & Execution

These calls form the backbone of modern Unix process management.

  • fork()

    • What it does: Clones the calling process to create a child process. The child inherits a copy of the parent’s address space (via Copy-On-Write), file descriptor table, environment, and execution context.
    • Key detail: It returns twice: 0 in the child process, and the child's Process ID (PID) in the parent process.
  • execve() (and the exec(3) wrapper family like execvp, execl)

    • What it does: Replaces the current process image with a new program binary.
    • Key detail: It does not return on success because the original code, stack, and heap are completely overwritten by the new program. File descriptors without O_CLOEXEC remain open across an execve.
  • waitpid() (and wait())

    • What it does: Suspends the parent process until a specific child process changes state (terminates, stops, or resumes).
    • Key detail: Prevents "zombie" processes by reaping the child's exit status code (WEXITSTATUS) from the kernel table.
  • exit() / _exit()

    • What it does: Terminates the calling process and flushes/releases allocated resources. _exit() is the raw system call (bypassing C library cleanup handlers like atexit()).

File Descriptor & I/O Manipulation

These calls operate directly on open file descriptors (fd), regardless of whether they represent regular files, sockets, pipes, or devices.

  • read() / write()

    • What it does: Reads bytes from or writes bytes to a file descriptor starting at the current offset.
    • Key detail: Unbuffered I/O. They do not guarantee reading or writing the full requested byte count in a single call (handling "short counts" is standard practice).
  • dup() / dup2() / dup3()

    • What it does: Duplicates an open file descriptor so that two descriptors share the exact same kernel Open File Description (sharing file position offset and flags).
    • Key detail: dup2(oldfd, newfd) is the fundamental mechanism used by shells to perform I/O redirection (e.g., redirecting standard output 1 to a log file).
  • lseek()

    • What it does: Repositions the file offset of an open descriptor (SEEK_SET, SEEK_CUR, SEEK_END).
    • Key detail: Works on seeking files; fails with ESPIPE when called on pipes or sockets.
  • fcntl()

    • What it does: Performs miscellaneous control operations on open file descriptors (e.g., querying or modifying flags like O_NONBLOCK via F_GETFL/F_SETFL, applying advisory locks, or duplicating descriptors with F_DUPFD_CLOEXEC).
  • close()

    • What it does: Decrements the reference count of the underlying open file description. When the reference count reaches zero, the underlying resource (file, socket, pipe) is freed by the kernel.

I/O Multiplexing & Event Systems

When dealing with multiple non-blocking sockets or pipes simultaneously, you need system calls that can wait on multiple file descriptors at once.

  • poll() (POSIX standard)

    • What it does: Blocks until one or more file descriptors in an array of struct pollfd become ready for I/O (reading, writing, or errors).
    • Key detail: Replaced the older POSIX select() API by removing file descriptor numerical limits (FD_SETSIZE = 1024).
  • Linux Event Notification Extensions (epoll_*):

    • While poll() is POSIX-standard, modern high-performance Linux servers use epoll_create1(), epoll_ctl(), and epoll_wait(). Unlike poll()'s O(N) linear scanning complexity, epoll provides O(1) scaling when monitoring tens of thousands of idle descriptors.

Memory Management & Mapping

  • mmap() / munmap()

    • What it does: Maps files or anonymous kernel memory pages directly into the process's virtual address space.
    • Key detail: Used for memory-mapped file I/O (avoiding redundant kernel-to-userspace buffer copying) and as the foundation for modern heap allocators (e.g., malloc allocates large blocks via mmap using MAP_ANONYMOUS).
  • mprotect()

    • What it does: Changes protection permissions (PROT_READ, PROT_WRITE, PROT_EXEC) on regions of virtual memory pages. Essential for JIT compilers and memory protection features.

Inter-Process Communication (IPC)

  • pipe() / pipe2()

    • What it does: Creates a unidirectional data channel (returning an array of two descriptors: fd[0] for reading, fd[1] for writing).
    • Key detail: The primary mechanism behind shell pipelines (e.g., ls | grep).
  • socket() / bind() / listen() / accept() / connect()

    • What it does: The POSIX Sockets API used for networking (TCP/UDP) and Unix Domain Sockets (AF_UNIX) for fast local IPC.
Loading...