马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
进程之间是相互独立的,这既是 Linux 稳定性高的重要原因,也带来了一个现实问题:进程之间怎么交换数据?这就是进程间通信(IPC)要解决的问题。
一、认识进程间通信
进程间通信指两个或多个进程之间在数据层面发生交互。因为进程具备独立性,各自拥有独立的虚拟地址空间,一个进程拿不到另一个进程的内存,所以通信成本天然偏高——必须付出额外的代价。
二、进程间通信的目标
- 本质:让不同的进程看到同一份“资源”,也就是同一块内存空间。
- 来源:这份资源通常由操作系统提供,不属于某一个进程独有,因此不会破坏进程的独立性。
- 路径:进程代表用户,资源从创建、使用到释放,全过程都是调用系统调用接口完成的。
三、进程间通信的发展与分类
一般操作系统都会有一个独立的通信模块,隶属于文件系统,IPC 通信模块会制定标准,所以进程间通信是有规范可循的。
1. 管道(文件级通信)
2. System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
3. POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
四、管道:Unix 里最古老的 IPC 形式
把一个进程的输出连接到另一个进程输入的那股数据流,就叫“管道”。
1. 匿名管道 pipe
- 匿名管道本质上也是一个文件,只是缓冲区的数据不会刷新到磁盘上。
- 刻意设计成读写两端,是为了保证管道只能单向通信。
#include <unistd.h>
// 功能:创建一无名管道
// 原型:int pipe(int fd[2])
// fd:文件描述符数组,fd[0] 表示读端,fd[1] 表示写端
// 返回值:成功返回 0,失败返回错误码
int pipe(int fd[2]);
匿名管道适用的一前提是:参与通信的两个进程必须有血缘关系,比如父子、兄弟、爷孙,最常见的是父子进程。
2. 用 fork 共享管道的原理
子进程的 PCB 是从父进程拷贝来的。如果父进程一开始用 pipe() 创建了匿名管道并且只打开了读端,那么子进程本该只能拿到写端(这样才能保证单向通信);但由于继承自父进程的写端,很容易导致单向失效。所以fork之后记得关掉多余的读写端。
int fd[2];
pipe(fd);
pid_t pid = fork();
if (pid < 0) {
perror("fork");
} else if (pid == 0) {
close(fd[0]); // 子进程只写
write(fd[1], buf, len);
close(fd[1]);
} else {
close(fd[1]); // 父进程只读
read(fd[0], buf, sizeof(buf));
close(fd[0]);
}
3. 管道读写规则
- 读写端都正常时,管道为空,读端会阻塞。
- 读写端都正常时,管道被写满,写端会阻塞。
- 读端正常读、写端关闭,读端会读到 0,表示读到了管道末尾,不会被阻塞。
- 读端关闭、写端正常,内核会杀掉正在写入的进程(通过信号杀死)。
注意:管道大小是固定的,不同内核版本里大小会有所差异。
4. 管道的特点
- 只能用于有共同祖先的进程之间通信:通常由一个进程创建管道,再调用 fork,之后父子进程就能用这条管道通信。
- 管道提供流式服务,不保证消息边界。
- 进程退出,管道随之释放,所以管道的生命周期随进程。
- 内核会对管道操作做同步与互斥。
- 管道是半双工的,数据只能单向流动;双方都要通信时必须建立两条管道。
五、小结
匿名管道是学习 IPC 的第一站,它的核心价值是“借文件之名,做内存之实”。搞清楚 fd 的读写端、fork 之后的端口关闭、阻塞读写规则这三件事,后面学命名管道、共享内存、消息队列时会顺畅很多。
qidao123.com企服评测-应用市场 编辑提示 |