Kahibaro
Discord Login Register

Using Linux system APIs

Why “Linux system APIs” Matter

When you move from simple scripts or basic C programs to serious Linux tools, you stop just “using commands” and start interacting with the OS through its system APIs. These APIs are what shells, core utilities, and servers use under the hood.

At a high level:

This chapter assumes you already know how to write Linux tools in Bash, Python, or C; here we focus on how those tools talk to the system itself.


System Calls vs C Library

Linux exposes functionality primarily via system calls. You almost never invoke syscalls directly by number; you call standard library functions that wrap them.

Conceptually:

Key ideas for tool authors:

Example C snippet showing syscall-style error handling:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(void) {
    ssize_t n = write(1, "hello\n", 6);   // fd 1 = stdout
    if (n == -1) {
        fprintf(stderr, "write failed: %s\n", strerror(errno));
        return 1;
    }
    return 0;
}

Basic Unix APIs You’ll Use Everywhere

These are the “bread and butter” APIs for almost every nontrivial Linux tool.

File Descriptors and Basic I/O

Linux resources (files, pipes, sockets, devices) are usually represented as file descriptors (FDs), small integers:

Core functions:

Example: a tiny cat-like program using only low-level I/O:

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
    int fd = 0; // stdin
    if (argc > 1) {
        fd = open(argv[1], O_RDONLY);
        if (fd == -1) {
            perror("open");
            return 1;
        }
    }
    char buf[4096];
    while (1) {
        ssize_t n = read(fd, buf, sizeof(buf));
        if (n == 0) break;          // EOF
        if (n < 0) {
            if (errno == EINTR) continue;
            perror("read");
            return 1;
        }
        ssize_t off = 0;
        while (off < n) {
            ssize_t m = write(1, buf + off, n - off);
            if (m < 0) {
                if (errno == EINTR) continue;
                perror("write");
                return 1;
            }
            off += m;
        }
    }
    if (fd != 0) close(fd);
    return 0;
}

Why this matters:

Processes and `fork`/`exec`

Linux creates new processes with fork() and runs new programs with execve() (usually via wrappers like execvp()).

Common functions:

Example: running another program from your tool:

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
int main(void) {
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    }
    if (pid == 0) {
        // child: exec "ls -l"
        char *argv[] = { "ls", "-l", NULL };
        execvp("ls", argv);
        perror("execvp"); // only reached on error
        _exit(127);
    }
    // parent: wait for child
    int status;
    if (waitpid(pid, &status, 0) == -1) {
        perror("waitpid");
        return 1;
    }
    if (WIFEXITED(status)) {
        printf("Child exited with code %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("Child killed by signal %d\n", WTERMSIG(status));
    }
    return 0;
}

This is the foundation for writing:

Signals

Signals are simple async notifications (e.g. SIGINT, SIGTERM).

Useful APIs:

Trivial signal handler example:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static void handle_sigint(int sig) {
    (void)sig;
    write(1, "Caught SIGINT\n", 14);
}
int main(void) {
    struct sigaction sa = {0};
    sa.sa_handler = handle_sigint;
    sigaction(SIGINT, &sa, NULL);
    while (1) {
        pause();  // wait for signals
    }
}

Linux-Specific System Calls and Interfaces

Here are some APIs that are either Linux-specific or widely used in Linux-centric tools.

`epoll` for Scalable I/O Multiplexing

If your tool needs to handle many sockets or pipes at once, epoll is the modern Linux choice (over select/poll).

Core functions:

Minimal usage pattern:

  1. epfd = epoll_create1(0);
  2. For each FD, call epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
  3. Loop calling epoll_wait(...) to get ready events.

This is how many high-performance servers and proxies operate.

`inotify` for Filesystem Events

inotify lets your tool react when files or directories change:

Example use cases:

`clone`, Namespaces, and `unshare`

Linux containers, some sandboxes, and advanced tools use namespaces and clone to create isolated environments.

Key APIs:

Flags like:

These are powerful but low-level; container frameworks sit on top of them.

`ioctl` and Device-Specific Interfaces

ioctl calls let you interact with devices or special files using custom commands:

The meaning of request and the extra data depends on the device. You see them in:

Example: query terminal size:

#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
    struct winsize ws;
    if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
        perror("ioctl");
        return 1;
    }
    printf("rows=%d cols=%d\n", ws.ws_row, ws.ws_col);
    return 0;
}

Pseudo-filesystems: `/proc` and `/sys`

Linux exposes much of its internal state as virtual files.

`/proc` for Process and Kernel Info

Examples:

You read these with ordinary file APIs: open, read, fopen, fgets.

Example: get process ID from /proc/self (just symbolic link):

#include <stdio.h>
#include <unistd.h>
int main(void) {
    FILE *f = fopen("/proc/self/stat", "r");
    if (!f) {
        perror("fopen");
        return 1;
    }
    int pid;
    if (fscanf(f, "%d", &pid) == 1) {
        printf("My PID is %d\n", pid);
    }
    fclose(f);
    return 0;
}

`/sys` for Devices and Kernel Parameters

/sys is the sysfs filesystem, exposing:

You typically:

Example: list CPU cores by reading /sys/devices/system/cpu/ using opendir / readdir.


Process Attributes and Resource Limits

Linux offers APIs around scheduling, priorities, CPU sets, and more.

Common ones used by system tools:

Example: set a soft limit on open file descriptors:

#include <sys/resource.h>
#include <stdio.h>
int main(void) {
    struct rlimit rl;
    if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
        perror("getrlimit");
        return 1;
    }
    printf("Old soft limit: %ld\n", (long)rl.rlim_cur);
    rl.rlim_cur = 1024;
    if (setrlimit(RLIMIT_NOFILE, &rl) == -1) {
        perror("setrlimit");
        return 1;
    }
    return 0;
}

Memory and Mapping

Most tools rely on normal malloc/free from the C library. But sometimes you want direct control over mappings.

Key APIs:

Common use cases:

Example: map a file read-only and print its first byte:

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s file\n", argv[0]);
        return 1;
    }
    int fd = open(argv[1], O_RDONLY);
    if (fd == -1) { perror("open"); return 1; }
    struct stat st;
    if (fstat(fd, &st) == -1) { perror("fstat"); return 1; }
    if (st.st_size == 0) { printf("Empty file\n"); return 0; }
    void *addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) { perror("mmap"); return 1; }
    printf("First byte: 0x%02x\n", ((unsigned char *)addr)[0]);
    munmap(addr, st.st_size);
    close(fd);
    return 0;
}

Time, Timers, and Clocks

Linux offers POSIX time APIs plus Linux extensions.

Useful APIs for tools:

Example: simple sleep using nanosleep:

#include <time.h>
#include <stdio.h>
int main(void) {
    struct timespec ts = { .tv_sec = 1, .tv_nsec = 500000000 }; // 1.5 seconds
    if (nanosleep(&ts, NULL) == -1) {
        perror("nanosleep");
        return 1;
    }
    printf("Done\n");
    return 0;
}

Exposing Your Own APIs: Syscalls vs Libraries

Most Linux tool authors do not add new kernel syscalls. Instead, they:

Typical layering:

  1. Raw Linux syscalls (read, open, epoll, clone, etc.).
  2. Your C (or Rust, etc.) library that provides a clean, stable API.
  3. One or more CLI tools that call into that library.

Benefits:

Practical Tips for Using Linux APIs

Using Linux system APIs effectively is what turns your programs from “scripts that glue commands together” into first-class Linux tools that behave like the rest of the system.

Views: 24

Comments

Please login to add a comment.

Don't have an account? Register now!