C系统编程Skill c-systems-programming

C系统编程是一种使用C语言进行低级系统交互的技能,包括文件输入输出、进程创建与管理、信号处理和系统调用等,适用于开发操作系统工具、守护进程、高性能应用和操作系统接口。关键词:C语言,系统编程,文件I/O,进程管理,信号处理,系统调用,低级交互,操作系统接口。

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

名称: c系统编程 用户可调用: false 描述: 用于C系统编程,包括文件I/O、进程、信号和系统调用,用于低级系统交互。 允许的工具:

  • 读取
  • 写入
  • 编辑
  • 搜索
  • 通配符
  • Bash

C系统编程

C语言中的系统编程通过系统调用提供对操作系统资源的直接访问,实现对文件、进程、信号和进程间通信的控制。此技能涵盖构建健壮低级应用程序的基本系统编程模式。

文件I/O操作

C提供标准库I/O(缓冲)和系统级I/O(非缓冲)操作。理解何时使用每种方式对性能和正确性至关重要。

标准I/O函数

标准I/O提供缓冲和便利函数用于常见操作。

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

// 使用标准I/O读写
int file_copy_stdio(const char *src, const char *dst) {
    FILE *source = fopen(src, "rb");
    if (!source) {
        perror("打开源文件失败");
        return -1;
    }

    FILE *dest = fopen(dst, "wb");
    if (!dest) {
        perror("打开目标文件失败");
        fclose(source);
        return -1;
    }

    char buffer[4096];
    size_t bytes;
    while ((bytes = fread(buffer, 1, sizeof(buffer), source)) > 0) {
        if (fwrite(buffer, 1, bytes, dest) != bytes) {
            perror("写入失败");
            fclose(source);
            fclose(dest);
            return -1;
        }
    }

    if (ferror(source)) {
        perror("读取失败");
        fclose(source);
        fclose(dest);
        return -1;
    }

    fclose(source);
    fclose(dest);
    return 0;
}

系统级I/O

系统调用提供对内核I/O操作的直接访问,无需缓冲。

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

// 使用系统调用读写
int file_copy_syscall(const char *src, const char *dst) {
    int source_fd = open(src, O_RDONLY);
    if (source_fd == -1) {
        perror("打开源文件失败");
        return -1;
    }

    int dest_fd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (dest_fd == -1) {
        perror("打开目标文件失败");
        close(source_fd);
        return -1;
    }

    char buffer[4096];
    ssize_t bytes_read, bytes_written;

    while ((bytes_read = read(source_fd, buffer, sizeof(buffer))) > 0) {
        bytes_written = write(dest_fd, buffer, bytes_read);
        if (bytes_written != bytes_read) {
            perror("写入失败");
            close(source_fd);
            close(dest_fd);
            return -1;
        }
    }

    if (bytes_read == -1) {
        perror("读取失败");
        close(source_fd);
        close(dest_fd);
        return -1;
    }

    close(source_fd);
    close(dest_fd);
    return 0;
}

进程管理

创建和管理进程是系统编程的基础。fork()exec() 系列函数支持进程创建和执行。

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

// 创建子进程并执行命令
int execute_command(const char *program, char *const argv[]) {
    pid_t pid = fork();

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

    if (pid == 0) {
        // 子进程
        execvp(program, argv);
        // execvp仅在错误时返回
        perror("execvp失败");
        exit(EXIT_FAILURE);
    }

    // 父进程
    int status;
    pid_t waited = waitpid(pid, &status, 0);

    if (waited == -1) {
        perror("waitpid失败");
        return -1;
    }

    if (WIFEXITED(status)) {
        return WEXITSTATUS(status);
    } else if (WIFSIGNALED(status)) {
        fprintf(stderr, "子进程被信号 %d 终止
",
                WTERMSIG(status));
        return -1;
    }

    return -1;
}

信号处理

信号提供异步事件通知。适当的信号处理对优雅关机和错误恢复至关重要。

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

// 信号处理的全局标志
static volatile sig_atomic_t keep_running = 1;

// 优雅关机的信号处理器
void signal_handler(int signum) {
    if (signum == SIGINT || signum == SIGTERM) {
        keep_running = 0;
    }
}

// 设置信号处理器
int setup_signals(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;
    }

    return 0;
}

