一、进程间通信 5 种方式
- 管道(无名管道、有名管道 FIFO)
- 信号
- 消息队列
- 共享内存
- 信号灯(信号量)
进程之间互相隔离,各自有独立地址空间,不能直接访问对方内存,必须依靠内核提供的机制完成数据交互。
1. 管道
1.1 无名管道 pipe
- 限制:只支持有亲缘关系的进程通信(父子、兄弟进程)
- 函数原型
c
int pipe(int pipefd[2]);
- 参数
pipefd[0]:读端文件描述符pipefd[1]:写端文件描述符
- 返回值:成功返回 0,失败返回 -1
无名管道核心特性(考试重点)
- 管道有写端
- 管道里面有数据,直接读取数据
- 管道无数据,读操作阻塞等待,直到有数据写入
- 管道没有写端(写端全部关闭)
- 管道有数据直接读;没有数据,read 直接返回 0,不阻塞
- 管道有读端
- 管道没写满(默认 64K 缓冲区),直接写入
- 管道写满,write 阻塞,等待读端读走数据之后才能继续写
- 管道没有读端(所有读端关闭)
- 执行 write 写数据,触发管道破裂 SIGPIPE 信号,进程直接退出。
无名管道是半双工:一端读、一端写,单向数据流。
1.2 有名管道 FIFO mkfifo
- 突破无名管道限制:任意两个进程之间都可以通信,不需要亲缘关系
- 特性:读写两端必须都打开,程序才会往下执行;只打开一端,程序阻塞等待另一端打开。
函数接口
c
int mkfifo(const char *pathname, mode_t mode);
- pathname:管道文件路径名
- mode:文件权限
- 返回值:成功 0,失败 -1
练习:写 read.c 和 write.c,实现两个程序聊天。
write.c 接收终端输入发给 read.c;read.c 接收打印,再把内容回传给 write.c。
2. 信号
信号是内核层给应用层进程发送通知的机制,属于异步通信。进程不知道信号什么时候到来。
进程收到信号三种处理方式
- 缺省 SIG_DFL:系统默认行为(大部分信号默认杀死进程)
- 忽略 SIG_IGN:收到信号直接无视,什么都不干
- 捕捉:注册自定义处理函数,信号来了执行自己写的回调函数
常用信号记忆表格
| 信号 | 触发来源 | 作用 |
|---|---|---|
| SIGINT | Ctrl + c |
中断进程 |
| SIGQUIT | Ctrl + \ |
退出进程 |
| SIGKILL(9) | kill -9 | 强制杀死,不能忽略、不能捕捉 |
| SIGSEGV | 程序内部 | 段错误 |
| SIGPIPE | 管道写操作 | 管道破裂 |
| SIGALRM | alarm() | 定时时间到达 |
| SIGCHLD | 子进程 | 子进程结束通知父进程 |
| SIGCONT | 唤醒继续运行暂停进程 | |
| SIGSTOP(19) | 暂停进程,不能忽略、不能捕捉 | |
| SIGTSTP | Ctrl + z |
挂起暂停 |
| SIGIO | 异步 IO 通知 |
重点:9 号 SIGKILL、19 号 SIGSTOP,不能忽略、不能捕捉,无法被程序拦截。
信号函数接口
signal() 注册信号处理函数
c
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- signum:信号编号
- handler:
SIG_IGN:忽略信号SIG_DFL:恢复系统默认处理- 自定义函数地址:捕捉信号,信号到来调用该函数
- 返回值:成功返回上一次注册的处理函数地址;失败返回
SIG_ERR
pause()
c
int pause(void);
功能:让进程休眠阻塞,直到收到信号才唤醒。
alarm() 闹钟
c
unsigned int alarm(unsigned int seconds);
seconds 秒之后,内核给当前进程发送 SIGALRM 闹钟信号。
📝 学习复盘 & 踩坑总结
- 无名管道只能亲缘进程;有名管道 FIFO 任意进程都能用。
- 管道破裂:写管道,但是所有读端全部关闭 → SIGPIPE 信号,进程直接死掉。写代码必须注意。
- 信号是异步!进程不知道信号什么时候来。9、19 号信号不受程序控制。
- pipe 创建出来两个 fd,fork 之后子进程会复制文件描述符,父子各持有读写两端,记得不用的一端 close 关闭。
- pause 会一直睡,只有信号到来才会解除阻塞。