c-systems-programming

C Systems Programming

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "c-systems-programming" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-c-systems-programming

C Systems Programming

Master C systems programming including file I/O, process management, inter-process communication, signals, and system calls for writing robust low-level system software.

File I/O Operations

File Descriptors

File descriptors are integers that represent open files in Unix-like systems. Standard file descriptors:

  • 0

  • Standard input (STDIN_FILENO)

  • 1

  • Standard output (STDOUT_FILENO)

  • 2

  • Standard error (STDERR_FILENO)

Basic File Operations

#include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <string.h>

int main(void) { int fd; char buffer[1024]; ssize_t bytes_read, bytes_written;

// Open file for reading
fd = open("input.txt", O_RDONLY);
if (fd == -1) {
    perror("open");
    return 1;
}

// Read from file
bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read == -1) {
    perror("read");
    close(fd);
    return 1;
}
buffer[bytes_read] = '\0';

// Close file
if (close(fd) == -1) {
    perror("close");
    return 1;
}

printf("Read %zd bytes: %s\n", bytes_read, buffer);
return 0;

}

Writing to Files

#include <fcntl.h> #include <unistd.h> #include <string.h> #include <stdio.h>

int write_file(const char *filename, const char *data) { int fd; ssize_t bytes_written; size_t len = strlen(data);

// Open file for writing, create if doesn't exist, truncate if exists
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
    perror("open");
    return -1;
}

// Write data
bytes_written = write(fd, data, len);
if (bytes_written == -1) {
    perror("write");
    close(fd);
    return -1;
}

if ((size_t)bytes_written != len) {
    fprintf(stderr, "Partial write: %zd of %zu bytes\n",
            bytes_written, len);
    close(fd);
    return -1;
}

if (close(fd) == -1) {
    perror("close");
    return -1;
}

return 0;

}

File Positioning

#include <fcntl.h> #include <unistd.h> #include <stdio.h>

int main(void) { int fd; char buffer[10]; off_t offset;

fd = open("data.txt", O_RDONLY);
if (fd == -1) {
    perror("open");
    return 1;
}

// Seek to byte 10 from start
offset = lseek(fd, 10, SEEK_SET);
if (offset == -1) {
    perror("lseek");
    close(fd);
    return 1;
}

// Read 10 bytes
if (read(fd, buffer, sizeof(buffer)) == -1) {
    perror("read");
    close(fd);
    return 1;
}

// Seek to end of file
offset = lseek(fd, 0, SEEK_END);
printf("File size: %lld bytes\n", (long long)offset);

close(fd);
return 0;

}

Process Management

Creating Processes with fork()

#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h>

