System calls
enable users to request a service from the operating system (OS). To execute a system call
, the execution of the process is halted, and the execution of the system call starts in kernel mode. This switch from user mode to kernel mode may incur a short delay.
System calls
are useful when a process requires some functionality that is administered or offered only by the operating system. Such functionalities include but are not limited to:
Input and output (I/O) operations
Creation or deletion of files
Creation and administration of processes
Networking
Each system call
is assigned a unique system call number. In UNIX, this information is stored in the unistd_32.h
header file that we can locate by running the following command on the terminal:
locate unistd_32.h
The locate
command is part of the mlocate package. If not already installed on your computer, it can be installed by running the following command on the terminal:
sudo apt install mlocate
The following information is present in the unistd_32.h
header file.
educative@rukhshan:/usr/src/linux-headers-5.8.0-55-generic/arch/x86/include/generated/uapi/asm$ cat unistd_32.h
#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H 1
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
...
The system call number of each system call is written next to it. For example, the system call number of the write
system call is 4, and for the exit
system call, it is 1.
To execute a system call, the system call number is saved in the general-purpose register (GPR) eax
. The arguments of the corresponding system call are stored in the GPRs ebx
, ecx
, and edx
. Information in these registers is stored in sequential order, which means that the first argument is saved in ebx
, the second in ecx
, etc.
The return value of any system call
is stored in the same register which was used to store its system call number, i.e., the eax
GPR.
To see what arguments a particular system call will take, we can run the following command on the terminal:
man 2 name_of_command
For example, man 2 write outputs a description of the write
system call.
WRITE(2) Linux Programmer's Manual WRITE(2)
NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
DESCRIPTION
write() writes up to count bytes from the buffer starting at buf to the
the file referred to by the file descriptor fd.
...
The following snippet of code demonstrates a simple assembly language program, which uses the write system call
to print a string to the console.
segment .data:message db "Welcome to Edpresso!" , 0xAmessage_length equ $-messagesegment .bss:segment .text:global _start_start:mov eax, 0x4 ;4 is the unique system call number of the write system callmov ebx, 1 ;1 is the file descriptor for the stdout streammov ecx, message ;message is the string that needs to be printed on the consolemov edx, message_length ;length of messageint 0x80 ;interrupt to run the kernelmov eax, 0x1 ;1 is the unique system call number of the exit system callmov ebx, 0 ;argument to exit system callint 0x80 ;interrupt to run the kernel and exit gracefully
In the .data
segment, we have defined a string and assigned message
as its identifier. The last character of the string is \n
, which is represented by 0XA
in hex. We use the db
directive to allocate space for this string. Additionally, we define another variable and name it message_length
, which stores the length of message
.
In the .text
section of our code, we set the value of the general purpose register (GPR) eax
to 0x4
, which is the system call number of the write
system call. The write
system call is used to write to a stream, and its syntax is as follows:
ssize_t write(int fd, const void *buf, size_t count);
The write system calls returns the number of bytes written to a stream. The first argument to the write system call is the file descriptor of the stream we intend to write to. In our case, this is the stdout
stream. The file descriptor of stdout
is 1, and we store it in the GPR ebx
. The second parameter is a pointer to the buffer that we wish to copy data from. This is the variable message
and we store it in the GPR ecx
. The third and final argument is the buffer size that we wish to write to, so we store message_length
in the GPR edx
.
Subsequently, the kernel is called using the int X80 instruction, which denotes a kernel interrupt. The kernel reads values from each of the registers filled above and writes the buffer to the stdout stream using the write
system call.
To exit gracefully, we call the exit
system call, which has 1 as its unique system call number.
Free Resources