目录
进程
进程基础知识
1. 进程的定义
进程是计算机中正在执行的程序的实例,它是操作系统进行资源分配和调度的基本单位。进程不仅包含程序的代码,还包括程序执行时所需的各种系统资源和管理信息。简单的理解,程序(可执行程序)是一个静态的概念,而进程是一个动态的概念,即进程是程序的一次运行过程。
进程是程序的一次动态执行过程,与静态的程序本身有本质区别:
- 程序:静态的指令集合,存储在存储介质上
- 进程:动态的执行实体,拥有生命周期(创建、就绪、运行、挂起、终止)
2. 进程的组成要素(进程映像)
一个完整的进程包含以下几个核心组成部分:
A. 代码段(Text Segment)
- 存储可执行指令
- 通常是只读的,防止程序自我修改
- 包含程序的实际操作代码
B. 数据段(Data Segment)
- 初始化数据段:全局变量、静态变量
- 未初始化数据段(BSS):未初始化的全局变量
- 堆(Heap):动态分配的内存区域
- 通过
malloc()、new等分配 - 手动管理,生命周期不确定
- 通过
C. 栈(Stack)
- 存储函数调用信息、局部变量、参数
- 自动管理,后进先出(LIFO)结构
- 包含:
- 函数返回地址
- 局部变量
- 函数参数
- 临时数据
3. 进程的特征
A. 动态性
- 有生命周期:创建、就绪、运行、挂起、终止
- 状态不断变化
B. 并发性
- 多个进程可以同时存在于内存中
- 宏观上同时运行,微观上交替执行
C. 独立性
- 每个进程有独立的地址空间
- 进程间互不干扰(除非显式通信)
D. 异步性
- 进程以不可预知的速度推进
- 需要同步机制协调
E. 结构性
- 有明确的结构组成(代码、数据、栈、PCB)
进程的状态
进程状态包括创建、就绪、运行、阻塞\挂起、终止,以及状态之间的转换如下图所示:
各状态详细说明
1. 创建 (New)
- 含义:进程正在被创建,但尚未准备好执行
- 资源分配:操作系统正在分配PCB、初始化资源
- 持续时间:通常很短
2. 就绪 (Ready)
- 含义:进程已准备好运行,等待CPU时间片
- 条件:拥有除CPU外的所有必要资源
- 队列:处于就绪队列中等待调度
3. 运行 (Running)
- 含义:进程正在CPU上执行指令
- 数量限制:单CPU系统中同一时刻只有一个进程处于此状态
- 资源占用:正在使用CPU资源
4. 阻塞/挂起 (Blocked/Suspended)
- 阻塞:等待某事件(如I/O完成、信号量)
- 挂起:进程被换出到外存,暂时不参与调度
5. 终止 (Terminated)
- 含义:进程已完成执行或被强制终止
- 清理工作:操作系统回收资源,撤销PCB
状态转换原因和过程
转换1: 创建 → 就绪
- 原因:操作系统完成进程初始化
- 过程:
- 分配PCB并初始化
- 分配内存空间
- 建立与其他进程的关系
- 加入就绪队列
转换2: 就绪 → 运行
- 原因:进程调度器选择该进程执行
- 过程:
- 调度器从就绪队列选择进程
- 进行上下文切换
- 恢复进程的CPU状态
- 开始执行
转换3: 运行 → 就绪
- 原因:
- 时间片用完
- 更高优先级进程就绪
- 被中断处理程序抢占
- 过程:
- 保存当前进程上下文
- 更新PCB状态为就绪
- 加入就绪队列末尾
- 调度其他进程
转换4: 运行 → 阻塞
- 原因:
- 等待I/O操作完成
- 请求系统资源不可用
- 等待信号量或锁
- 等待子进程结束
- 过程:
- 进程主动发起等待请求
- 保存进程上下文
- 更新PCB状态为阻塞
- 加入相应等待队列
转换5: 阻塞 → 就绪
- 原因:等待的事件发生
- 过程:
- 事件完成(如I/O完成中断)
- 从阻塞队列移除
- 更新PCB状态为就绪
- 加入就绪队列
转换6: 运行 → 终止
- 原因:
- 正常执行完成
- 出现无法处理的错误
- 被其他进程杀死
- 父进程终止
- 过程:
- 释放所有分配的资源
- 通知父进程
- 从所有队列中移除
- 撤销PCB
转换7: 就绪 → 就绪挂起
- 原因:内存不足,需要换出低优先级进程
- 过程:进程被换出到外存,释放内存
转换8: 就绪挂起 → 就绪
- 原因:内存可用,需要执行该进程
- 过程:进程被换入内存
转换9: 阻塞 → 阻塞挂起
- 原因:所有阻塞进程都可能被挂起以释放内存
- 过程:进程被换出到外存
转换10: 阻塞挂起 → 阻塞
- 原因:等待的事件发生,但内存不足
- 过程:保持挂起状态,但事件已记录
转换11: 阻塞挂起 → 就绪挂起
- 原因:等待的事件发生
- 过程:状态改为就绪,但仍在外存中
进程的优缺点
进程是操作系统进行资源分配和调度的基本单位,是程序的一次执行实例。每个进程拥有独立的地址空间、文件描述符、环境变量等系统资源。
| 优点 | 缺点 |
|---|---|
| 1. 强大的隔离性 • 独立地址空间,互不干扰 • 一个进程崩溃不影响其他进程 • 安全边界清晰 | 1. 创建和销毁开销大 • 需要分配完整的地址空间 • 资源初始化复杂 • 系统调用频繁 |
| 2. 稳定性高 • 错误不会扩散到其他进程 • 可独立重启崩溃的进程 • 系统整体稳定性好 | 2. 上下文切换成本高 • 需要切换页表、刷新TLB • 缓存失效严重 • 性能开销大 |
| 3. 安全性好 • 进程间无法直接访问内存 • 操作系统提供保护机制 • 权限控制明确 | 3. 通信复杂低效 • 需要IPC机制(管道、消息队列等) • 数据拷贝次数多 • 通信延迟高 |
| 4. 资源管理清晰 • 操作系统可精确统计资源使用 • 资源回收彻底 • 内存泄漏影响范围有限 | 4. 资源占用多 • 每个进程都有独立的资源副本 • 内存浪费严重 • 系统资源消耗大 |
| 5. 编程模型简单 • 单进程程序逻辑清晰 • 无需考虑复杂的同步问题 • 调试相对容易 | 5. 并发性能有限 • 难以充分利用多核CPU • 进程间协调困难 • 扩展性差 |
| 6. 容错性强 • 可监控和管理每个进程 • 支持进程级别的热备 • 系统健壮性好 | 6. 启动速度慢 • 加载程序、初始化资源耗时 • 响应延迟高 • 不适合实时性要求高的场景 |
| 7. 跨平台一致性 • 进程模型在各操作系统间相对统一 • 可移植性好 • 行为可预测 | 7. 状态管理复杂 • 进程状态维护成本高 • 父子进程关系复杂 • 僵尸进程问题 |
进程控制块
操作系统为每个进程维护的数据结构,包含:
| PCB 组件 | 内容描述 |
|---|---|
| 进程标识符 | 进程ID(PID)、父进程ID(PPID) |
| 进程状态 | 运行、就绪、阻塞、挂起等 |
| CPU状态 | 寄存器值、程序计数器、栈指针 |
| 调度信息 | 优先级、调度队列指针 |
| 内存管理 | 页表、段表、内存限制 |
| 记账信息 | CPU使用时间、时间限制 |
| I/O状态 | 打开文件列表、I/O设备分配 |
进程间调度与上下文切换
进程调度
进程调度是操作系统的核心功能,负责决定哪个进程在何时使用CPU资源。调度器需要平衡多个目标:公平性、响应性、吞吐量和系统效率。
进程调度层次
1. 长期调度(作业调度)
- 频率:分钟级
- 作用:决定哪些作业进入内存准备执行
- 控制并发度:防止系统过载
2. 中期调度(内存调度)
- 频率:秒级
- 作用:在内存和磁盘间交换进程
- 解决内存压力:挂起和激活进程
3. 短期调度(CPU调度)
- 频率:毫秒级(频繁)
- 作用:决定下一个运行的进程
- 性能关键:直接影响系统响应性
上下文切换
上下文切换是保存当前运行进程的状态,并恢复另一个进程状态的过程,使得CPU可以从一个进程切换到另一个进程。
上下文切换的完整步骤
1. 触发条件
- 时间片用完
- I/O请求
- 更高优先级进程就绪
- 进程主动放弃CPU
- 中断处理
2. 上下文切换详细流程
3. 详细技术实现
保存当前进程上下文
; 伪汇编代码示例 - 保存进程状态
context_save:
pushad ; 保存所有通用寄存器
pushf ; 保存标志寄存器
mov [eax+PCB.esp], esp ; 保存栈指针到PCB
mov [eax+PCB.eip], eip ; 保存指令指针到PCB
; 保存其他CPU状态...
进程控制块(PCB)结构
// PCB中保存的关键信息
typedef struct pcb {
// 进程标识
pid_t pid;
pid_t ppid;
// CPU状态
uint32_t eax, ebx, ecx, edx;
uint32_t esi, edi, esp, ebp;
uint32_t eip;
uint32_t eflags;
// 内存管理
uint32_t cr3; // 页目录基址寄存器
page_table_t* page_table;
// 调度信息
process_state_t state;
int priority;
uint64_t time_remaining;
// 资源信息
file_descriptor_t* open_files;
uint32_t working_directory;
// 统计信息
uint64_t cpu_time_used;
uint64_t start_time;
} pcb_t;
内存管理单元切换
// 切换地址空间
void switch_address_space(pcb_t* next_process) {
// 1. 刷新TLB(Translation Lookaside Buffer)
flush_tlb();
// 2. 切换页表
write_cr3(next_process->cr3);
// 3. 更新内存管理数据结构
current_page_table = next_process->page_table;
}
恢复新进程上下文
; 伪汇编代码示例 - 恢复进程状态
context_restore:
mov esp, [ebx+PCB.esp] ; 恢复栈指针
mov cr3, [ebx+PCB.cr3] ; 切换地址空间
; 恢复其他寄存器...
popf ; 恢复标志寄存器
popad ; 恢复通用寄存器
jmp [ebx+PCB.eip] ; 跳转到保存的指令地址
调度算法详解
1. 先来先服务(FCFS)
// 简单队列实现
typedef struct {
pcb_t* queue[MAX_PROCESSES];
int front, rear;
} fcfs_queue_t;
void fcfs_schedule(fcfs_queue_t* q) {
if (q->front <= q->rear) {
pcb_t* next = q->queue[q->front++];
switch_to_process(next);
}
}
特点:非抢占式,简单但可能导致护航效应
2. 最短作业优先(SJF)
// 按预计运行时间排序
void sjf_schedule(pcb_t** ready_queue, int count) {
// 按预计运行时间排序
qsort(ready_queue, count, sizeof(pcb_t*), compare_burst_time);
// 选择预计运行时间最短的进程
pcb_t* next = ready_queue[0];
switch_to_process(next);
}
3. 优先级调度
typedef struct {
pcb_t* process;
int priority;
} priority_item_t;
void priority_schedule(priority_queue_t* pq) {
priority_item_t* highest = pq->extract_max();
if (highest) {
switch_to_process(highest->process);
}
}
4. 轮转调度(RR)
// 环形队列实现轮转
typedef struct {
pcb_t* processes[MAX_PROCESSES];
int front, rear;
int time_quantum;
} rr_queue_t;
void rr_schedule(rr_queue_t* q) {
pcb_t* current = q->processes[q->front];
if (current->time_remaining > 0) {
// 进程还有时间,重新入队
q->rear = (q->rear + 1) % MAX_PROCESSES;
q->processes[q->rear] = current;
}
// 选择下一个进程
q->front = (q->front + 1) % MAX_PROCESSES;
pcb_t* next = q->processes[q->front];
switch_to_process(next);
}
5. 多级反馈队列(MLFQ)
typedef struct {
rr_queue_t queues[NUM_PRIORITY_LEVELS];
int time_quantums[NUM_PRIORITY_LEVELS];
} mlfq_scheduler_t;
void mlfq_schedule(mlfq_scheduler_t* scheduler) {
// 从最高优先级队列开始检查
for (int i = 0; i < NUM_PRIORITY_LEVELS; i++) {
if (!is_empty(&scheduler->queues[i])) {
pcb_t* next = rr_schedule_from_queue(&scheduler->queues[i]);
// 如果进程用完时间片还没结束,降低优先级
if (next->time_used >= scheduler->time_quantums[i] &&
next->state != TERMINATED) {
demote_process(next, i + 1);
}
switch_to_process(next);
return;
}
}
}
减少不必要的上下文切换
- 使用线程代替进程:减少地址空间切换
- 调整调度参数:合理设置时间片大小
- 优化I/O操作:使用异步I/O减少阻塞
- 进程亲和性:将进程绑定到特定CPU核心
多进程编程
多进程编程是一种并发编程范式,通过创建多个独立的进程来同时执行多个任务。每个进程拥有自己独立的地址空间、文件描述符、环境变量等系统资源,操作系统负责进程间的调度和资源管理。
多进程编程优缺点对比表格
| 优点 | 缺点 |
|---|---|
| 1. 强大的隔离性 • 独立地址空间,进程间互不干扰 • 一个进程崩溃不会影响其他进程 • 内存泄漏仅限于单个进程 | 1. 创建和销毁开销大 • 需要分配完整的地址空间 • 资源初始化复杂耗时 • 系统调用频繁,性能开销大 |
| 2. 高稳定性和容错性 • 单个进程失败不影响系统整体 • 可独立重启崩溃的进程 • 错误隔离性好 | 2. 上下文切换成本高 • 需要切换页表、刷新TLB • 缓存失效严重 • 切换时间比线程长5-10倍 |
| 3. 安全性好 • 进程间无法直接访问内存 • 操作系统提供硬件级保护 • 权限控制机制完善 | 3. 进程间通信(IPC)复杂 • 需要专门的IPC机制 • 数据序列化和拷贝开销大 • 通信延迟高,编程复杂 |
| 4. 简化编程模型 • 单进程逻辑清晰简单 • 无需考虑复杂的同步问题 • 调试相对容易 | 4. 资源占用多 • 每个进程都有独立资源副本 • 内存浪费严重 • 系统资源消耗大 |
| 5. 更好的CPU利用率 • 真正利用多核CPU并行执行 • I/O阻塞时不浪费CPU时间 • 适合计算密集型任务 | 5. 启动速度慢 • 加载程序、初始化资源耗时 • 响应延迟高 • 不适合实时性要求高的场景 |
| 6. 清晰的资源管理 • 操作系统可精确监控资源使用 • 资源回收彻底 • 资源统计准确 | 6. 状态管理复杂 • 进程状态维护成本高 • 父子进程关系复杂 • 僵尸进程和孤儿进程问题 |
| 7. 跨平台一致性 • 进程模型在各系统间相对统一 • 可移植性好 • 行为可预测性强 | 7. 扩展性限制 • 进程数量受系统限制 • 太多进程导致性能下降 • 难以动态调整规模 |
适合多进程的场景 ✅
-
需要强隔离性的应用
- Web浏览器(每个标签页独立进程)
- 数据库连接池
- 安全敏感服务
-
计算密集型任务
- 科学计算
- 图像/视频处理
- 大数据分析
-
独立服务组件
- 微服务架构
- 系统守护进程
- 批处理任务
不适合多进程的场景 ❌
-
高并发网络服务器
- 每个连接一个进程开销太大
- 推荐使用线程池或异步I/O
-
实时性要求高的应用
- 游戏引擎
- 实时音视频处理
- 高频交易系统
-
资源受限环境
- 嵌入式系统
- 移动设备应用
- 低功耗设备
多进程编程是一种强大但重量级的并发解决方案。在选择多进程时应该考虑:
- 隔离性需求:是否需要进程级别的错误隔离
- 资源条件:系统是否有足够资源支持多进程
- 性能要求:是否可以接受进程创建和IPC的开销
- 开发复杂度:是否有能力处理复杂的进程管理和通信
现代系统通常采用混合架构,在需要强隔离的组件间使用进程,在组件内部使用线程处理并发,充分发挥各自的优势。
多进程创建
进程创建的主要方法
在多进程编程中,主要有以下几种创建进程的方法:
| 方法 | 特点 | 适用场景 |
|---|---|---|
| fork() | 创建当前进程的副本,父子进程共享代码但拥有独立数据空间 | 通用的进程复制,最常用 |
| vfork() | 更轻量的fork,父子进程暂时共享地址空间 | 子进程立即exec的情况 |
| exec系列 | 用新程序替换当前进程映像 | 执行外部程序 |
| fork()+exec组合 | 创建新进程并执行新程序 | 执行外部程序的标准方式 |
| posix_spawn() | 创建进程并设置属性的高级接口 | 需要精细控制进程属性 |
| system() | 执行shell命令 | 简单的命令执行 |
详细方法和代码示例
1. fork() - 最基本的进程创建
特点:
- 创建调用进程的完整副本
- 子进程继承父进程的代码、数据、堆栈、文件描述符等
- 写时复制(Copy-on-Write)技术优化内存使用
- 返回两次:父进程返回子进程PID,子进程返回0
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void fork_example() {
printf("父进程开始,PID: %d\n", getpid());
int shared_var = 100; // 父子进程各自有独立的副本
pid_t pid = fork();
if (pid == -1) {
perror("fork失败");
return;
}
if (pid == 0) {
// 子进程代码
printf("子进程: PID=%d, 父进程PID=%d\n", getpid(), getppid());
shared_var = 200; // 修改的是子进程的副本
printf("子进程: shared_var=%d\n", shared_var);
_exit(0); // 子进程退出
} else {
// 父进程代码
printf("父进程: 创建了子进程PID=%d\n", pid);
printf("父进程: shared_var=%d\n", shared_var); // 仍然是100
wait(NULL); // 等待子进程结束
printf("父进程: 子进程已结束\n");
}
}
2. vfork() - 轻量级进程创建
特点:
- 创建进程但不复制页表
- 子进程与父进程共享地址空间
- 子进程必须先调用exec或exit
- 性能比fork更好,但使用限制多
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void vfork_example() {
printf("vfork示例开始\n");
int stack_var = 50; // 栈变量
pid_t pid = vfork(); // 使用vfork
if (pid == -1) {
perror("vfork失败");
return;
}
if (pid == 0) {
// 子进程 - 与父进程共享地址空间
printf("子进程修改前: stack_var=%d\n", stack_var);
stack_var = 100; // 这会直接影响父进程!
printf("子进程修改后: stack_var=%d\n", stack_var);
// vfork创建的子进程必须调用exec或_exit
_exit(0); // 使用_exit避免刷新stdio缓冲区
} else {
// 父进程
wait(NULL);
printf("父进程: stack_var=%d (被子进程修改)\n", stack_var);
}
}
3. exec系列函数 - 程序执行
exec函数族:
execl()- 参数列表execv()- 参数数组execlp()- 在PATH中查找程序execvp()- 在PATH中查找程序+参数数组execle()- 带环境变量execve()- 系统调用
#include <stdio.h>
#include <unistd.h>
void exec_example() {
printf("当前进程PID: %d\n", getpid());
// 方法1: execl - 参数列表
printf("执行ls命令...\n");
execl("/bin/ls", "ls", "-l", "-a", NULL);
// 如果exec成功,后面的代码不会执行
perror("exec失败");
}
void exec_with_path_example() {
// 方法2: execlp - 在PATH中查找程序
execlp("ps", "ps", "aux", NULL);
perror("execlp失败");
}
void exec_with_env_example() {
// 方法3: execle - 带自定义环境变量
char *envp[] = {"MY_VAR=hello", "PATH=/bin:/usr/bin", NULL};
execle("/bin/echo", "echo", "环境变量测试", NULL, envp);
perror("execle失败");
}
4. fork() + exec组合 - 标准模式
特点:
- 最常用的进程创建模式
- fork创建新进程,exec加载新程序
- 结合了两者的优点
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
void fork_exec_example() {
printf("fork+exec组合示例\n");
pid_t pid = fork();
if (pid == -1) {
perror("fork失败");
return;
}
if (pid == 0) {
// 子进程执行新程序
printf("子进程准备执行date命令\n");
execl("/bin/date", "date", NULL);
// 如果exec失败才执行到这里
perror("exec失败");
_exit(1);
} else {
// 父进程等待子进程结束
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
} else {
printf("子进程异常退出\n");
}
}
}
// 更复杂的例子:执行带参数的程序
void complex_fork_exec() {
pid_t pid = fork();
if (pid == 0) {
// 子进程执行grep命令
char *args[] = {"grep", "main", "*.c", NULL};
execvp("grep", args);
perror("execvp失败");
_exit(1);
} else {
wait(NULL);
printf("grep命令执行完成\n");
}
}
5. system() - 执行shell命令
特点:
- 简单的命令行执行
- 内部使用fork+exec实现
- 阻塞等待命令完成
#include <stdio.h>
#include <stdlib.h>
void system_example() {
printf("system函数示例\n");
// 执行简单的shell命令
int result = system("ls -l | head -5");
if (result == -1) {
perror("system失败");
} else {
printf("命令退出状态: %d\n", WEXITSTATUS(result));
}
// 执行复杂的管道命令
system("ps aux | grep $$ | grep -v grep");
}
6. posix_spawn() - 高级进程创建
特点:
- POSIX标准的高级接口
- 可以精细控制进程属性
- 比fork+exec更高效
#include <stdio.h>
#include <spawn.h>
#include <sys/wait.h>
extern char **environ; // 环境变量
void posix_spawn_example() {
printf("posix_spawn示例\n");
pid_t pid;
char *argv[] = {"ls", "-l", "-h", NULL};
// 使用posix_spawn创建进程
int result = posix_spawn(&pid, "/bin/ls", NULL, NULL, argv, environ);
if (result != 0) {
perror("posix_spawn失败");
return;
}
printf("创建的子进程PID: %d\n", pid);
// 等待子进程结束
int status;
waitpid(pid, &status, 0);
printf("子进程执行完成\n");
}
综合比较表格
| 方法 | 性能 | 灵活性 | 安全性 | 可移植性 | 使用难度 |
|---|---|---|---|---|---|
| fork() | 中等(写时复制) | 高 | 高 | 所有Unix系统 | 简单 |
| vfork() | 高 | 低 | 低(共享地址空间) | 多数Unix系统 | 中等 |
| exec系列 | 高(替换当前进程) | 中 | 高 | 所有Unix系统 | 简单 |
| fork()+exec | 中等 | 高 | 高 | 所有Unix系统 | 中等 |
| system() | 低(需要shell) | 低 | 中(shell注入风险) | 所有Unix系统 | 非常简单 |
| posix_spawn() | 高 | 高 | 高 | POSIX系统 | 复杂 |
实际应用建议
选择指南:
- 简单任务复制:使用
fork() - 执行外部命令:使用
fork() + exec - 性能关键且子进程立即exec:考虑
vfork() - 执行shell命令:使用
system()(注意安全) - 需要精细控制:使用
posix_spawn()
安全注意事项:
// 不安全的system使用
system("rm -rf /user/files/" + user_input); // 危险!
// 安全的替代方案
void safe_command_execution(const char* filename) {
// 验证输入
if (strstr(filename, "..") || strchr(filename, '/')) {
fprintf(stderr, "无效文件名\n");
return;
}
// 使用execl避免shell注入
execl("/bin/rm", "rm", "-f", filename, NULL);
}
错误处理最佳实践:
pid_t pid = fork();
if (pid == -1) {
// fork失败处理
perror("fork失败");
switch (errno) {
case EAGAIN:
printf("系统进程数达到限制\n");
break;
case ENOMEM:
printf("内存不足\n");
break;
default:
printf("未知错误\n");
}
return;
}
if (pid == 0) {
// 子进程代码
execl("/bin/ls", "ls", NULL);
perror("exec失败"); // 只有exec失败时才执行
_exit(127); // 使用_exit避免刷新父进程的缓冲区
} else {
// 父进程代码
int status;
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid失败");
} else {
if (WIFEXITED(status)) {
printf("子进程退出码: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程被信号终止: %d\n", WTERMSIG(status));
}
}
}
不同的进程创建方法各有优缺点,选择合适的方法取决于具体需求:
- 学习和简单应用:从
fork()和system()开始 - 生产环境:推荐使用
fork() + exec组合 - 高性能需求:考虑
posix_spawn()或特定场景下的vfork() - 安全敏感:避免直接使用
system()处理用户输入
多进程结束与资源回收
进程(结束)退出方式
1. 正常退出方式
A. 从main函数返回
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("程序正常结束\n");
return 0; // 等价于 exit(0)
}
B. 调用exit()函数
#include <stdio.h>
#include <stdlib.h>
void exit_example() {
printf("准备退出程序\n");
// 刷新所有缓冲区,调用atexit注册的函数
exit(EXIT_SUCCESS); // 正常退出
// 或者 exit(EXIT_FAILURE); // 异常退出
}
C. 调用_exit()或_Exit()
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void _exit_example() {
printf("这条消息不会被显示"); // 没有换行符,缓冲区未刷新
// 立即退出,不刷新缓冲区,不调用atexit函数
_exit(0); // POSIX系统调用
// 或 _Exit(0); // C99标准
}
2. 异常退出方式
A. 被信号终止
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_exit_example() {
printf("进程PID: %d\n", getpid());
// 发送信号终止自己
raise(SIGTERM); // 优雅终止
// 或 raise(SIGKILL); // 强制终止(不可捕获)
printf("这行不会执行\n");
}
B. 调用abort()
#include <stdio.h>
#include <stdlib.h>
void abort_example() {
printf("准备调用abort()\n");
// 产生SIGABRT信号,通常导致核心转储
abort();
printf("这行不会执行\n");
}
退出状态码
标准退出码含义
#include <stdio.h>
#include <stdlib.h>
void exit_codes_example() {
int result = some_operation();
if (result == 0) {
exit(EXIT_SUCCESS); // 成功,通常为0
} else if (result == -1) {
exit(EXIT_FAILURE); // 失败,通常为1
} else if (result == 2) {
exit(2); // 自定义错误码
}
}
// 常见的退出码约定:
// 0 - 成功
// 1 - 一般错误
// 2 - 命令行用法错误
// 126 - 命令不可执行
// 127 - 命令未找到
// 128+ - 信号终止(128 + 信号编号)
资源回收机制
1. 僵尸进程问题
什么是僵尸进程
僵尸进程(Zombie Process)是已经终止但仍然保留在进程表中,等待父进程读取其终止状态的进程。僵尸进程会占用系统资源,但不会影响程序运行。简单的说,僵尸进程是已经死掉的进程的残留信息,父进程需要通过调用wait()或waitpid()来回收这些信息。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void zombie_example() {
pid_t pid = fork();
if (pid == 0) {
// 子进程立即退出,成为僵尸
printf("子进程PID: %d 退出\n", getpid());
exit(0);
} else {
// 父进程不调用wait,子进程成为僵尸
printf("父进程继续运行,子进程PID: %d 将成为僵尸\n", pid);
sleep(30); // 在此期间检查进程状态
printf("父进程退出\n");
}
}
2. 等待子进程退出
A. wait() - 等待任意子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void wait_example() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程工作...\n");
sleep(2);
printf("子进程结束\n");
exit(42); // 自定义退出码
} else {
// 父进程
printf("父进程等待子进程...\n");
int status;
pid_t finished_pid = wait(&status);
if (finished_pid == -1) {
perror("wait失败");
return;
}
printf("子进程 %d 已结束\n", finished_pid);
if (WIFEXITED(status)) {
printf("正常退出,退出码: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("被信号终止,信号: %d\n", WTERMSIG(status));
}
}
}
B. waitpid() - 等待特定子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void waitpid_example() {
pid_t children[3];
// 创建多个子进程
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 %d 开始,PID: %d\n", i, getpid());
sleep(i + 1); // 不同的睡眠时间
printf("子进程 %d 结束\n", i);
exit(i); // 不同的退出码
} else {
children[i] = pid;
}
}
// 父进程等待特定子进程
for (int i = 0; i < 3; i++) {
int status;
// 阻塞等待特定子进程
pid_t finished_pid = waitpid(children[i], &status, 0);
if (WIFEXITED(status)) {
printf("子进程 %d (PID: %d) 退出码: %d\n",
i, finished_pid, WEXITSTATUS(status));
}
}
}
C. 非阻塞等待
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
void nonblocking_wait_example() {
pid_t pid = fork();
if (pid == 0) {
// 子进程长时间运行
printf("子进程开始长时间任务...\n");
sleep(10);
exit(0);
} else {
// 父进程非阻塞等待
int status;
while (1) {
pid_t result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
// 子进程还在运行
printf("子进程仍在运行,父进程做其他工作...\n");
sleep(1);
} else if (result == pid) {
// 子进程已结束
printf("子进程已结束\n");
break;
} else {
// 错误
perror("waitpid错误");
break;
}
}
}
}
3. 信号处理与子进程回收
使用SIGCHLD信号
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
// SIGCHLD信号处理函数
void sigchld_handler(int sig) {
int status;
pid_t pid;
// 循环回收所有已结束的子进程
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
printf("信号处理: 子进程 %d 正常退出,退出码: %d\n",
pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("信号处理: 子进程 %d 被信号 %d 终止\n",
pid, WTERMSIG(status));
}
}
}
void sigchld_example() {
// 注册SIGCHLD信号处理函数
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
return;
}
// 创建多个子进程
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
printf("子进程 %d 开始\n", getpid());
sleep(i + 1);
exit(i);
}
}
// 父进程继续工作
printf("父进程继续工作,子进程结束时会自动回收\n");
for (int i = 0; i < 10; i++) {
printf("父进程工作... %d\n", i);
sleep(1);
}
}
资源清理常用方法
1. 退出处理函数
使用atexit()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void cleanup_temp_files() {
printf("清理临时文件...\n");
// 实际代码中删除临时文件
}
void close_database_connections() {
printf("关闭数据库连接...\n");
// 实际代码中关闭数据库
}
void release_memory() {
printf("释放内存资源...\n");
// 实际代码中释放动态内存
}
void atexit_example() {
// 注册退出处理函数(逆序执行)
atexit(release_memory);
atexit(close_database_connections);
atexit(cleanup_temp_files);
printf("主程序运行...\n");
// 正常退出时会自动调用注册的函数
// exit(0);
}
使用on_exit()(Linux特有)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void exit_handler(int status, void *arg) {
printf("退出处理: 状态=%d, 参数=%s\n", status, (char*)arg);
}
void on_exit_example() {
// 注册退出处理函数
on_exit(exit_handler, "自定义参数");
printf("程序运行中...\n");
exit(42); // 退出时会调用exit_handler
}
2. 文件描述符清理
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
void file_cleanup_example() {
int fd1, fd2;
// 打开文件
fd1 = open("file1.txt", O_CREAT | O_WRONLY, 0644);
fd2 = open("file2.txt", O_CREAT | O_WRONLY, 0644);
if (fd1 == -1 || fd2 == -1) {
perror("打开文件失败");
// 清理已打开的文件描述符
if (fd1 != -1) close(fd1);
if (fd2 != -1) close(fd2);
exit(1);
}
// 工作代码...
// 正常退出前清理
close(fd1);
close(fd2);
// 或者让系统自动关闭(不推荐用于重要资源)
}
3. 避免资源泄漏的模式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
void robust_process_management() {
pid_t pid = fork();
if (pid == 0) {
// 子进程代码
// 设置资源限制
struct rlimit lim;
lim.rlim_cur = 100; // 软限制
lim.rlim_max = 200; // 硬限制
setrlimit(RLIMIT_CPU, &lim);
// 子进程工作
printf("子进程工作...\n");
sleep(2);
// 子进程显式清理
printf("子进程清理资源...\n");
exit(0);
} else {
// 父进程代码
int status;
// 设置超时等待
for (int i = 0; i < 5; i++) {
pid_t result = waitpid(pid, &status, WNOHANG);
if (result == pid) {
// 子进程正常结束
printf("子进程正常结束\n");
break;
} else if (result == 0 && i == 4) {
// 超时,强制终止子进程
printf("子进程超时,强制终止\n");
kill(pid, SIGKILL);
waitpid(pid, &status, 0); // 等待确认终止
} else {
sleep(1);
}
}
// 检查终止原因
if (WIFEXITED(status)) {
printf("退出码: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("被信号终止: %d\n", WTERMSIG(status));
}
}
}
特殊情况的处理
1. 孤儿进程处理
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void orphan_process_example() {
pid_t pid = fork();
if (pid == 0) {
// 子进程让父进程先退出
sleep(1);
printf("子进程PID: %d, 新的父进程PID: %d (init)\n",
getpid(), getppid());
exit(0);
} else {
// 父进程立即退出
printf("父进程退出,子进程PID: %d 将成为孤儿\n", pid);
exit(0);
}
}
2. 进程组和会话管理
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void process_group_example() {
pid_t pid = fork();
if (pid == 0) {
// 子创建新的进程组
setpgid(0, 0);
printf("子进程在新进程组中\n");
// 工作...
sleep(10);
exit(0);
} else {
// 父进程可以终止整个进程组
sleep(1);
printf("父进程终止整个进程组\n");
kill(-pid, SIGTERM); // 负号表示进程组
int status;
waitpid(pid, &status, 0);
}
}
总结表格
| 退出方式 | 特点 | 适用场景 |
|---|---|---|
| return from main | 自动调用exit() | 简单程序 |
| exit() | 刷新缓冲区,调用atexit函数 | 标准退出方式 |
| _exit()/_Exit() | 立即退出,不清理 | 子进程避免干扰父进程 |
| 信号终止 | 可能产生核心转储 | 调试或异常处理 |
| abort() | 产生SIGABRT信号 | 严重错误情况 |
| 回收方法 | 特点 | 适用场景 |
|---|---|---|
| wait() | 阻塞等待任意子进程 | 简单子进程管理 |
| waitpid() | 可指定进程,可非阻塞 | 精确控制,多子进程 |
| SIGCHLD信号 | 异步通知 | 避免阻塞父进程 |
| WNOHANG | 非阻塞检查 | 父进程需要继续工作 |
实践总结
- 总是检查系统调用返回值
- 使用wait()或waitpid()回收子进程
- 注册退出处理函数清理资源
- 处理SIGCHLD信号避免僵尸进程
- 为长时间运行的子进程设置超时
- 适当使用进程组管理相关进程
进程间通信
进程间通信(Inter-Process Communication, IPC) 是指在不同进程之间传播或交换信息的技术。由于每个进程都有自己独立的地址空间,一个进程不能直接访问另一个进程的变量和数据结构,因此需要操作系统提供特殊的机制来实现进程间的数据共享和通信。
IPC的主要目的
- 数据传输:一个进程需要将数据发送给另一个进程
- 资源共享:多个进程共享相同的资源
- 通知事件:一个进程需要向另一个进程通知某事件的发生
- 进程控制:有些进程希望控制另一个进程的执行
1. 无名管道(Unnamed Pipe)
特点:
- 只能用于具有亲缘关系的进程(如父子进程)
- 半双工通信(数据只能单向流动)
- 存在于内存中,随进程的结束而销毁
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
void unnamed_pipe_example() {
int pipefd[2];
pid_t pid;
char buffer[100];
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
return;
}
pid = fork();
if (pid == -1) {
perror("fork");
return;
}
if (pid == 0) {
// 子进程 - 读取数据
close(pipefd[1]); // 关闭写端
ssize_t count = read(pipefd[0], buffer, sizeof(buffer));
if (count > 0) {
printf("子进程收到: %s\n", buffer);
}
close(pipefd[0]);
_exit(0);
} else {
// 父进程 - 写入数据
close(pipefd[0]); // 关闭读端
const char *message = "Hello from parent process!";
write(pipefd[1], message, strlen(message) + 1);
close(pipefd[1]);
wait(NULL); // 等待子进程结束
printf("父进程: 数据已发送\n");
}
}
// 双向通信示例
void bidirectional_pipe_example() {
int pipe1[2], pipe2[2];
pid_t pid;
char buffer[100];
// 创建两个管道实现双向通信
if (pipe(pipe1) == -1 || pipe(pipe2) == -1) {
perror("pipe");
return;
}
pid = fork();
if (pid == 0) {
// 子进程
close(pipe1[1]); // 关闭pipe1的写端
close(pipe2[0]); // 关闭pipe2的读端
// 从父进程读取
read(pipe1[0], buffer, sizeof(buffer));
printf("子进程收到: %s\n", buffer);
// 向父进程发送
const char *reply = "Hello from child!";
write(pipe2[1], reply, strlen(reply) + 1);
close(pipe1[0]);
close(pipe2[1]);
_exit(0);
} else {
// 父进程
close(pipe1[0]); // 关闭pipe1的读端
close(pipe2[1]); // 关闭pipe2的写端
// 向子进程发送
const char *message = "Hello from parent!";
write(pipe1[1], message, strlen(message) + 1);
// 从子进程读取
read(pipe2[0], buffer, sizeof(buffer));
printf("父进程收到回复: %s\n", buffer);
close(pipe1[1]);
close(pipe2[0]);
wait(NULL);
}
}
2. 有名管道(Named Pipe / FIFO)
特点:
- 可用于无亲缘关系的进程
- 在文件系统中有一个路径名
- 持久化直到被显式删除
写入进程 (fifo_writer.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FIFO_PATH "/tmp/my_fifo"
void fifo_writer() {
int fd;
char message[] = "Hello from FIFO writer!";
// 创建FIFO(如果不存在)
if (mkfifo(FIFO_PATH, 0666) == -1) {
// 如果FIFO已存在,可以忽略这个错误
if (errno != EEXIST) {
perror("mkfifo");
return;
}
}
printf("写入进程: 等待读取进程连接...\n");
// 打开FIFO进行写入(会阻塞直到有进程打开读取)
fd = open(FIFO_PATH, O_WRONLY);
if (fd == -1) {
perror("open");
return;
}
// 写入数据
if (write(fd, message, strlen(message) + 1) == -1) {
perror("write");
} else {
printf("写入进程: 数据已发送\n");
}
close(fd);
// 删除FIFO文件(可选)
unlink(FIFO_PATH);
}
读取进程 (fifo_reader.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define FIFO_PATH "/tmp/my_fifo"
void fifo_reader() {
int fd;
char buffer[100];
// 确保FIFO存在
if (access(FIFO_PATH, F_OK) == -1) {
printf("FIFO不存在,请先运行写入进程\n");
return;
}
printf("读取进程: 打开FIFO...\n");
// 打开FIFO进行读取
fd = open(FIFO_PATH, O_RDONLY);
if (fd == -1) {
perror("open");
return;
}
// 读取数据
ssize_t count = read(fd, buffer, sizeof(buffer));
if (count > 0) {
printf("读取进程收到: %s\n", buffer);
}
close(fd);
}
3. 共享内存(Shared Memory)
特点:
- 最快的IPC方式
- 多个进程可以直接读写同一块内存区域
- 需要同步机制(如信号量)来避免竞态条件
写入进程 (shm_writer.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SHM_SIZE 1024
#define SHM_KEY 0x1234
#define SEM_KEY 0x5678
// 信号量操作
void sem_wait(int semid) {
struct sembuf sb = {0, -1, 0};
semop(semid, &sb, 1);
}
void sem_signal(int semid) {
struct sembuf sb = {0, 1, 0};
semop(semid, &sb, 1);
}
void shared_memory_writer() {
int shmid, semid;
char *shared_memory;
// 创建共享内存段
shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
return;
}
// 创建信号量
semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
return;
}
// 初始化信号量为1(二进制信号量)
semctl(semid, 0, SETVAL, 1);
// 附加共享内存到当前进程
shared_memory = shmat(shmid, NULL, 0);
if (shared_memory == (char *)-1) {
perror("shmat");
return;
}
printf("写入进程: 准备写入数据...\n");
// 使用信号量保护共享内存
sem_wait(semid);
// 写入数据到共享内存
const char *message = "Hello from shared memory writer!";
strcpy(shared_memory, message);
printf("写入进程: 数据已写入共享内存\n");
sem_signal(semid);
// 等待用户输入,让读取进程有时间读取
printf("按Enter键继续...\n");
getchar();
// 清理
shmdt(shared_memory);
shmctl(shmid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID);
}
读取进程 (shm_reader.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SHM_SIZE 1024
#define SHM_KEY 0x1234
#define SEM_KEY 0x5678
void sem_wait(int semid) {
struct sembuf sb = {0, -1, 0};
semop(semid, &sb, 1);
}
void sem_signal(int semid) {
struct sembuf sb = {0, 1, 0};
semop(semid, &sb, 1);
}
void shared_memory_reader() {
int shmid, semid;
char *shared_memory;
char buffer[SHM_SIZE];
// 获取共享内存段
shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
return;
}
// 获取信号量
semid = semget(SEM_KEY, 1, 0666);
if (semid == -1) {
perror("semget");
return;
}
// 附加共享内存到当前进程
shared_memory = shmat(shmid, NULL, 0);
if (shared_memory == (char *)-1) {
perror("shmat");
return;
}
printf("读取进程: 等待数据...\n");
// 使用信号量保护共享内存访问
sem_wait(semid);
// 从共享内存读取数据
strcpy(buffer, shared_memory);
printf("读取进程收到: %s\n", buffer);
sem_signal(semid);
// 分离共享内存
shmdt(shared_memory);
}
4. 消息队列(Message Queue)
特点:
- 消息的链表,存储在内核中
- 支持不同消息类型
- 可以按类型读取消息
发送进程 (msg_sender.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSG_KEY 0x1234
// 消息结构
struct message {
long mtype;
char mtext[100];
int mdata;
};
void message_queue_sender() {
int msgid;
struct message msg;
// 创建消息队列
msgid = msgget(MSG_KEY, IPC_CREAT | 0666);
if (msgid == -1) {
perror("msgget");
return;
}
printf("发送进程: 准备发送消息...\n");
// 发送类型1的消息
msg.mtype = 1;
strcpy(msg.mtext, "紧急消息");
msg.mdata = 100;
if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
perror("msgsnd");
return;
}
printf("发送进程: 类型1消息已发送\n");
// 发送类型2的消息
msg.mtype = 2;
strcpy(msg.mtext, "普通消息");
msg.mdata = 200;
if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) == -1) {
perror("msgsnd");
return;
}
printf("发送进程: 类型2消息已发送\n");
// 等待,让接收进程有时间接收
sleep(2);
// 删除消息队列
msgctl(msgid, IPC_RMID, NULL);
printf("发送进程: 消息队列已删除\n");
}
接收进程 (msg_receiver.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#define MSG_KEY 0x1234
struct message {
long mtype;
char mtext[100];
int mdata;
};
void message_queue_receiver() {
int msgid;
struct message msg;
// 获取消息队列
msgid = msgget(MSG_KEY, 0666);
if (msgid == -1) {
perror("msgget");
return;
}
printf("接收进程: 等待消息...\n");
// 接收类型1的消息(紧急消息)
if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 1, 0) == -1) {
perror("msgrcv");
return;
}
printf("接收进程收到类型1: %s, 数据: %d\n", msg.mtext, msg.mdata);
// 接收类型2的消息(普通消息)
if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 2, 0) == -1) {
perror("msgrcv");
return;
}
printf("接收进程收到类型2: %s, 数据: %d\n", msg.mtext, msg.mdata);
// 尝试非阻塞接收(应该没有消息了)
if (msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 0, IPC_NOWAIT) == -1) {
printf("接收进程: 没有更多消息\n");
}
}
5. 信号量(Semaphore)
特点:
- 用于进程同步,而不是数据传输
- 控制对共享资源的访问
- 可以用于解决竞态条件
生产者-消费者示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define KEY 0x1234
#define BUFFER_SIZE 5
// 共享缓冲区结构
struct shared_buffer {
int data[BUFFER_SIZE];
int in;
int out;
};
// 信号量操作
void P(int semid, int semnum) {
struct sembuf sb = {semnum, -1, 0};
semop(semid, &sb, 1);
}
void V(int semid, int semnum) {
struct sembuf sb = {semnum, 1, 0};
semop(semid, &sb, 1);
}
void producer() {
int shmid, semid;
struct shared_buffer *buffer;
// 获取共享内存和信号量
shmid = shmget(KEY, sizeof(struct shared_buffer), 0666);
semid = semget(KEY, 2, 0666);
if (shmid == -1 || semid == -1) {
printf("请先运行初始化程序\n");
return;
}
// 附加共享内存
buffer = shmat(shmid, NULL, 0);
for (int i = 0; i < 10; i++) {
// 生产一个项目
int item = i + 100;
// 等待空位(empty信号量)
P(semid, 0);
// 进入临界区(mutex信号量)
P(semid, 1);
// 生产项目
buffer->data[buffer->in] = item;
printf("生产者: 生产项目 %d 到位置 %d\n", item, buffer->in);
buffer->in = (buffer->in + 1) % BUFFER_SIZE;
// 离开临界区
V(semid, 1);
// 通知有可用项目(full信号量)
V(semid, 2);
sleep(1); // 模拟生产时间
}
shmdt(buffer);
}
void consumer() {
int shmid, semid;
struct shared_buffer *buffer;
// 获取共享内存和信号量
shmid = shmget(KEY, sizeof(struct shared_buffer), 0666);
semid = semget(KEY, 2, 0666);
if (shmid == -1 || semid == -1) {
printf("请先运行初始化程序\n");
return;
}
// 附加共享内存
buffer = shmat(shmid, NULL, 0);
for (int i = 0; i < 10; i++) {
// 等待可用项目(full信号量)
P(semid, 2);
// 进入临界区(mutex信号量)
P(semid, 1);
// 消费项目
int item = buffer->data[buffer->out];
printf("消费者: 消费项目 %d 从位置 %d\n", item, buffer->out);
buffer->out = (buffer->out + 1) % BUFFER_SIZE;
// 离开临界区
V(semid, 1);
// 通知有空位(empty信号量)
V(semid, 0);
sleep(2); // 模拟消费时间
}
shmdt(buffer);
}
// 初始化共享内存和信号量
void init_resources() {
int shmid, semid;
struct shared_buffer *buffer;
// 创建共享内存
shmid = shmget(KEY, sizeof(struct shared_buffer), IPC_CREAT | 0666);
// 创建信号量集(3个信号量:empty, mutex, full)
semid = semget(KEY, 3, IPC_CREAT | 0666);
// 初始化信号量
semctl(semid, 0, SETVAL, BUFFER_SIZE); // empty = BUFFER_SIZE
semctl(semid, 1, SETVAL, 1); // mutex = 1
semctl(semid, 2, SETVAL, 0); // full = 0
// 初始化共享缓冲区
buffer = shmat(shmid, NULL, 0);
buffer->in = 0;
buffer->out = 0;
shmdt(buffer);
printf("资源初始化完成\n");
}
// 清理资源
void cleanup_resources() {
int shmid = shmget(KEY, 0, 0);
int semid = semget(KEY, 0, 0);
if (shmid != -1) shmctl(shmid, IPC_RMID, NULL);
if (semid != -1) semctl(semid, 0, IPC_RMID);
printf("资源清理完成\n");
}
IPC方式比较总结
| IPC方式 | 通信关系 | 数据传输 | 同步需求 | 性能 | 复杂度 |
|---|---|---|---|---|---|
| 无名管道 | 亲缘进程 | 字节流 | 自动同步 | 高 | 低 |
| 有名管道 | 任意进程 | 字节流 | 自动同步 | 中 | 中 |
| 共享内存 | 任意进程 | 内存直接访问 | 需要显式同步 | 最高 | 高 |
| 消息队列 | 任意进程 | 消息 | 自动同步 | 中 | 中 |
| 信号量 | 任意进程 | 无数据传输 | 同步原语 | 高 | 高 |
选择建议
- 父子进程通信:无名管道
- 无亲缘关系进程:有名管道、共享内存、消息队列
- 高性能需求:共享内存(配合信号量)
- 结构化消息:消息队列
- 纯同步需求:信号量
这些IPC机制可以单独使用,也可以组合使用以满足复杂的进程间通信需求。
进程间同步
进程同步是指协调多个进程的执行顺序,确保它们以可预测的方式访问共享资源,避免竞态条件和数据不一致的问题。在多进程环境中,由于进程的并发执行和资源共享,同步机制至关重要。
1. 信号量(Semaphore)
信号量是最常用的进程同步机制,是一个计数器,用于控制多个进程对共享资源的访问。
System V 信号量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/wait.h>
// 联合体用于semctl
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
// P操作 - 等待信号量
void sem_wait(int semid, int semnum) {
struct sembuf sb = {semnum, -1, 0};
semop(semid, &sb, 1);
}
// V操作 - 释放信号量
void sem_signal(int semid, int semnum) {
struct sembuf sb = {semnum, 1, 0};
semop(semid, &sb, 1);
}
// 二进制信号量示例(互斥锁)
void binary_semaphore_example() {
int semid;
key_t key = ftok(".", 'S');
union semun arg;
// 创建信号量集(1个信号量)
semid = semget(key, 1, IPC_CREAT | 0666);
// 初始化信号量为1(二进制信号量)
arg.val = 1;
semctl(semid, 0, SETVAL, arg);
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程尝试获取锁...\n");
sem_wait(semid, 0); // P操作
printf("子进程获得锁,进入临界区\n");
sleep(2); // 模拟临界区操作
printf("子进程离开临界区\n");
sem_signal(semid, 0); // V操作
exit(0);
} else {
// 父进程
sleep(1); // 确保子进程先尝试获取锁
printf("父进程尝试获取锁...\n");
sem_wait(semid, 0); // P操作(会阻塞直到子进程释放)
printf("父进程获得锁,进入临界区\n");
sleep(1);
printf("父进程离开临界区\n");
sem_signal(semid, 0); // V操作
wait(NULL);
// 清理信号量
semctl(semid, 0, IPC_RMID);
}
}
// 计数信号量示例(控制资源池)
void counting_semaphore_example() {
int semid;
key_t key = ftok(".", 'C');
union semun arg;
// 创建3个资源的信号量
semid = semget(key, 1, IPC_CREAT | 0666);
arg.val = 3; // 初始有3个可用资源
semctl(semid, 0, SETVAL, arg);
// 创建多个子进程竞争资源
for (int i = 0; i < 5; i++) {
if (fork() == 0) {
printf("进程 %d 等待资源...\n", getpid());
sem_wait(semid, 0); // 获取资源
printf("进程 %d 获得资源,使用中...\n", getpid());
sleep(2); // 使用资源
sem_signal(semid, 0); // 释放资源
printf("进程 %d 释放资源\n", getpid());
exit(0);
}
}
// 等待所有子进程结束
for (int i = 0; i < 5; i++) {
wait(NULL);
}
semctl(semid, 0, IPC_RMID);
}
POSIX 命名信号量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <sys/wait.h>
#define SEM_NAME "/my_named_sem"
void posix_semaphore_example() {
sem_t *sem;
// 创建命名信号量
sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
if (sem == SEM_FAILED) {
perror("sem_open");
return;
}
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程等待信号量...\n");
sem_wait(sem);
printf("子进程进入临界区\n");
sleep(2);
printf("子进程离开临界区\n");
sem_post(sem);
sem_close(sem);
exit(0);
} else {
// 父进程
sleep(1);
printf("父进程等待信号量...\n");
sem_wait(sem);
printf("父进程进入临界区\n");
sleep(1);
printf("父进程离开临界区\n");
sem_post(sem);
wait(NULL);
sem_close(sem);
sem_unlink(SEM_NAME); // 删除命名信号量
}
}
2. 文件锁(File Locking)
使用文件锁可以实现进程间的互斥访问。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <fcntl.h>
#include <sys/wait.h>
void file_lock_example() {
const char *lockfile = "/tmp/my_lockfile";
int fd;
// 创建锁文件
fd = open(lockfile, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("open");
return;
}
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程尝试获取文件锁...\n");
// 获取排他锁(会阻塞直到获取)
if (flock(fd, LOCK_EX) == -1) {
perror("flock");
exit(1);
}
printf("子进程获得文件锁\n");
sleep(2);
printf("子进程释放文件锁\n");
flock(fd, LOCK_UN); // 释放锁
close(fd);
exit(0);
} else {
// 父进程
sleep(1);
printf("父进程尝试获取文件锁...\n");
// 非阻塞尝试获取锁
if (flock(fd, LOCK_EX | LOCK_NB) == -1) {
printf("父进程: 锁被占用,等待...\n");
// 阻塞等待
flock(fd, LOCK_EX);
}
printf("父进程获得文件锁\n");
sleep(1);
printf("父进程释放文件锁\n");
flock(fd, LOCK_UN);
close(fd);
wait(NULL);
// 删除锁文件
unlink(lockfile);
}
}
// 使用fcntl记录锁
void fcntl_lock_example() {
const char *filename = "/tmp/shared_file";
int fd = open(filename, O_CREAT | O_RDWR, 0666);
struct flock lock;
pid_t pid = fork();
if (pid == 0) {
// 子进程设置写锁
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0; // 锁定整个文件
printf("子进程设置写锁...\n");
fcntl(fd, F_SETLKW, &lock); // 阻塞直到获取锁
printf("子进程获得写锁\n");
sleep(2);
// 释放锁
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &lock);
printf("子进程释放锁\n");
close(fd);
exit(0);
} else {
sleep(1);
// 父进程尝试获取读锁
lock.l_type = F_RDLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
printf("父进程尝试获取读锁...\n");
if (fcntl(fd, F_SETLK, &lock) == -1) {
printf("父进程: 文件被锁定,无法获取读锁\n");
} else {
printf("父进程获得读锁\n");
lock.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &lock);
}
wait(NULL);
close(fd);
unlink(filename);
}
}
3. 互斥锁在共享内存中
虽然互斥锁通常用于线程,但可以通过共享内存用于进程间同步。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>
typedef struct {
pthread_mutex_t mutex;
pthread_mutexattr_t mutex_attr;
int shared_counter;
} shared_data_t;
void shared_memory_mutex_example() {
// 创建共享内存区域
shared_data_t *shared = mmap(NULL, sizeof(shared_data_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (shared == MAP_FAILED) {
perror("mmap");
return;
}
// 初始化互斥锁属性为进程共享
pthread_mutexattr_init(&shared->mutex_attr);
pthread_mutexattr_setpshared(&shared->mutex_attr, PTHREAD_PROCESS_SHARED);
// 初始化互斥锁
pthread_mutex_init(&shared->mutex, &shared->mutex_attr);
shared->shared_counter = 0;
// 创建多个子进程
for (int i = 0; i < 3; i++) {
if (fork() == 0) {
// 子进程
for (int j = 0; j < 5; j++) {
pthread_mutex_lock(&shared->mutex);
// 临界区开始
int temp = shared->shared_counter;
usleep(100000); // 模拟处理时间
shared->shared_counter = temp + 1;
printf("进程 %d: 计数器 = %d\n", getpid(), shared->shared_counter);
// 临界区结束
pthread_mutex_unlock(&shared->mutex);
usleep(50000);
}
exit(0);
}
}
// 等待所有子进程
for (int i = 0; i < 3; i++) {
wait(NULL);
}
printf("最终计数器值: %d (应该是15)\n", shared->shared_counter);
// 清理
pthread_mutex_destroy(&shared->mutex);
pthread_mutexattr_destroy(&shared->mutex_attr);
munmap(shared, sizeof(shared_data_t));
}
4. 条件变量在共享内存中
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_mutexattr_t mutex_attr;
pthread_condattr_t cond_attr;
int data_ready;
int shared_data;
} shared_cond_t;
void condition_variable_example() {
shared_cond_t *shared = mmap(NULL, sizeof(shared_cond_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// 初始化属性
pthread_mutexattr_init(&shared->mutex_attr);
pthread_mutexattr_setpshared(&shared->mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_condattr_init(&shared->cond_attr);
pthread_condattr_setpshared(&shared->cond_attr, PTHREAD_PROCESS_SHARED);
// 初始化互斥锁和条件变量
pthread_mutex_init(&shared->mutex, &shared->mutex_attr);
pthread_cond_init(&shared->cond, &shared->cond_attr);
shared->data_ready = 0;
shared->shared_data = 0;
pid_t producer = fork();
if (producer == 0) {
// 生产者进程
for (int i = 1; i <= 3; i++) {
pthread_mutex_lock(&shared->mutex);
// 生产数据
shared->shared_data = i * 100;
shared->data_ready = 1;
printf("生产者: 生产数据 %d\n", shared->shared_data);
// 通知消费者
pthread_cond_signal(&shared->cond);
pthread_mutex_unlock(&shared->mutex);
sleep(1); // 生产间隔
}
exit(0);
}
pid_t consumer = fork();
if (consumer == 0) {
// 消费者进程
for (int i = 0; i < 3; i++) {
pthread_mutex_lock(&shared->mutex);
// 等待数据就绪
while (!shared->data_ready) {
printf("消费者: 等待数据...\n");
pthread_cond_wait(&shared->cond, &shared->mutex);
}
// 消费数据
printf("消费者: 消费数据 %d\n", shared->shared_data);
shared->data_ready = 0;
pthread_mutex_unlock(&shared->mutex);
}
exit(0);
}
// 等待子进程结束
wait(NULL);
wait(NULL);
// 清理
pthread_mutex_destroy(&shared->mutex);
pthread_cond_destroy(&shared->cond);
pthread_mutexattr_destroy(&shared->mutex_attr);
pthread_condattr_destroy(&shared->cond_attr);
munmap(shared, sizeof(shared_cond_t));
}
5. 屏障(Barrier)
屏障用于同步多个进程,使它们在某个点等待,直到所有进程都到达后才继续执行。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>
#define NUM_PROCESSES 4
typedef struct {
pthread_barrier_t barrier;
pthread_barrierattr_t barrier_attr;
} shared_barrier_t;
void barrier_example() {
shared_barrier_t *shared = mmap(NULL, sizeof(shared_barrier_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// 初始化屏障属性
pthread_barrierattr_init(&shared->barrier_attr);
pthread_barrierattr_setpshared(&shared->barrier_attr, PTHREAD_PROCESS_SHARED);
// 初始化屏障,等待4个进程
pthread_barrier_init(&shared->barrier, &shared->barrier_attr, NUM_PROCESSES);
printf("创建 %d 个进程在屏障处同步\n", NUM_PROCESSES);
// 创建子进程
for (int i = 0; i < NUM_PROCESSES - 1; i++) {
if (fork() == 0) {
// 子进程工作
printf("进程 %d 开始第一阶段工作\n", getpid());
sleep(i + 1); // 不同的工作负载
printf("进程 %d 到达屏障点,等待其他进程...\n", getpid());
pthread_barrier_wait(&shared->barrier);
printf("进程 %d 通过屏障,开始第二阶段工作\n", getpid());
sleep(1);
printf("进程 %d 完成\n", getpid());
exit(0);
}
}
// 父进程也参与
printf("父进程 %d 开始第一阶段工作\n", getpid());
sleep(2);
printf("父进程 %d 到达屏障点,等待其他进程...\n", getpid());
pthread_barrier_wait(&shared->barrier);
printf("父进程 %d 通过屏障,开始第二阶段工作\n", getpid());
sleep(1);
printf("父进程 %d 完成\n", getpid());
// 等待所有子进程
for (int i = 0; i < NUM_PROCESSES - 1; i++) {
wait(NULL);
}
// 清理
pthread_barrier_destroy(&shared->barrier);
pthread_barrierattr_destroy(&shared->barrier_attr);
munmap(shared, sizeof(shared_barrier_t));
}
6. 读写锁在共享内存中
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>
typedef struct {
pthread_rwlock_t rwlock;
pthread_rwlockattr_t rwlock_attr;
int shared_data;
int reader_count;
} shared_rwlock_t;
void reader_writer_example() {
shared_rwlock_t *shared = mmap(NULL, sizeof(shared_rwlock_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// 初始化读写锁属性
pthread_rwlockattr_init(&shared->rwlock_attr);
pthread_rwlockattr_setpshared(&shared->rwlock_attr, PTHREAD_PROCESS_SHARED);
// 初始化读写锁
pthread_rwlock_init(&shared->rwlock, &shared->rwlock_attr);
shared->shared_data = 0;
shared->reader_count = 0;
// 创建读者进程
for (int i = 0; i < 3; i++) {
if (fork() == 0) {
// 读者进程
for (int j = 0; j < 3; j++) {
pthread_rwlock_rdlock(&shared->rwlock);
shared->reader_count++;
printf("读者 %d: 读取数据 %d (当前读者数: %d)\n",
getpid(), shared->shared_data, shared->reader_count);
sleep(1);
shared->reader_count--;
pthread_rwlock_unlock(&shared->rwlock);
sleep(2);
}
exit(0);
}
}
// 创建写者进程
for (int i = 0; i < 2; i++) {
if (fork() == 0) {
// 写者进程
for (int j = 0; j < 2; j++) {
pthread_rwlock_wrlock(&shared->rwlock);
printf("写者 %d: 开始写入...\n", getpid());
shared->shared_data += 10;
sleep(2);
printf("写者 %d: 写入完成,新值: %d\n", getpid(), shared->shared_data);
pthread_rwlock_unlock(&shared->rwlock);
sleep(3);
}
exit(0);
}
}
// 等待所有子进程
for (int i = 0; i < 5; i++) {
wait(NULL);
}
printf("最终数据值: %d\n", shared->shared_data);
// 清理
pthread_rwlock_destroy(&shared->rwlock);
pthread_rwlockattr_destroy(&shared->rwlock_attr);
munmap(shared, sizeof(shared_rwlock_t));
}
进程同步方法比较总结
| 同步方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 信号量 | 互斥访问、资源计数、生产者消费者 | 灵活,支持计数 | 需要手动管理 |
| 文件锁 | 简单互斥、文件访问控制 | 简单易用,不需要特殊IPC | 性能较低,基于文件系统 |
| 共享内存+互斥锁 | 高性能共享数据访问 | 性能高,类似线程同步 | 复杂,需要共享内存 |
| 条件变量 | 复杂的等待/通知场景 | 支持复杂的同步模式 | 必须与互斥锁配合使用 |
| 屏障 | 并行计算阶段同步 | 简单实现阶段同步 | 需要预先知道进程数量 |
| 读写锁 | 读多写少的场景 | 提高读并发性能 | 实现相对复杂 |
同步方法的选择指南
- 简单互斥:文件锁或二进制信号量
- 资源池管理:计数信号量
- 高性能数据共享:共享内存 + 互斥锁/读写锁
- 复杂协调:条件变量
- 并行计算:屏障
- 读多写少:读写锁
实践
- 避免死锁:按照固定顺序获取锁
- 减少锁粒度:只锁定必要的数据
- 超时机制:为锁操作设置超时
- 错误处理:检查所有同步原语的返回值
- 资源清理:进程退出前释放所有同步资源
正确的同步机制选择和使用对于构建稳定、高效的多进程应用至关重要。
现代进程使用模式
1. 进程池模式
// 预创建进程,避免频繁创建销毁
typedef struct {
pid_t* workers;
int worker_count;
task_queue_t queue;
} process_pool_t;
void process_pool_init(process_pool_t* pool, int count) {
pool->workers = malloc(count * sizeof(pid_t));
pool->worker_count = count;
for (int i = 0; i < count; i++) {
pid_t pid = fork();
if (pid == 0) {
// 工作进程
worker_loop(pool);
_exit(0);
} else {
pool->workers[i] = pid;
}
}
}
2. 监督树模式
// 父进程监督子进程,崩溃时重启
void supervisor() {
while (1) {
pid_t pid = fork();
if (pid == 0) {
// 子进程执行实际工作
worker_main();
_exit(0);
} else {
// 父进程等待并处理崩溃
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == RESTART_CODE) {
printf("子进程正常退出,准备重启...\n");
sleep(1); // 避免频繁重启
} else {
printf("子进程异常退出,重启中...\n");
sleep(5); // 异常时等待更久
}
}
}
}
多进程性能优化
多进程编程的性能优化旨在减少进程开销、提高资源利用率、优化通信效率,从而提升整体系统性能。以下是从各个角度进行的详细优化策略。
1. 进程创建与销毁优化
进程池技术
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#define WORKER_COUNT 4
#define TASK_QUEUE_SIZE 100
typedef struct {
int task_id;
int data;
int result;
} task_t;
typedef struct {
task_t tasks[TASK_QUEUE_SIZE];
int head;
int tail;
int count;
pthread_mutex_t mutex;
pthread_cond_t not_empty;
pthread_cond_t not_full;
} task_queue_t;
typedef struct {
pid_t workers[WORKER_COUNT];
task_queue_t *queue;
int running;
} process_pool_t;
// 初始化进程池
void process_pool_init(process_pool_t *pool) {
// 创建共享任务队列
pool->queue = mmap(NULL, sizeof(task_queue_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// 初始化队列
pool->queue->head = 0;
pool->queue->tail = 0;
pool->queue->count = 0;
// 初始化进程共享的锁和条件变量
pthread_mutexattr_t mutex_attr;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&pool->queue->mutex, &mutex_attr);
pthread_condattr_t cond_attr;
pthread_condattr_init(&cond_attr);
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&pool->queue->not_empty, &cond_attr);
pthread_cond_init(&pool->queue->not_full, &cond_attr);
pool->running = 1;
// 创建工作进程
for (int i = 0; i < WORKER_COUNT; i++) {
pid_t pid = fork();
if (pid == 0) {
worker_process(pool->queue);
exit(0);
} else {
pool->workers[i] = pid;
}
}
}
// 工作进程主循环
void worker_process(task_queue_t *queue) {
while (1) {
pthread_mutex_lock(&queue->mutex);
// 等待任务
while (queue->count == 0) {
pthread_cond_wait(&queue->not_empty, &queue->mutex);
}
// 获取任务
task_t *task = &queue->tasks[queue->head];
queue->head = (queue->head + 1) % TASK_QUEUE_SIZE;
queue->count--;
pthread_cond_signal(&queue->not_full);
pthread_mutex_unlock(&queue->mutex);
// 处理任务
task->result = process_task(task->data);
printf("Worker %d: Task %d completed, result=%d\n",
getpid(), task->task_id, task->result);
}
}
// 提交任务
void submit_task(process_pool_t *pool, task_t *task) {
pthread_mutex_lock(&pool->queue->mutex);
// 等待队列有空位
while (pool->queue->count == TASK_QUEUE_SIZE) {
pthread_cond_wait(&pool->queue->not_full, &pool->queue->mutex);
}
// 添加任务
pool->queue->tasks[pool->queue->tail] = *task;
pool->queue->tail = (pool->queue->tail + 1) % TASK_QUEUE_SIZE;
pool->queue->count++;
pthread_cond_signal(&pool->queue->not_empty);
pthread_mutex_unlock(&pool->queue->mutex);
}
预创建进程
// 预创建进程,避免运行时fork开销
void precreate_processes() {
pid_t workers[10];
// 预创建进程
for (int i = 0; i < 10; i++) {
workers[i] = fork();
if (workers[i] == 0) {
// 子进程进入等待状态
pause(); // 等待信号唤醒
exit(0);
}
}
// 当需要时唤醒进程,而不是重新创建
// kill(workers[0], SIGCONT);
}
2. 进程间通信优化
共享内存优化
#include <sys/shm.h>
#include <sys/mman.h>
// 大页面共享内存(如果系统支持)
void huge_page_shared_memory() {
size_t size = 2 * 1024 * 1024; // 2MB
// 尝试使用大页面
void *shm = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
if (shm == MAP_FAILED) {
// 回退到普通共享内存
shm = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}
if (shm != MAP_FAILED) {
printf("共享内存分配成功: %p\n", shm);
}
}
// 内存对齐优化
typedef struct __attribute__((aligned(64))) {
int data1;
int data2;
char padding[56]; // 填充到缓存行大小
} cache_aligned_data_t;
void cache_optimized_shared_memory() {
// 确保每个进程访问的数据在不同的缓存行
cache_aligned_data_t *shared_data = mmap(NULL, sizeof(cache_aligned_data_t) * WORKER_COUNT,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// 每个进程访问自己缓存行对齐的数据
for (int i = 0; i < WORKER_COUNT; i++) {
if (fork() == 0) {
cache_aligned_data_t *my_data = &shared_data[i];
// 处理数据,避免缓存行竞争
my_data->data1 = i * 100;
exit(0);
}
}
}
批量数据传输
// 批量处理减少IPC调用次数
typedef struct {
int items[1000];
int count;
} batch_data_t;
void batch_ipc_example() {
int shmid = shmget(IPC_PRIVATE, sizeof(batch_data_t), IPC_CREAT | 0666);
batch_data_t *batch = shmat(shmid, NULL, 0);
if (fork() == 0) {
// 生产者:批量生产数据
for (int i = 0; i < 10; i++) {
// 积累一批数据
batch->count = 100;
for (int j = 0; j < 100; j++) {
batch->items[j] = i * 100 + j;
}
// 通知消费者(一次IPC调用传输100个数据项)
// 使用信号量或条件变量通知
sleep(1); // 模拟生产时间
}
exit(0);
} else {
// 消费者:批量消费数据
for (int i = 0; i < 10; i++) {
// 等待批量数据就绪
while (batch->count == 0) {
usleep(1000);
}
// 处理整批数据
printf("收到 %d 个数据项\n", batch->count);
batch->count = 0; // 重置计数
}
wait(NULL);
}
}
3. CPU和内存优化
CPU亲和性设置
#define _GNU_SOURCE
#include <sched.h>
void set_cpu_affinity(pid_t pid, int cpu_id) {
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu_id, &set);
if (sched_setaffinity(pid, sizeof(cpu_set_t), &set) == -1) {
perror("sched_setaffinity");
}
}
void optimized_cpu_affinity() {
int num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
for (int i = 0; i < WORKER_COUNT; i++) {
pid_t pid = fork();
if (pid == 0) {
// 子进程绑定到特定CPU
set_cpu_affinity(0, i % num_cpus); // 循环分配CPU
// 工作代码
while (1) {
// 计算密集型任务
}
exit(0);
}
}
}
// NUMA感知的内存分配
void numa_aware_allocation() {
// 在支持NUMA的系统上,将进程和其使用的内存绑定到同一NUMA节点
// 这通常需要numactl库或系统调用
}
内存布局优化
// 避免虚假共享
typedef struct {
int counter __attribute__((aligned(64))); // 缓存行对齐
char padding[60]; // 填充到64字节
} aligned_counter_t;
void avoid_false_sharing() {
aligned_counter_t *counters = mmap(NULL, sizeof(aligned_counter_t) * WORKER_COUNT,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
// 每个进程更新自己的计数器,不会互相干扰缓存行
for (int i = 0; i < WORKER_COUNT; i++) {
if (fork() == 0) {
for (int j = 0; j < 1000000; j++) {
counters[i].counter++; // 独立的缓存行
}
exit(0);
}
}
}
4. I/O优化
异步I/O和I/O多路复用
#include <aio.h>
#include <fcntl.h>
void async_io_example() {
int fd = open("large_file.dat", O_RDONLY);
char buffer[4096];
struct aiocb aio_request = {0};
aio_request.aio_fildes = fd;
aio_request.aio_buf = buffer;
aio_request.aio_nbytes = sizeof(buffer);
aio_request.aio_offset = 0;
// 发起异步读取
if (aio_read(&aio_request) == -1) {
perror("aio_read");
return;
}
// 进程可以继续做其他工作
printf("异步I/O进行中,继续处理其他任务...\n");
// 等待I/O完成
while (aio_error(&aio_request) == EINPROGRESS) {
usleep(1000); // 做其他工作
}
// I/O完成
ssize_t bytes_read = aio_return(&aio_request);
printf("读取了 %zd 字节数据\n", bytes_read);
close(fd);
}
// 使用epoll管理多个I/O
void epoll_multiplexing() {
int epoll_fd = epoll_create1(0);
struct epoll_event event;
// 添加多个文件描述符到epoll
// ... 设置代码
while (1) {
struct epoll_event events[10];
int n = epoll_wait(epoll_fd, events, 10, -1);
for (int i = 0; i < n; i++) {
if (events[i].events & EPOLLIN) {
// 处理可读事件
handle_readable(events[i].data.fd);
}
}
}
}
5. 负载均衡优化
工作窃取(Work Stealing)
typedef struct {
task_t *local_queue;
int local_head;
int local_tail;
task_t *global_queues;
int num_workers;
} work_stealing_pool_t;
void work_stealing_worker(work_stealing_pool_t *pool, int worker_id) {
while (1) {
// 首先从自己的本地队列获取任务
task_t *task = get_local_task(pool, worker_id);
if (task == NULL) {
// 本地队列为空,尝试从其他工作者窃取任务
for (int i = 0; i < pool->num_workers; i++) {
if (i != worker_id) {
task = steal_task(pool, i);
if (task != NULL) break;
}
}
}
if (task != NULL) {
process_task(task);
} else {
// 没有任务,休眠一会儿
usleep(1000);
}
}
}
动态负载均衡
typedef struct {
int *worker_loads;
int num_workers;
pthread_mutex_t load_mutex;
} load_balancer_t;
int get_least_loaded_worker(load_balancer_t *balancer) {
int min_load = INT_MAX;
int best_worker = 0;
pthread_mutex_lock(&balancer->load_mutex);
for (int i = 0; i < balancer->num_workers; i++) {
if (balancer->worker_loads[i] < min_load) {
min_load = balancer->worker_loads[i];
best_worker = i;
}
}
balancer->worker_loads[best_worker]++;
pthread_mutex_unlock(&balancer->load_mutex);
return best_worker;
}
void update_worker_load(load_balancer_t *balancer, int worker_id, int load_change) {
pthread_mutex_lock(&balancer->load_mutex);
balancer->worker_loads[worker_id] += load_change;
pthread_mutex_unlock(&balancer->load_mutex);
}
6. 同步优化
无锁数据结构
#include <stdatomic.h>
// 无锁队列
typedef struct {
_Atomic int head;
_Atomic int tail;
task_t tasks[TASK_QUEUE_SIZE];
} lockfree_queue_t;
int lockfree_enqueue(lockfree_queue_t *queue, task_t task) {
int current_tail = atomic_load(&queue->tail);
int next_tail = (current_tail + 1) % TASK_QUEUE_SIZE;
if (next_tail == atomic_load(&queue->head)) {
return -1; // 队列满
}
queue->tasks[current_tail] = task;
atomic_store(&queue->tail, next_tail);
return 0;
}
int lockfree_dequeue(lockfree_queue_t *queue, task_t *task) {
int current_head = atomic_load(&queue->head);
if (current_head == atomic_load(&queue->tail)) {
return -1; // 队列空
}
*task = queue->tasks[current_head];
atomic_store(&queue->head, (current_head + 1) % TASK_QUEUE_SIZE);
return 0;
}
读写锁优化
// 读多写少的场景使用读写锁
void read_heavy_workload() {
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
// 多个读者可以并发
if (fork() == 0) {
pthread_rwlock_rdlock(&rwlock);
// 读取数据
pthread_rwlock_unlock(&rwlock);
exit(0);
}
// 写者需要独占访问
if (fork() == 0) {
pthread_rwlock_wrlock(&rwlock);
// 修改数据
pthread_rwlock_unlock(&rwlock);
exit(0);
}
}
7. 监控和调优
性能监控
#include <sys/resource.h>
void monitor_performance() {
struct rusage usage;
while (1) {
getrusage(RUSAGE_SELF, &usage);
printf("用户CPU时间: %ld.%06ld秒\n",
usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
printf("系统CPU时间: %ld.%06ld秒\n",
usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
printf("页面错误: %ld\n", usage.ru_majflt);
printf("自愿上下文切换: %ld\n", usage.ru_nvcsw);
printf("非自愿上下文切换: %ld\n", usage.ru_nivcsw);
sleep(5); // 每5秒监控一次
}
}
// 使用perf_event进行详细性能分析
void perf_monitoring() {
// 需要Linux perf_event支持
// 可以监控缓存命中率、分支预测失败、指令周期等硬件计数器
}
8. 编译优化
编译选项优化
# 使用优化的编译选项
gcc -O3 -march=native -mtune=native -flto -pthread program.c -o program
# 针对特定微架构优化
gcc -O3 -march=skylake -mtune=skylake program.c -o program
链接时优化(LTO)
// 在多个源文件中使用LTO
// 编译时:gcc -O3 -flto -c file1.c
// gcc -O3 -flto -c file2.c
// 链接时:gcc -O3 -flto file1.o file2.o -o program
性能优化总结
| 优化领域 | 具体技术 | 预期收益 | 复杂度 |
|---|---|---|---|
| 进程管理 | 进程池、预创建 | 减少90%创建开销 | 中等 |
| 通信优化 | 批量传输、共享内存 | 减少50-80%通信开销 | 高 |
| CPU优化 | CPU亲和性、NUMA感知 | 提高20-40%性能 | 中等 |
| 内存优化 | 缓存对齐、大页面 | 减少缓存失效,提高内存带宽 | 中等 |
| I/O优化 | 异步I/O、I/O多路复用 | 提高I/O密集型应用性能 | 高 |
| 负载均衡 | 工作窃取、动态调度 | 提高资源利用率10-30% | 高 |
| 同步优化 | 无锁数据结构、读写锁 | 减少锁竞争,提高并发性 | 高 |
| 监控调优 | 性能分析、硬件计数器 | 识别瓶颈,针对性优化 | 中等 |
实践建议
- 先测量后优化:使用性能分析工具找到真正的瓶颈
- 渐进式优化:一次优化一个方面,验证效果
- 考虑可维护性:不要为了性能牺牲代码可读性和可维护性
- 测试不同负载:确保优化在各种工作负载下都有效
- 文档化优化:记录为什么选择某种优化策略


被折叠的 条评论
为什么被折叠?



