C系统编程Skill c-systems-programming

C系统编程技能专注于使用C语言进行低层系统软件开发,涵盖文件I/O、进程管理、信号处理和系统调用,适用于操作系统组件、设备驱动和高性能服务器等场景。关键词:C语言,系统编程,文件I/O,进程管理,信号处理,系统调用,低层软件开发。

操作系统 0 次安装 0 次浏览 更新于 3/25/2026

name: c-systems-programming user-invocable: false description: 当使用C语言编写需要文件I/O、进程管理、信号和系统调用的低层系统软件时使用。 allowed-tools:

  • Bash
  • Read
  • Write
  • Edit

C系统编程

掌握C系统编程,包括文件I/O、进程管理、进程间通信、信号和系统调用,用于编写稳健的低层系统软件。

文件I/O操作

文件描述符

文件描述符是表示类Unix系统中打开文件的整数。标准文件描述符:

  • 0 - 标准输入 (STDIN_FILENO)
  • 1 - 标准输出 (STDOUT_FILENO)
  • 2 - 标准错误 (STDERR_FILENO)

基本文件操作

#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;

    // 打开文件进行读取
    fd = open("input.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 从文件读取
    bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read == -1) {
        perror("read");
        close(fd);
        return 1;
    }
    buffer[bytes_read] = '\0';

    // 关闭文件
    if (close(fd) == -1) {
        perror("close");
        return 1;
    }

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

写入文件

#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);

    // 打开文件进行写入,如果不存在则创建,如果存在则截断
    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open");
        return -1;
    }

    // 写入数据
    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
",
                bytes_written, len);
        close(fd);
        return -1;
    }

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

    return 0;
}

文件定位

#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;
    }

    // 从开始位置寻找到第10字节
    offset = lseek(fd, 10, SEEK_SET);
    if (offset == -1) {
        perror("lseek");
        close(fd);
        return 1;
    }

    // 读取10字节
    if (read(fd, buffer, sizeof(buffer)) == -1) {
        perror("read");
        close(fd);
        return 1;
    }

    // 寻找到文件末尾
    offset = lseek(fd, 0, SEEK_END);
    printf("File size: %lld bytes
", (long long)offset);

    close(fd);
    return 0;
}

进程管理

使用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)
", getpid());

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

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

        // 等待子进程退出
        if (waitpid(pid, &status, 0) == -1) {
            perror("waitpid");
            return 1;
        }

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

    return 0;
}

使用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) {
        // 子进程 - 执行ls命令
        char *args[] = {"ls", "-l", "/tmp", NULL};
        char *envp[] = {NULL};

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

        // 如果execve返回,表示发生错误
        perror("execve");
        return 1;
    } else {
        // 父进程 - 等待子进程
        int status;
        waitpid(pid, &status, 0);
        printf("Child completed
");
    }

    return 0;
}

进程信息

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

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

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

进程间通信

管道

#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];

    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

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

    if (pid == 0) {
        // 子进程 - 写入管道
        close(pipefd[0]);  // 关闭读取端

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

        return 0;
    } else {
        // 父进程 - 从管道读取
        close(pipefd[1]);  // 关闭写入端

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

        wait(NULL);
    }

    return 0;
}

命名管道(FIFO)

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

// 写入进程
void fifo_writer(void) {
    const char *fifo_path = "/tmp/myfifo";
    int fd;
    const char *msg = "Message through FIFO";

    // 如果FIFO不存在,则创建
    if (mkfifo(fifo_path, 0666) == -1) {
        perror("mkfifo");
        // 如果已存在则继续
    }

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

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

// 读取进程
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
", buffer);
    close(fd);

    unlink(fifo_path);
}

信号处理

基本信号处理

#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("
Received SIGINT (Ctrl+C)
");
        keep_running = 0;
    } else if (signum == SIGTERM) {
        printf("Received SIGTERM
");
        keep_running = 0;
    }
}

