Using system calls – and when not to use them
System calls are an exciting topic in any conversation about Unix and Linux. They are one of the lowest parts when it comes to system programming in Linux. If we were to look at this from a top-down approach, the shell and the binaries we run would be at the top. Just below that, we have the standard C library functions, such as printf()
, fgets()
, putc()
, and so on. Below them, at the lowest levels, we have the system calls, such as creat()
, write()
, and so on:

Figure 3.1 – High-level functions and low-level functions
When I talk about system calls here in this book, I mean system calls as C functions provided by the kernel, not the actual system call table. The system call functions we use here reside in user space, but the functions themselves execute in kernel space.
Many of the standard C library functions, such as putc()
, use one or more system call functions behind the curtains. The putc()
function is an excellent example; this uses write()
to print a character on the screen (which is a system call). There are also standard C library functions that don't use any system calls at all, such as atoi()
, which resides entirely in user space. There is no need to involve the kernel to convert a string into a number.
Generally speaking, if there is a standard C library function available, we should use that instead of a system call. System calls are often harder to work with and more primitive. Think of system calls as low-level operations, and standard C functions as high-level operations.
There are cases, though, when we need to use system calls, or when they are easier to use or more beneficial. Learning when and why to use system calls will make you a better system programmer altogether. For example, there are many filesystem operations we can perform on Linux via system calls that aren't available elsewhere. Another example when we need to use a system call is when we want to fork()
a process, something we will discuss in more detail later on. In other words, we need to use system calls when we need to perform some form of system operation.
Getting ready
In this recipe, we will be using a Linux-specific system call, so you'll need a Linux computer (which you most probably already have since you're reading this book). But do notice that the sysinfo()
system call won't work under FreeBSD or macOS.
How to do it…
There isn't actually much difference between using a function from the standard C library versus using a system call function. System calls in Linux are declared in unistd.h
, so we need to include this file when using system calls.
- Write the following small program and name it
sys-write.c
. It uses thewrite()
system call. Notice that we don't includestdio.h
here. Since we aren't using anyprintf()
function or any of the stdin, stdout, or stderr file streams, we don't needstdio.h
here. We print directly to file descriptor 1, which is standard output. The three standard file descriptors are always opened:#include <unistd.h> int main(void) { Â Â Â Â write(1, "hello, world\n", 13); Â Â Â Â return 0; }
- Compile it. From now on, we will always include
-Wall
,-Wextra
, and-pedantic
to write cleaner and better code:$> gcc -Wall -Wextra -pedantic -std=c99 \ > sys-write.c -o sys-write
- Run the program:
$> ./sys-write hello, world
- Now, write the same program but with the
fputs()
function instead—a higher-level function. Notice that we includestdio.h
here, instead ofunistd.h
. Name the programwrite-chars.c
:#include <stdio.h> int main(void) { Â Â Â Â fputs("hello, world\n", stdout); Â Â Â Â return 0; }
- Compile it:
$> gcc -Wall -Wextra -pedantic -std=c99 \ > write-chars.c -o write-chars
- Then, run it:
$> ./write-chars hello, world
- Now, it's time to write a program that reads some user and system information. Save the program as
my-sys.c
. All the system calls in the program are highlighted. This program fetches your user's ID, current working directory, the machine's total and free random-access memory (RAM), and current process ID (PID):#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/sysinfo.h> int main(void) {    char cwd[100] = { 0 }; /* for current dir */    struct sysinfo si; /* for system information */    getcwd(cwd, 100); /* get current working dir */    sysinfo(&si); /* get system information                   * (linux only) */        printf("Your user ID is %d\n", getuid());    printf("Your effective user ID is %d\n",       geteuid());    printf("Your current working directory is %s\n",       cwd);    printf("Your machine has %ld megabytes of "       "total RAM\n", si.totalram / 1024  / 1024);    printf("Your machine has %ld megabytes of "       "free RAM\n", si.freeram / 1024 / 1024);    printf("Currently, there are %d processes "       "running\n", si.procs);    printf("This process ID is %d\n", getpid());    printf("The parent process ID is %d\n",       getppid());    return 0; }
- Compile the program:
$> gcc -Wall -Wextra -pedantic -std=c99 my-sys.c -o \ > my-sys
- Then, run the program. You should now see some information about your user and the machine you are using:
$> ./my-sys Your user ID is 1000 Your effective user ID is 1000 Your current working directory is /mnt/localnas_disk2/linux-sys/ch3/code Your machine has 31033 megabytes of total RAM Your machine has 6117 megabytes of free RAM Currently, there are 2496 processes running This process ID is 30421 The parent process ID is 11101
How it works…
In Steps 1-6, we explored the difference between write()
and fputs()
. The difference might not be that obvious but write()
, the system call, uses file descriptors instead of file streams. This goes for almost all system calls. File descriptors are more primitive than file streams. The same top-to-bottom approach goes for file descriptors versus file streams. File streams are layered on top of file descriptors and provide a higher-level interface. Sometimes, though, we need to use file descriptors instead, as these offer more control. File streams, on the other hand, offer a more powerful and richer input and output, with formatted output—for example, such as printf()
.
In Steps 7-9, we wrote a program that fetches some system and user information. Here, we included three system call-specific header files: unistd.h
, sys/types.h
, and sys/sysinfo.h
.
We have already seen unistd.h
, a common header file for system calls in Unix and Linux systems. The sys/types.h
header file is another common header file for system calls, especially when it comes to getting values from the system. This header file contains special variable types; for example, uid_t
and gid_t
for user ID (UID) and group ID (GID). These are usually an int
. Others are ino_t
for inode numbers, pid_t
for PIDs, and so on.
The sys/sysinfo.h
header file is specifically for the sysinfo()
function, which is a system call specifically for Linux, and hence this won't work under other Unix systems such as macOS, Solaris, or FreeBSD/OpenBSD/NetBSD. This header file declares the sysinfo
struct, which we populate with information by calling the sysinfo()
function.
The first system call we use in the program is getcwd()
, to get the current working directory. The function takes two arguments: a buffer where it should save the path, and the length of that buffer.
The next system call is the Linux-specific sysinfo()
function. This one gives us a lot of information. When the function executes, all data is saved to the struct sysinfo
. This information includes the uptime of the system; load average; total amount of memory; available and used memory; total and available swap space; and the total number of processes running. In man 2 sysinfo
, you can find information on the variables in the struct sysinfo
and their data types. Further down in the code, we print some of these values using printf()
—for example, si.totalram
, which contains the size of the system's memory.
The rest of the system calls are called directly from printf()
and returns integer values.
There's more…
There is a lot of detailed information about Linux system calls in the manual. A good starting point is man 2 intro
and man 2 syscalls
.
Tip
Most system calls will return -1 if an error occurs. It's generally a good idea to check for this value to detect errors.