// 带信号处理的主循环
void run_with_signals(void) {
    if (setup_signals() == -1) {
        return;
    }

    while (keep_running) {
        // 执行工作
        sleep(1);
        printf("工作中...
");
    }

    printf("优雅关机
");
}

进程间通信

管道支持相关进程之间的通信,常用于命令管道和父子进程通信。

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

// 创建管道示例:ls | wc -l
int pipeline_example(void) {
    int pipefd[2];

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

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

    if (pid1 == 0) {
        // 第一个子进程:ls
        close(pipefd[0]);  // 关闭读取端
        dup2(pipefd[1], STDOUT_FILENO);
        close(pipefd[1]);
        execlp("ls", "ls", NULL);
        perror("execlp ls");
        exit(EXIT_FAILURE);
    }

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

    if (pid2 == 0) {
        // 第二个子进程:wc -l
        close(pipefd[1]);  // 关闭写入端
        dup2(pipefd[0], STDIN_FILENO);
        close(pipefd[0]);
        execlp("wc", "wc", "-l", NULL);
        perror("execlp wc");
        exit(EXIT_FAILURE);
    }

    // 父进程
    close(pipefd[0]);
    close(pipefd[1]);

    waitpid(pid1, NULL, 0);
    waitpid(pid2, NULL, 0);

    return 0;
}

文件锁定

文件锁定防止多进程环境中的并发访问问题。

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

// 建议性文件锁定
int lock_file(int fd, int lock_type) {
    struct flock fl;
    fl.l_type = lock_type;    // F_RDLCK, F_WRLCK, F_UNLCK
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;             // 锁定整个文件
    fl.l_pid = getpid();

    if (fcntl(fd, F_SETLKW, &fl) == -1) {
        perror("fcntl");
        return -1;
    }

    return 0;
}

// 带独占锁写入文件
int write_locked(const char *filename, const char *data) {
    int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644);
    if (fd == -1) {
        perror("open");
        return -1;
    }

    // 获取独占锁
    if (lock_file(fd, F_WRLCK) == -1) {
        close(fd);
        return -1;
    }

    // 写入数据
    ssize_t written = write(fd, data, strlen(data));
    if (written == -1) {
        perror("write");
        lock_file(fd, F_UNLCK);
        close(fd);
        return -1;
    }

    // 释放锁
    lock_file(fd, F_UNLCK);
    close(fd);

    return 0;
}

目录操作

处理目录需要理解目录流和条目操作。

#include <dirent.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

// 递归列出目录中的所有文件
void list_directory(const char *path, int indent) {
    DIR *dir = opendir(path);
    if (!dir) {
        perror("opendir");
        return;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        // 跳过 . 和 ..
        if (strcmp(entry->d_name, ".") == 0 ||
            strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        // 带缩进打印
        for (int i = 0; i < indent; i++) {
            printf("  ");
        }
        printf("%s", entry->d_name);

        // 检查是否为目录
        char fullpath[1024];
        snprintf(fullpath, sizeof(fullpath), "%s/%s", path,
                 entry->d_name);

        struct stat statbuf;
        if (stat(fullpath, &statbuf) == 0) {
            if (S_ISDIR(statbuf.st_mode)) {
                printf("/
");
                list_directory(fullpath, indent + 1);
            } else {
                printf(" (%ld 字节)
", statbuf.st_size);
            }
        } else {
            printf("
");
        }
    }

    closedir(dir);
}

系统调用中的错误处理

适当的错误处理在系统编程中至关重要。系统调用通过返回值 和 errno 指示错误。

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

// 带错误处理的稳健文件操作
int safe_file_operation(const char *filename) {
    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        switch (errno) {
            case ENOENT:
                fprintf(stderr, "文件未找到: %s
", filename);
                break;
            case EACCES:
                fprintf(stderr, "权限被拒绝: %s
", filename);
                break;
            default:
                fprintf(stderr, "打开 %s 错误: %s
",
                        filename, strerror(errno));
        }
        return -1;
    }

    char buffer[1024];
    ssize_t bytes_read;

    while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
        // 处理数据
        write(STDOUT_FILENO, buffer, bytes_read);
    }

    if (bytes_read == -1) {
        fprintf(stderr, "读取错误: %s
", strerror(errno));
        close(fd);
        return -1;
    }

    if (close(fd) == -1) {
        fprintf(stderr, "关闭文件错误: %s
", strerror(errno));
        return -1;
    }

    return 0;
}

最佳实践

  1. 始终检查系统调用的返回值并适当处理错误
  2. 使用 errnostrerror() 进行详细错误报告
  3. 在所有代码路径中关闭文件描述符和释放资源,包括错误路径
  4. 使用 sigaction() 而不是已弃用的 signal() 进行信号处理
  5. 避免在信号处理器中执行阻塞操作;使用 sig_atomic_t 标志
  6. 偏好使用标准I/O进行缓冲操作;使用系统调用进行直接控制
  7. 在访问共享文件的所有进程中使用一致的建议性锁
  8. 使用umask或显式模式位设置适当的文件权限
  9. 通过重试被中断的系统调用来处理 EINTR 错误,当适当时
  10. 使用 waitpid() 防止僵尸进程并处理子进程终止

常见陷阱

  1. 忘记检查系统调用的返回值导致静默错误
  2. 使用 signal() 而不是 sigaction(),缺少重要的控制标志
  3. 在信号处理器中执行复杂操作导致竞争条件
  4. 不在错误路径中关闭文件描述符,导致资源泄漏
  5. 在同一文件上混合缓冲和非缓冲I/O,导致数据损坏
  6. 忘记等待子进程,创建僵尸进程
  7. 在信号处理器中使用不安全函数(printf、malloc等)
  8. 不处理 EINTR 错误,导致操作提前终止
  9. 在文件操作中忽略竞争条件,无适当锁定
  10. 在不检查进程是否存在的情况下使用 kill(),向错误进程发送信号

何时使用C系统编程

当需要以下情况时使用C系统编程:

  • 直接访问操作系统资源和内核接口
  • 对进程执行和资源管理的最大控制
  • 无缓冲开销的低级I/O操作
  • 构建系统工具、守护进程或服务
  • 使用管道、信号或共享内存进行进程间通信
  • 对文件操作和权限的细粒度控制
  • 信号处理以优雅关机和错误恢复
  • 需要最小开销的高性能应用程序
  • 与遗留系统接口或移植Unix工具
  • 理解高级抽象在底层如何工作

资源