int main(void) {
    struct sigaction sa;

    // 设置信号处理程序
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

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

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

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

    printf("Cleaning up and exiting
");
    return 0;
}

发送信号

#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) {
        // 子进程 - 暂停直到接收信号
        printf("Child waiting for signal...
");
        pause();
        printf("Child received signal
");
        return 0;
    } else {
        // 父进程 - 延迟后发送信号
        printf("Parent sleeping...
");
        sleep(2);

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

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

    return 0;
}

信号屏蔽

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

int main(void) {
    sigset_t set, oldset;

    // 初始化信号集
    sigemptyset(&set);
    sigaddset(&set, SIGINT);

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

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

    // 解除屏蔽SIGINT
    if (sigprocmask(SIG_SETMASK, &oldset, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }

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

    return 0;
}

系统调用

常见系统调用

#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;

    // 获取当前工作目录
    if (getcwd(cwd, sizeof(cwd)) != NULL) {
        printf("Current directory: %s
", cwd);
    }

    // 获取文件信息
    if (stat("/etc/passwd", &file_stat) == 0) {
        printf("File size: %lld bytes
",
               (long long)file_stat.st_size);
        printf("Permissions: %o
", file_stat.st_mode & 0777);
        printf("Last modified: %s", ctime(&file_stat.st_mtime));
    }

    // 获取当前时间
    current_time = time(NULL);
    printf("Current time: %s", ctime(&current_time));

    // 获取进程时间
    printf("Process times:
");
    printf("  Ticks per second: %ld
", sysconf(_SC_CLK_TCK));
}

目录操作

#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:
", path);
    errno = 0;
    while ((entry = readdir(dir)) != NULL) {
        printf("  %s (type: %d)
", 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;
}

错误处理

使用errno

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

void demonstrate_error_handling(void) {
    int fd;

    // 尝试打开不存在的文件
    fd = open("/nonexistent/file.txt", O_RDONLY);
    if (fd == -1) {
        int saved_errno = errno;  // 立即保存errno

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

        // 使用perror的替代方法
        errno = saved_errno;
        perror("open");

        // 检查特定错误
        if (saved_errno == ENOENT) {
            fprintf(stderr, "File does not exist
");
        } else if (saved_errno == EACCES) {
            fprintf(stderr, "Permission denied
");
        }
    }
}

稳健错误处理模式

#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;

    // 打开源文件
    src_fd = open(src, O_RDONLY);
    if (src_fd == -1) {
        fprintf(stderr, "Cannot open source file %s: %s
",
                src, strerror(errno));
        goto cleanup;
    }

    // 打开目标文件
    dst_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dst_fd == -1) {
        fprintf(stderr, "Cannot open destination file %s: %s
",
                dst, strerror(errno));
        goto cleanup;
    }

    // 复制数据
    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
", strerror(errno));
            goto cleanup;
        }
    }

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

    ret = 0;  // 成功

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

POSIX API

POSIX线程基础

#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
", *value);
    sleep(1);
    *value = 100;
    return value;
}

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

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

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

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

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

POSIX共享内存

#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;

    // 创建共享内存对象
    shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
    if (shm_fd == -1) {
        perror("shm_open");
        return 1;
    }

    // 设置大小
    if (ftruncate(shm_fd, size) == -1) {
        perror("ftruncate");
        return 1;
    }

    // 映射共享内存
    ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    // 写入共享内存
    strcpy((char *)ptr, "Hello, shared memory!");

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

    return 0;
}

最佳实践

  1. 始终检查返回值:每个系统调用都可能失败。使用errno、perror或strerror检查返回值并适当处理错误。

  2. 使用信号安全函数:在信号处理程序中,仅使用异步信号安全函数。避免printf、malloc和其他非可重入函数。

  3. 关闭文件描述符:完成时始终关闭文件描述符以防止资源泄漏。使用带goto的清理模式进行复杂错误处理。

  4. 处理部分I/O:read()和write()可能传输少于请求的字节数。始终检查返回值并在必要时循环。

  5. 使用O_CLOEXEC:打开文件时,使用O_CLOEXEC标志以防止文件描述符在exec()调用中泄漏。

  6. 适当进程清理:始终使用wait()或waitpid()等待子进程以防止僵尸进程。

  7. 使用sigaction替代signal:sigaction()接口比旧的signal()函数更可移植和可靠。

  8. 避免竞态条件:小心处理信号和文件操作。使用适当的同步机制如互斥锁或信号量。

  9. 小心设置信号屏蔽:在关键部分屏蔽信号以防止竞态条件,但之后恢复原始屏蔽。

  10. 使用POSIX标准:优先使用符合POSIX标准的函数而非系统特定扩展,以在类Unix系统间获得更好的可移植性。

常见陷阱

  1. 忽略errno:系统调用失败后不检查errno,或在没有错误时检查它,可能导致误导性的错误消息。

  2. 文件描述符泄漏:在错误路径中忘记关闭文件描述符会导致随时间资源耗尽。

  3. 僵尸进程:不等待子进程会留下僵尸进程,消耗系统资源直到父进程退出。

  4. 信号处理程序复杂性:在信号处理程序中使用非异步信号安全函数可能导致死锁或崩溃。

  5. 假设完整I/O:假设read()或write()传输所有请求的字节数可能导致数据损坏或丢失。

  6. fork()竞态条件:文件描述符和信号在使用fork()时可能导致竞态条件。使用小心同步。

  7. exec()错误使用:忘记在exec()系列函数的参数数组中添加NULL终止符会导致未定义行为。

  8. 缓冲区溢出:不检查大小当读取到缓冲区可能导致安全漏洞和崩溃。

  9. 混合I/O方法:在同一文件上混合低级I/O(open, read, write)和stdio(fopen, fread, fwrite)可能导致缓冲问题。

  10. 硬编码路径:使用硬编码路径而非环境变量或配置文件会降低可移植性和灵活性。

何时使用此技能

在以下情况下使用C系统编程:

  • 开发操作系统组件或内核模块
  • 创建系统实用程序和命令行工具
  • 编写设备驱动程序或低级硬件接口
  • 构建需要直接系统控制的高性能服务器
  • 实现进程管理器或作业调度器
  • 创建进程间通信机制
  • 开发资源有限的嵌入式系统
  • 构建需要精确控制进程和信号的工具
  • 实现自定义文件系统或存储解决方案
  • 处理需要确定性行为的实时系统

此技能对于系统程序员、嵌入式开发人员以及任何在操作系统级别工作的人来说是必不可少的。

资源

文档

  • 《Linux程序设计接口》作者Michael Kerrisk
  • 《UNIX环境高级编程》作者W. Richard Stevens
  • POSIX.1-2017规范
  • Linux man-pages项目:https://man7.org/linux/man-pages/

在线资源

工具

  • strace:跟踪系统调用和信号
  • ltrace:库调用跟踪器
  • gdb:用于调试系统程序的GNU调试器
  • valgrind:内存调试和性能分析
  • perf:用于性能分析的Linux性能分析工具