int main(void) { pid_t pid; int status;

printf("Parent process (PID: %d)\n", getpid());

pid = fork();
if (pid == -1) {
    perror("fork");
    return 1;
}

if (pid == 0) {
    // Child process
    printf("Child process (PID: %d, Parent: %d)\n",
           getpid(), getppid());
    sleep(2);
    printf("Child exiting\n");
    return 42;
} else {
    // Parent process
    printf("Parent created child (PID: %d)\n", pid);

    // Wait for child to exit
    if (waitpid(pid, &#x26;status, 0) == -1) {
        perror("waitpid");
        return 1;
    }

    if (WIFEXITED(status)) {
        printf("Child exited with status: %d\n",
               WEXITSTATUS(status));
    }
}

return 0;

}

Executing Programs with exec()

#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h>

int main(void) { pid_t pid;

pid = fork();
if (pid == -1) {
    perror("fork");
    return 1;
}

if (pid == 0) {
    // Child process - execute ls command
    char *args[] = {"ls", "-l", "/tmp", NULL};
    char *envp[] = {NULL};

    execve("/bin/ls", args, envp);

    // If execve returns, an error occurred
    perror("execve");
    return 1;
} else {
    // Parent process - wait for child
    int status;
    waitpid(pid, &#x26;status, 0);
    printf("Child completed\n");
}

return 0;

}

Process Information

#include <unistd.h> #include <stdio.h> #include <sys/types.h>

void print_process_info(void) { printf("Process ID (PID): %d\n", getpid()); printf("Parent Process ID (PPID): %d\n", getppid()); printf("Process Group ID (PGID): %d\n", getpgrp()); printf("User ID (UID): %d\n", getuid()); printf("Effective User ID (EUID): %d\n", geteuid()); printf("Group ID (GID): %d\n", getgid()); printf("Effective Group ID (EGID): %d\n", getegid()); }

int main(void) { print_process_info(); return 0; }

Inter-Process Communication

Pipes

#include <unistd.h> #include <stdio.h> #include <string.h> #include <sys/wait.h>

int main(void) { int pipefd[2]; pid_t pid; char buffer[100];

// Create pipe
if (pipe(pipefd) == -1) {
    perror("pipe");
    return 1;
}

pid = fork();
if (pid == -1) {
    perror("fork");
    return 1;
}

if (pid == 0) {
    // Child process - writes to pipe
    close(pipefd[0]);  // Close read end

    const char *msg = "Hello from child process!";
    write(pipefd[1], msg, strlen(msg) + 1);
    close(pipefd[1]);

    return 0;
} else {
    // Parent process - reads from pipe
    close(pipefd[1]);  // Close write end

    read(pipefd[0], buffer, sizeof(buffer));
    printf("Parent received: %s\n", buffer);
    close(pipefd[0]);

    wait(NULL);
}

return 0;

}

Named Pipes (FIFOs)

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h>

// Writer process void fifo_writer(void) { const char *fifo_path = "/tmp/myfifo"; int fd; const char *msg = "Message through FIFO";

// Create FIFO if it doesn't exist
if (mkfifo(fifo_path, 0666) == -1) {
    perror("mkfifo");
    // Continue if already exists
}

fd = open(fifo_path, O_WRONLY);
if (fd == -1) {
    perror("open");
    return;
}

write(fd, msg, strlen(msg) + 1);
close(fd);

}

// Reader process void fifo_reader(void) { const char *fifo_path = "/tmp/myfifo"; int fd; char buffer[100];

fd = open(fifo_path, O_RDONLY);
if (fd == -1) {
    perror("open");
    return;
}

read(fd, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
close(fd);

unlink(fifo_path);

}

Signal Handling

Basic Signal Handling

#include <signal.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h>

volatile sig_atomic_t keep_running = 1;

void signal_handler(int signum) { if (signum == SIGINT) { printf("\nReceived SIGINT (Ctrl+C)\n"); keep_running = 0; } else if (signum == SIGTERM) { printf("Received SIGTERM\n"); keep_running = 0; } }

int main(void) { struct sigaction sa;

// Setup signal handler
sa.sa_handler = signal_handler;
sigemptyset(&#x26;sa.sa_mask);
sa.sa_flags = 0;

if (sigaction(SIGINT, &#x26;sa, NULL) == -1) {
    perror("sigaction SIGINT");
    return 1;
}

if (sigaction(SIGTERM, &#x26;sa, NULL) == -1) {
    perror("sigaction SIGTERM");
    return 1;
}

printf("Running... Press Ctrl+C to stop\n");
while (keep_running) {
    printf("Working...\n");
    sleep(1);
}

printf("Cleaning up and exiting\n");
return 0;

}

Sending Signals

#include <signal.h> #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h>

int main(void) { pid_t pid;

pid = fork();
if (pid == -1) {
    perror("fork");
    return 1;
}

if (pid == 0) {
    // Child process - pause until signal received
    printf("Child waiting for signal...\n");
    pause();
    printf("Child received signal\n");
    return 0;
} else {
    // Parent process - send signal after delay
    printf("Parent sleeping...\n");
    sleep(2);

    printf("Parent sending SIGUSR1 to child\n");
    if (kill(pid, SIGUSR1) == -1) {
        perror("kill");
        return 1;
    }

    wait(NULL);
    printf("Child process completed\n");
}

return 0;

}

Signal Masking

#include <signal.h> #include <stdio.h> #include <unistd.h>

int main(void) { sigset_t set, oldset;

// Initialize signal set
sigemptyset(&#x26;set);
sigaddset(&#x26;set, SIGINT);

// Block SIGINT
if (sigprocmask(SIG_BLOCK, &#x26;set, &#x26;oldset) == -1) {
    perror("sigprocmask");
    return 1;
}

printf("SIGINT blocked for 5 seconds (Ctrl+C won't work)\n");
sleep(5);

// Unblock SIGINT
if (sigprocmask(SIG_SETMASK, &#x26;oldset, NULL) == -1) {
    perror("sigprocmask");
    return 1;
}

printf("SIGINT unblocked (Ctrl+C will work now)\n");
sleep(5);

return 0;

}

System Calls

Common System Calls

#include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <stdio.h> #include <time.h>

void demonstrate_system_calls(void) { struct stat file_stat; char cwd[1024]; time_t current_time;

// Get current working directory
if (getcwd(cwd, sizeof(cwd)) != NULL) {
    printf("Current directory: %s\n", cwd);
}

// Get file information
if (stat("/etc/passwd", &#x26;file_stat) == 0) {
    printf("File size: %lld bytes\n",
           (long long)file_stat.st_size);
    printf("Permissions: %o\n", file_stat.st_mode &#x26; 0777);
    printf("Last modified: %s", ctime(&#x26;file_stat.st_mtime));
}

// Get current time
current_time = time(NULL);
printf("Current time: %s", ctime(&#x26;current_time));

// Get process times
printf("Process times:\n");
printf("  Ticks per second: %ld\n", sysconf(_SC_CLK_TCK));

}

Directory Operations

#include <sys/types.h> #include <dirent.h> #include <stdio.h> #include <errno.h>

int list_directory(const char *path) { DIR *dir; struct dirent *entry;

dir = opendir(path);
if (dir == NULL) {
    perror("opendir");
    return -1;
}

printf("Contents of %s:\n", path);
errno = 0;
while ((entry = readdir(dir)) != NULL) {
    printf("  %s (type: %d)\n", entry->d_name, entry->d_type);
}

if (errno != 0) {
    perror("readdir");
    closedir(dir);
    return -1;
}

if (closedir(dir) == -1) {
    perror("closedir");
    return -1;
}

return 0;

}

Error Handling

Using errno

#include <errno.h> #include <string.h> #include <stdio.h> #include <fcntl.h> #include <unistd.h>

void demonstrate_error_handling(void) { int fd;

// Attempt to open non-existent file
fd = open("/nonexistent/file.txt", O_RDONLY);
if (fd == -1) {
    int saved_errno = errno;  // Save errno immediately

    printf("Error code: %d\n", saved_errno);
    printf("Error message (strerror): %s\n", strerror(saved_errno));

    // Alternative using perror
    errno = saved_errno;
    perror("open");

    // Check specific error
    if (saved_errno == ENOENT) {
        fprintf(stderr, "File does not exist\n");
    } else if (saved_errno == EACCES) {
        fprintf(stderr, "Permission denied\n");
    }
}

}

Robust Error Handling Pattern

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h>

int copy_file(const char *src, const char *dst) { int src_fd = -1, dst_fd = -1; char buffer[4096]; ssize_t bytes_read, bytes_written; int ret = -1;

// Open source file
src_fd = open(src, O_RDONLY);
if (src_fd == -1) {
    fprintf(stderr, "Cannot open source file %s: %s\n",
            src, strerror(errno));
    goto cleanup;
}

// Open destination file
dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dst_fd == -1) {
    fprintf(stderr, "Cannot open destination file %s: %s\n",
            dst, strerror(errno));
    goto cleanup;
}

// Copy data
while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) {
    bytes_written = write(dst_fd, buffer, bytes_read);
    if (bytes_written != bytes_read) {
        fprintf(stderr, "Write error: %s\n", strerror(errno));
        goto cleanup;
    }
}

if (bytes_read == -1) {
    fprintf(stderr, "Read error: %s\n", strerror(errno));
    goto cleanup;
}

ret = 0;  // Success

cleanup: if (src_fd != -1) close(src_fd); if (dst_fd != -1) close(dst_fd); return ret; }

POSIX APIs

POSIX Threads Basics

#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h>

void *thread_function(void *arg) { int *value = (int *)arg; printf("Thread running with value: %d\n", *value); sleep(1); *value = 100; return value; }

int main(void) { pthread_t thread; int value = 42; void *result;

if (pthread_create(&#x26;thread, NULL, thread_function, &#x26;value) != 0) {
    perror("pthread_create");
    return 1;
}

printf("Main thread waiting...\n");

if (pthread_join(thread, &#x26;result) != 0) {
    perror("pthread_join");
    return 1;
}

printf("Thread returned: %d\n", *(int *)result);
return 0;

}

POSIX Shared Memory

#include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h>

int main(void) { const char *name = "/my_shm"; const size_t size = 4096; int shm_fd; void *ptr;

// Create shared memory object
shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
    perror("shm_open");
    return 1;
}

// Set size
if (ftruncate(shm_fd, size) == -1) {
    perror("ftruncate");
    return 1;
}

// Map shared memory
ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (ptr == MAP_FAILED) {
    perror("mmap");
    return 1;
}

// Write to shared memory
strcpy((char *)ptr, "Hello, shared memory!");

// Cleanup
munmap(ptr, size);
close(shm_fd);
shm_unlink(name);

return 0;

}

Best Practices

Always Check Return Values: Every system call can fail. Check return values and handle errors appropriately using errno, perror, or strerror.

Use Signal-Safe Functions: In signal handlers, only use async-signal-safe functions. Avoid printf, malloc, and other non-reentrant functions.

Close File Descriptors: Always close file descriptors when done to prevent resource leaks. Use cleanup patterns with goto for complex error handling.

Handle Partial I/O: read() and write() may transfer fewer bytes than requested. Always check return values and loop when necessary.

Use O_CLOEXEC: When opening files, use O_CLOEXEC flag to prevent file descriptor leaks across exec() calls.

Proper Process Cleanup: Always wait for child processes using wait() or waitpid() to prevent zombie processes.

Use sigaction Over signal: The sigaction() interface is more portable and reliable than the older signal() function.

Avoid Race Conditions: Be careful with signals and file operations. Use proper synchronization mechanisms like mutexes or semaphores.

Set Signal Masks Carefully: Block signals during critical sections to prevent race conditions, but restore the original mask afterward.

Use POSIX Standards: Prefer POSIX-compliant functions over system-specific extensions for better portability across Unix-like systems.

Common Pitfalls

Ignoring errno: Not checking errno after system call failures or checking it when no error occurred can lead to misleading error messages.

File Descriptor Leaks: Forgetting to close file descriptors in error paths causes resource exhaustion over time.

Zombie Processes: Not waiting for child processes leaves zombie processes that consume system resources until parent exits.

Signal Handler Complexity: Using non-async-signal-safe functions in signal handlers can cause deadlocks or crashes.

Assuming Complete I/O: Assuming read() or write() transfers all requested bytes can lead to data corruption or loss.

Race Conditions with fork: File descriptors and signals can cause race conditions when forking. Use careful synchronization.

Incorrect exec() Usage: Forgetting the NULL terminator in argument arrays for exec() family functions causes undefined behavior.

Buffer Overflows: Not checking sizes when reading into buffers can cause security vulnerabilities and crashes.

Mixing I/O Methods: Mixing low-level I/O (open, read, write) with stdio (fopen, fread, fwrite) on the same file can cause buffering issues.

Hardcoded Paths: Using hardcoded paths instead of environment variables or configuration files reduces portability and flexibility.

When to Use This Skill

Use C systems programming when you need to:

  • Develop operating system components or kernel modules

  • Create system utilities and command-line tools

  • Write device drivers or low-level hardware interfaces

  • Build high-performance servers requiring direct system control

  • Implement process managers or job schedulers

  • Create inter-process communication mechanisms

  • Develop embedded systems with limited resources

  • Build tools requiring precise control over processes and signals

  • Implement custom file systems or storage solutions

  • Work with real-time systems requiring deterministic behavior

This skill is essential for systems programmers, embedded developers, and anyone working close to the operating system level.

Resources

Documentation

  • The Linux Programming Interface by Michael Kerrisk

  • Advanced Programming in the UNIX Environment by W. Richard Stevens

  • POSIX.1-2017 specification

  • Linux man-pages project: https://man7.org/linux/man-pages/

Online Resources

Tools

  • strace: Trace system calls and signals

  • ltrace: Library call tracer

  • gdb: GNU debugger for debugging system programs

  • valgrind: Memory debugging and profiling

  • perf: Linux profiling tool for performance analysis

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript-type-system

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

cpp-templates-metaprogramming

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-utility-types

No summary provided by upstream source.

Repository SourceNeeds Review