进程与线程通信机制
一、进程间通信(IPC)方式
1.1 管道(Pipe)
1.1.1 无名管道(匿名管道)
-
创建方式:通过pipe()系统调用创建
-
特点:
-
只能在有亲缘关系的进程间使用(父子进程、兄弟进程)
-
单向通信,一个管道只能单向传输数据
-
数据存放在内核缓冲区中
-
生命周期随进程结束而结束
-
// 创建无名管道
int pipe_fd[2];
pipe(pipe_fd);
// pipe_fd[0]:读端
// pipe_fd[1]:写端
1.1.2 有名管道(FIFO)
-
创建方式:
bash
# 命令行创建
mkfifo pipe_name# 或使用系统调用
mkfifo("pipe_name", 0666); -
特点:
-
可以在任意进程间使用
-
在文件系统中有一个路径名(如 /tmp/myfifo)
-
必须两个端口都打开才能正常通信
-
容量限制:64KB
-
1.1.3 管道阻塞情况
| 写阻塞 | 管道为空时写入 | 等待有数据可读 |
| 读阻塞 | 读端已打开,但管道为空 | 等待数据写入 |
| 写满阻塞 | 管道已满(64KB) | 等待有空间可写 |
| 管道破裂 | 读端关闭,写端继续写入 | 收到SIGPIPE信号 |
1.2 共享内存(Shared Memory)
-
速度:比管道快,因为是直接内存访问
-
特点:
-
多个进程共享同一块物理内存区域
-
只能读取最新一次的数据(覆盖写入)
-
需要加入同步机制防止数据竞争
-
内存较大,适合大数据传输
-
-
问题:需要手动同步,否则会出现数据不一致
1.3 信号(Signal)
-
定义:软件中断,用于通知进程发生了某个事件
-
常见信号:
-
SIGINT (2):Ctrl+C 中断信号
-
SIGKILL (9):强制终止信号(不可捕获)
-
SIGTERM (15):正常终止信号
-
SIGSEGV (11):段错误信号
-
-
特点:异步通信,不能传递复杂数据
1.4 信号量(Semaphore) – 进程间
-
作用:进程间的同步机制,控制对共享资源的访问
-
类型:
-
二值信号量(0或1)
-
计数信号量(0-N)
-
-
操作:
-
P操作(wait):申请资源,信号量减1
-
V操作(signal):释放资源,信号量加1
-
1.5 消息队列(Message Queue)
-
函数:msgsnd() 发送消息,msgrcv() 接收消息
-
特点:
-
消息链表,存储在内核中
-
每个消息有类型标识
-
支持不同优先级的消息
-
消息按类型或先进先出方式读取
-
1.6 套接字(Socket)
-
特点:
-
最通用的IPC机制
-
支持不同主机间的进程通信
-
支持多种协议(TCP/UDP)
-
可用于网络编程和本地进程通信
-
二、线程间通信
2.1 全局变量
-
最简单的线程通信方式
-
所有线程共享进程的全局数据区
-
需要同步机制保护,避免数据竞争
int global_var = 0; // 所有线程可见
void* thread_func(void* arg) {
global_var++; // 需要同步保护
return NULL;
}
2.2 互斥(Mutex)
-
作用:保证同一时间只有一个线程访问共享资源
-
相关函数:
pthread_mutex_lock(&mutex); // 加锁
pthread_mutex_unlock(&mutex); // 解锁
pthread_mutex_trylock(&mutex); // 尝试加锁(非阻塞) -
特点:
-
保护临界区代码
-
防止多个线程同时修改共享数据
-
避免数据不一致
-
2.3 同步(信号量)
-
相关函数:
sem_wait(&sem); // P操作,等待信号量
sem_post(&sem); // V操作,释放信号量 -
用途:
-
控制线程执行顺序
-
限制同时访问资源的线程数量
-
实现生产者-消费者模型
-
2.4 读写锁(Read-Write Lock)
-
特点:
-
允许多个线程同时读
-
只允许一个线程写
-
写操作具有排他性
-
-
适用场景:读多写少的共享数据
2.5 条件变量(Condition Variable)
-
作用:线程间条件等待和通知
-
相关函数:
pthread_cond_wait(&cond, &mutex); // 等待条件
pthread_cond_signal(&cond); // 通知一个等待线程
pthread_cond_broadcast(&cond); // 通知所有等待线程 -
通常与互斥锁配合使用
三、进程创建与内存管理
3.1 进程创建过程
3.1.1 内存分配
当创建新进程时,内核会分配:
task_struct 结构体
-
进程控制块(PCB)
-
包含进程的所有信息
-
如:PID、状态、优先级、打开文件等
mm_struct 结构体
-
内存描述符
-
管理进程的虚拟地址空间
-
包含页表、内存区域等信息
3.1.2 进程创建函数
-
fork():
-
创建子进程
-
子进程复制父进程的完整地址空间
-
开销较大
-
-
vfork():
-
创建子进程,但共享父进程的地址空间
-
子进程先运行,父进程阻塞
-
子进程退出或exec后父进程继续
-
-
clone():
-
共享 mm_struct(内存描述符)
-
可以控制共享哪些资源
-
用于创建线程
-
3.2 CPU与内存管理
3.2.1 CPU中的MMU
-
MMU:内存管理单元(Memory Management Unit)
-
功能:
-
虚拟地址 → 物理地址 转换
-
内存保护
-
缓存控制
-
-
核心组件:页表
-
存储虚拟页到物理页的映射关系
-
每个进程有自己的页表
-
由操作系统维护
-
3.2.2 页表工作原理
虚拟地址 → [MMU] → 物理地址
↓
页表查询
3.3 进程初始化
3.3.1 内核init
-
第一个进程:init进程(PID=1)
-
由内核创建,所有其他进程的祖先
-
职责:
-
初始化系统
-
启动系统服务
-
管理孤儿进程
-
3.3.2 exec() 和用户init
// 典型启动流程
fork() → exec("/sbin/init") → 用户空间初始化
-
exec():替换当前进程的映像
-
用户init:
-
/sbin/init 或 /etc/init
-
读取配置文件(如 /etc/inittab)
-
启动登录进程或图形界面
-
四、通信机制对比总结
4.1 进程间通信对比
| 管道 | 慢 | 小(64KB) | 简单 | 有亲缘关系进程 |
| 共享内存 | 最快 | 大 | 复杂(需同步) | 大数据传输 |
| 信号 | 快 | 极小 | 简单 | 简单事件通知 |
| 消息队列 | 中等 | 中等 | 中等 | 结构化消息 |
| 套接字 | 慢 | 大 | 复杂 | 网络/跨主机 |
4.2 线程同步机制对比
| 互斥锁 | 保护临界区 | 防止数据竞争 |
| 信号量 | 控制访问数量 | 资源池管理 |
| 条件变量 | 条件等待通知 | 生产者-消费者 |
| 读写锁 | 读多写少 | 共享数据访问 |
五、关键概念详解
5.1 进程创建的内存开销
创建进程:
1. 分配task_struct(约1KB)
2. 分配mm_struct(约100字节)
3. 复制页表(4KB/页)
4. 复制数据段、代码段等
-
fork():复制整个地址空间 → 开销大
-
clone():共享地址空间 → 开销小(用于创建线程)
5.2 线程与进程的资源共享
| 代码段 | 不共享 | 共享 |
| 数据段 | 不共享 | 共享 |
| 堆 | 不共享 | 共享 |
| 栈 | 独立 | 独立(每个线程有自己的栈) |
| 文件描述符 | 不共享 | 共享 |
| 信号处理 | 独立 | 共享 |
5.3 管道阻塞的深入理解
写端 → [管道缓冲区] → 读端
阻塞情况:
1. 读端关闭,写端写入 → SIGPIPE
2. 写端关闭,读端读取 → 返回0(EOF)
3. 缓冲区空,读端读取 → 阻塞
4. 缓冲区满,写端写入 → 阻塞



