名称: 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;
}
最佳实践
- 始终检查系统调用的返回值并适当处理错误
- 使用
errno和strerror()进行详细错误报告 - 在所有代码路径中关闭文件描述符和释放资源,包括错误路径
- 使用
sigaction()而不是已弃用的signal()进行信号处理 - 避免在信号处理器中执行阻塞操作;使用
sig_atomic_t标志 - 偏好使用标准I/O进行缓冲操作;使用系统调用进行直接控制
- 在访问共享文件的所有进程中使用一致的建议性锁
- 使用umask或显式模式位设置适当的文件权限
- 通过重试被中断的系统调用来处理
EINTR错误,当适当时 - 使用
waitpid()防止僵尸进程并处理子进程终止
常见陷阱
- 忘记检查系统调用的返回值导致静默错误
- 使用
signal()而不是sigaction(),缺少重要的控制标志 - 在信号处理器中执行复杂操作导致竞争条件
- 不在错误路径中关闭文件描述符,导致资源泄漏
- 在同一文件上混合缓冲和非缓冲I/O,导致数据损坏
- 忘记等待子进程,创建僵尸进程
- 在信号处理器中使用不安全函数(printf、malloc等)
- 不处理
EINTR错误,导致操作提前终止 - 在文件操作中忽略竞争条件,无适当锁定
- 在不检查进程是否存在的情况下使用
kill(),向错误进程发送信号
何时使用C系统编程
当需要以下情况时使用C系统编程:
- 直接访问操作系统资源和内核接口
- 对进程执行和资源管理的最大控制
- 无缓冲开销的低级I/O操作
- 构建系统工具、守护进程或服务
- 使用管道、信号或共享内存进行进程间通信
- 对文件操作和权限的细粒度控制
- 信号处理以优雅关机和错误恢复
- 需要最小开销的高性能应用程序
- 与遗留系统接口或移植Unix工具
- 理解高级抽象在底层如何工作