/system_call.html Linux System Calls - Operating Systems Visualizer

Linux System Calls

Understanding the foundation of communication between user programs and the kernel

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.

Why System Calls Matter

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

How System Calls Work

When a program invokes a system call, the following sequence of events occurs:

  1. The application prepares arguments for the system call
  2. The processor transfers control to a special address in the kernel
  3. The CPU switches from user mode to kernel mode (increasing privilege level)
  4. The kernel executes the requested operation
  5. The CPU returns to user mode
  6. Control is returned to the application, often with a return value
Note: In Linux, system calls are typically not invoked directly but through wrapper functions provided by the C library (glibc). These wrapper functions handle the details of how to transfer control to the kernel.

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.

Basic File Operations Example
#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.

Process Creation Example
#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.

Memory Allocation Example
#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.

Pipe Example
#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.

Simple TCP Socket Example
#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:

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.

Important: System call numbers and their behavior can vary between different kernel versions and architectures. Always refer to the kernel documentation for your specific system.

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.

Using strace
# 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:

Direct System Call Example
#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:

Error Handling Example
#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.

What You'll See

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

Unprivileged Mode
Idle

C Library (glibc)

System Call Wrapper
Idle
Mode Boundary

Kernel Space

System Call Handler

Privileged Mode
Idle

Kernel Service

Resource Access
Idle

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().

How System Calls Work:
  1. Application code calls a C library function
  2. The library prepares arguments and invokes the actual system call
  3. CPU switches from user mode to kernel mode (privilege level change)
  4. The kernel's system call handler processes the request
  5. Kernel services perform the requested operation with privileged access
  6. Results are returned back to user space
  7. CPU switches back to user mode

System Calls Quiz

Which system call is used to create a new process in Linux?
A. create()
B. fork()
C. exec()
D. spawn()