What are System Calls?
System calls are the fundamental interface between an application and the Linux kernel. They provide a way for user-space programs to request services from the kernel, which has privileged access to hardware and other system resources that user programs cannot directly access.
System calls form the essential bridge between user applications and the operating system kernel. Without them, programs would have no standardized way to perform crucial operations like reading files, creating processes, or communicating over networks.
Key Characteristics of System Calls
- Protection Boundary: System calls cross the protection boundary between user mode and kernel mode
- Controlled Interface: They provide a controlled interface for accessing privileged resources
- Abstraction: System calls abstract hardware details, making application development easier
- Standardization: They offer a standardized API regardless of underlying hardware
How System Calls Work
When a program invokes a system call, the following sequence of events occurs:
- The application prepares arguments for the system call
- The processor transfers control to a special address in the kernel
- The CPU switches from user mode to kernel mode (increasing privilege level)
- The kernel executes the requested operation
- The CPU returns to user mode
- Control is returned to the application, often with a return value
Categories of System Calls
Linux system calls can be grouped into several categories based on their functionality:
| Category | Description | Example System Calls |
|---|---|---|
| Process Control | Creation, execution, and termination of processes | fork(), exec(), exit(), wait() |
| File Management | Creation, reading, writing, and manipulation of files | open(), read(), write(), close() |
| Directory Management | Creation and management of directories | mkdir(), rmdir(), chdir() |
| Information Management | Getting/setting various system information | getpid(), getuid(), time() |
| Inter-Process Communication | Communication between processes | pipe(), shmget(), socket() |
| Protection | Control of access to resources | chmod(), chown(), umask() |
Common System Call Examples
File I/O System Calls
These system calls are used for reading from and writing to files.
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd;
char buffer[100];
ssize_t bytes_read;
// Open file for reading
fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("Error opening file");
return 1;
}
// Read from file
bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0'; // Null-terminate the string
// Write to standard output
write(STDOUT_FILENO, "File contents: ", 15);
write(STDOUT_FILENO, buffer, bytes_read);
}
// Close the file
close(fd);
return 0;
}
Process Control System Calls
These system calls are used for creating and managing processes.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main() {
pid_t pid;
int status;
// Create a child process
pid = fork();
if (pid == -1) {
// Fork failed
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// Child process
printf("Child process with PID: %d\n", getpid());
printf("Parent PID: %d\n", getppid());
// Execute a new program
execl("/bin/ls", "ls", "-la", NULL);
// execl only returns if there's an error
perror("execl");
exit(EXIT_FAILURE);
} else {
// Parent process
printf("Parent process. Created child with PID: %d\n", pid);
// Wait for the child to complete
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("Child exited with status: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
Memory Management System Calls
These system calls are used to allocate and manage memory.
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
int main() {
// Allocate memory using mmap
void* memory = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (memory == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
// Use the memory
char* message = (char*)memory;
strcpy(message, "Hello, memory mapping!");
printf("%s\n", message);
// Free the memory
if (munmap(memory, 4096) == -1) {
perror("munmap");
exit(EXIT_FAILURE);
}
return 0;
}
Inter-Process Communication System Calls
These system calls allow processes to communicate with each other.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main() {
int pipefd[2];
pid_t pid;
char buffer[100];
// Create a pipe
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// Create a child process
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// Child process
close(pipefd[1]); // Close unused write end
// Read from pipe
read(pipefd[0], buffer, sizeof(buffer));
printf("Child received: %s\n", buffer);
close(pipefd[0]);
exit(EXIT_SUCCESS);
} else {
// Parent process
close(pipefd[0]); // Close unused read end
// Write to pipe
const char* message = "Hello from parent process!";
write(pipefd[1], message, strlen(message) + 1);
close(pipefd[1]);
wait(NULL); // Wait for child
}
return 0;
}
Network Communication System Calls
These system calls enable network communication.
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
int main() {
int socket_fd;
struct sockaddr_in server_addr;
// Create socket
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd == -1) {
perror("socket");
return 1;
}
// Prepare server address structure
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(80);
// Convert IP address from text to binary
if (inet_pton(AF_INET, "93.184.216.34", &server_addr.sin_addr) <= 0) {
perror("inet_pton");
close(socket_fd);
return 1;
}
// Connect to server
if (connect(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
close(socket_fd);
return 1;
}
printf("Connected to server\n");
// Send HTTP request
const char* message = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
send(socket_fd, message, strlen(message), 0);
// Receive response
char buffer[4096];
int bytes_received;
bytes_received = recv(socket_fd, buffer, sizeof(buffer) - 1, 0);
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
printf("Received %d bytes:\n%s\n", bytes_received, buffer);
}
// Close socket
close(socket_fd);
return 0;
}
System Call Interface in Linux
In Linux, system calls are implemented as software interrupts. When a program needs to make a system call, it triggers a software interrupt (traditionally via the int 0x80 instruction, though modern systems use more efficient mechanisms like sysenter/syscall).
System Call Number
Each system call is identified by a unique number. The kernel uses this number to locate the appropriate system call handler function. For example, on x86-64 systems:
readsystem call has number 0writesystem call has number 1opensystem call has number 2
System Call Tables
The kernel maintains a table of system call handler functions. When a system call is invoked, the kernel uses the system call number as an index into this table to find and execute the appropriate handler function.
Using strace to Observe System Calls
The strace command is a powerful tool that allows you to trace the system calls made by a program. This can be extremely helpful for debugging and understanding how programs interact with the kernel.
# Trace system calls made by a command
$ strace ls -la
# Trace system calls with timing information
$ strace -tt ls -la
# Trace only specific system calls
$ strace -e open,read,write ls -la
# Attach to a running process
$ strace -p PID
Creating and Using System Calls
While most programs use system calls through library functions, it's important to understand how to invoke them directly.
Direct System Call Invocation
In C, you can invoke system calls directly using either inline assembly or the syscall function. Here's an example of writing to stdout without using the C library:
#include <unistd.h>
#include <sys/syscall.h>
int main() {
const char* message = "Hello, direct system call!\n";
// Using syscall() function
syscall(SYS_write, STDOUT_FILENO, message, 25);
return 0;
}
Error Handling
System calls indicate errors by returning negative values. The actual error code is stored in the errno variable. Always check return values and handle errors appropriately:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main() {
int fd = open("nonexistent-file.txt", O_RDONLY);
if (fd == -1) {
printf("Error opening file: %s (errno = %d)\n",
strerror(errno), errno);
exit(EXIT_FAILURE);
}
// File operations would go here
close(fd);
return 0;
}
System Call Wrappers
Most C libraries provide wrapper functions for system calls that handle the details of the system call invocation and error checking. These wrappers make it easier to use system calls in everyday programming.
System Call Simulation
This interactive visualization demonstrates how system calls cross the boundary between user space and kernel space, enabling applications to safely request services from the operating system.
The animation shows the complete journey of a system call from a user application to the kernel and back. You'll observe the mode transitions, parameter passing, and privilege level changes that occur during this process.
User Space
Application
C Library (glibc)
Kernel Space
System Call Handler
Kernel Service
File System Call
Simulate a file operation system call like open(), read(), or write().
Network System Call
Simulate a network operation system call like socket() or connect().
Process System Call
Simulate a process management system call like fork() or exec().
- Application code calls a C library function
- The library prepares arguments and invokes the actual system call
- CPU switches from user mode to kernel mode (privilege level change)
- The kernel's system call handler processes the request
- Kernel services perform the requested operation with privileged access
- Results are returned back to user space
- CPU switches back to user mode