1、进程间通信
1.1 进程间通信的方式(IPC,Inter-Process Communication)
| 管道(Pipe) | 最简单、最方便,基于文件描述符的字节流通信 | 简单数据传输、亲缘进程通信 |
| 信号(Signal) | 异步通知机制,仅传递 “事件”,无法传递大量数据 | 进程间事件通知(如终止、暂停) |
| 消息队列 | 基于 “消息” 的结构化通信,可按类型读取消息 | 非亲缘进程、结构化数据传输 |
| 共享内存 | 最高效(直接访问同一块内存),需配合同步机制(如信号量)避免资源竞争 | 大量数据传输、高性能场景 |
| 信号灯(信号量) | 主要用于进程 / 线程同步,而非数据传输 | 多进程资源互斥 / 同步 |
| 本地域套接字 | 通用性最强(支持跨网络、跨主机,本地通信效率接近共享内存) | 复杂通信场景、跨进程 / 主机 |
1.2 管道
管道是内核维护的一块单向缓存区,本质是 “伪文件”(无实际磁盘存储,数据在内存中),数据按 “先进先出”(FIFO)顺序传输。
1.2.1 管道特性
🔹 读端行为(阻塞模式,无 O_NONBLOCK)
| 有至少 1 个写端 + 有数据 | 读取数据,返回读取的字节数 |
| 有至少 1 个写端 + 无数据 | 阻塞等待,直到有数据写入(或所有写端关闭) |
| 无任何写端 + 有数据 | 读取数据,返回读取的字节数 |
| 无任何写端 + 无数据 | 返回 0(EOF),不阻塞,程序可继续执行 |
🔹 写端行为(阻塞模式,无 O_NONBLOCK)
| 有至少 1 个读端 + 缓存区未满 | 写入数据,返回写入的字节数(匿名管道缓存区默认 64K,FIFO 动态分配) |
| 有至少 1 个读端 + 缓存区满 | 阻塞等待,直到读端取走数据(缓存区有空位) |
| 无任何读端 |
触发 SIGPIPE 信号: ① 默认处理:进程终止(“异常结束”); ② 捕获 / 忽略信号:write () 返回 – 1,errno=EPIPE |
🔹(非阻塞模式 O_NONBLOCK)
- 读端:无数据时read()返回 – 1(errno=EAGAIN),而非阻塞;
- 写端:缓存区满时write()返回 – 1(errno=EAGAIN),而非阻塞;
- 无读端写数据:仍触发 SIGPIPE(或返回 EPIPE),和阻塞模式一致。
1.2.2 无名管道(匿名管道,pipe)
- 核心特性:
- 仅适用于有亲缘关系的进程(父子进程、兄弟进程);
- 单向通信:读端(pipefd [0])只能读,写端(pipefd [1])只能写;
- 随进程生命周期结束而销毁(无持久化);
- 半双工通信:数据只能沿一个方向传输,双向通信需创建两个管道。
- 关键规则:
- 读端关闭后,写端写入会触发SIGPIPE信号,进程默认终止;
- 写端关闭后,读端读完缓存区数据后会返回 0(表示 EOF);
- 缓存区满时写操作阻塞,缓存区空时读操作阻塞。
1.2.3 有名管道(命名管道,FIFO)
- 核心特性:
- 适用于任意进程(无需亲缘关系),通过文件系统中的 “管道文件” 标识;
- 管道文件仅作为通信标识,数据仍存储在内核缓存区,不落地磁盘;
- 持久化:管道文件会保留在文件系统中,直到手动删除。
- 关键规则:
- 打开 FIFO 时,读端会阻塞直到有写端打开,写端也会阻塞直到有读端打开;
- 可通过open函数设置非阻塞模式(O_NONBLOCK)避免阻塞。
| 通信本质 | 半双工(底层特性) | 半双工(底层特性) |
| 单向通信实现 | 单管道,一端关读、一端关写 | 单管道,一端关读、一端关写 |
| 双向通信实现方式 1 | 单管道 + 同时持有读写端(有风险) | 单管道 + 同时持有读写端(有风险) |
| 双向通信实现方式 2 | 双管道(父→子、子→父,推荐) | 双管道(两个 FIFO,推荐) |
| 双向通信的核心差异 | 仅亲缘进程可用 | 任意进程可用(通过文件名) |
1.3 管道函数接口
1.3.1 pipe(创建无名管道)
- 函数原型:
int pipe(int pipefd[2]);
- 功能:创建一个无名管道,在内核中分配缓存区,并返回两个文件描述符用于读写该缓存区。
- 参数说明:
- pipefd:长度为 2 的 int 数组,用于接收管道的读 / 写文件描述符:
- pipefd[0]:管道读端文件描述符(仅能读,写操作会报错);
- pipefd[1]:管道写端文件描述符(仅能写,读操作会报错)。
- pipefd:长度为 2 的 int 数组,用于接收管道的读 / 写文件描述符:
- 返回值:
- 成功:返回 0;
- 失败:返回 – 1,且设置errno(如 EMFILE 表示文件描述符耗尽,ENFILE 表示系统文件表满)。
- 注意事项:
- 创建管道后,通常需要配合fork()创建子进程,子进程会继承管道的文件描述符;
- 父子进程通信时,需关闭不需要的端(如父进程写、子进程读,则父进程关闭 pipefd [0],子进程关闭 pipefd [1]),避免缓存区异常。
1.3.2 mkfifo(创建有名管道)
- 函数原型:
int mkfifo(const char *pathname, mode_t mode);
- 功能:在文件系统中创建一个有名管道(FIFO)文件,作为进程间通信的标识。
- 参数说明:
- pathname:FIFO 文件的路径(如./my_fifo),需确保路径合法且无同名文件;
- mode:FIFO 文件的权限(如 0664,和普通文件权限规则一致,最终权限会受 umask 影响)。
- 返回值:
- 成功:返回 0;
- 失败:返回 – 1,且设置errno(如 EEXIST 表示文件已存在,EACCES 表示权限不足)。
- 注意事项:
- 创建 FIFO 后,需通过open()函数打开(读端用 O_RDONLY,写端用 O_WRONLY);
- FIFO 文件仅为标识,数据传输仍在内核缓存区完成,删除文件不影响正在进行的通信。
1.3.3 示例 1:无名管道(父子进程通信)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int pipefd[2];
pid_t pid;
char buf[1024];
// 1. 创建无名管道
if (pipe(pipefd) == -1) {
perror("pipe error");
return -1;
}
// 2. 创建子进程
pid = fork();
if (pid == -1) {
perror("fork error");
return -1;
}
if (pid == 0) { // 子进程:读数据
close(pipefd[1]); // 关闭写端(子进程只读)
int n = read(pipefd[0], buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\\0';
printf("子进程读到数据:%s\\n", buf);
}
close(pipefd[0]); // 关闭读端
return 0;
} else { // 父进程:写数据
close(pipefd[0]); // 关闭读端(父进程只写)
char *msg = "Hello 子进程!这是父进程的消息";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]); // 关闭写端(触发子进程读EOF)
wait(NULL); // 等待子进程结束
return 0;
}
}
1.3.4 示例 2:有名管道(任意进程通信)
进程 A(写端):
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
int main() {
// 1. 创建有名管道
if (mkfifo("./my_fifo", 0664) == -1) {
perror("mkfifo error");
return -1;
}
// 2. 打开FIFO写端
int fd = open("./my_fifo", O_WRONLY);
if (fd == -1) {
perror("open error");
return -1;
}
// 3. 写入数据
char *msg = "Hello FIFO!这是进程A的消息";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
进程 B(读端):
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
int main() {
char buf[1024];
// 1. 打开FIFO读端(若未创建,需先运行进程A)
int fd = open("./my_fifo", O_RDONLY);
if (fd == -1) {
perror("open error");
return -1;
}
// 2. 读取数据
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\\0';
printf("进程B读到数据:%s\\n", buf);
}
close(fd);
return 0;
}
编译运行:
# 终端1:编译并运行进程A(写端)
gcc fifo_write.c -o fifo_write && ./fifo_write
# 终端2:编译并运行进程B(读端)
gcc fifo_read.c -o fifo_read && ./fifo_read
输出(终端 2):进程B读到数据:Hello FIFO!这是进程A的消息
1.4 信号
用户态与内核态通过信号完成异步信息通知
多进程可通过信号完成简单的异步通信
1.4.1 信号类型:kill -l(列出所有信号)
| SIGINT | 2 | 终端输入 ctrl+c 触发,默认终止进程 |
| SIGQUIT | 3 | 终端输入 ctrl+\\ 触发,默认终止进程并生成核心转储文件(core dump) |
| SIGKILL | 9 | 强制杀死进程,不能被忽略、捕捉、阻塞(唯一无法拦截的终止信号) |
| SIGSTOP | 19 | 强制暂停进程,不能被忽略、捕捉、阻塞(唯一无法拦截的暂停信号) |
| SIGSEGV | 11 | 段错误:进程非法访问内存(如访问空指针、越界数组),默认终止进程并生成 core dump |
| SIGPIPE | 13 | 管道破裂:向无读端的管道 / 套接字写入数据,默认终止进程 |
| SIGTSTP | 20 | 终端输入 ctrl+z 触发,默认挂起进程(可通过 fg/bg 恢复) |
| SIGALRM | 14 | 定时信号:alarm() 设置的定时时间到后,内核向进程发送该信号,默认终止进程 |
| SIGCHLD | 17 | 子进程终止 / 暂停 / 恢复时,内核向父进程发送该信号,默认忽略 |
| SIGCONT | 18 | 恢复被暂停的进程(如 SIGSTOP/SIGTSTP 暂停的进程),默认让进程继续执行 |
| SIGIO | 29 | 异步 IO 信号:进程注册的 IO 事件就绪时(如套接字可读),内核向对应进程发送该信号 |
注:信号编号随架构不同略有差异
1.4.2 信号的处理方式
9号(SIGKILL)和19号(SIGSTOP)信号不能被忽略和捕捉
- 缺省(SIG_DFL):按照系统默认行为处理信号(如 SIGINT 默认终止进程)
- 忽略(SIG_IGN):收到信号后不做任何处理(如 SIGCHLD 默认忽略)
- 捕捉:自定义信号处理函数,收到信号时按照用户指定的方式处理信号
1.4.3 核心函数接口
signal 函数(设置信号处理方式,简易接口)
#include <signal.h>
// 信号处理函数类型定义
typedef void (*sighandler_t)(int);
// 核心接口
sighandler_t signal(int signum, sighandler_t handler);
等价于:
void (*signal(int signum, void (*handler)(int))) (int)
- 功能:修改指定信号(signum)的处理方式
- 参数:
- signum:要处理的信号编号(如 2/SIGINT)
- handler:处理方式:
- SIG_IGN:忽略该信号
- SIG_DFL:恢复默认处理方式
- 自定义函数指针:捕捉信号并执行自定义逻辑
- 返回值:
- 成功:返回上一次注册的信号处理函数指针
- 失败:返回 SIG_ERR(需检查 errno)
raise 函数(向自身进程发信号)
#include <signal.h>
int raise(int sig);
- 功能:给调用该函数的进程发送指定信号(等价于 kill(getpid(), sig))
- 返回值:成功返回 0,失败返回非 0
alarm 函数(设置定时信号)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
- 功能:设置定时器,seconds 秒后内核向进程发送 SIGALRM 信号
- 特性:
- 一个进程只能有一个 alarm 定时器,重复调用会覆盖之前的定时
- alarm(0) 会取消当前定时器
- 返回值:返回之前定时器剩余的秒数(无定时器则返回 0)
pause 函数(挂起进程)
#include <unistd.h>
int pause(void);
- 功能:将调用的进程挂起(阻塞),直到收到一个未被忽略的信号时2被唤醒
- 返回值:
- 被信号唤醒后,若信号被捕捉且处理函数返回,pause 返回 – 1,errno 设为 EINTR
- 若信号默认处理是终止进程,则 pause 不会返回
kill 函数(向指定进程发信号)
#include <signal.h>
#include <sys/types.h>
int kill(pid_t pid, int sig);
- 功能:向指定 PID 的进程 / 进程组发送信号
- 参数:
- pid:
- pid > 0:发送信号给 PID 为pid的进程
- pid = 0:发送信号给当前进程所在进程组的所有进程
- pid = -1:发送信号给所有有权限发送的进程(除 init 进程)
- pid < -1:发送信号给进程组 ID 为-pid的所有进程
- sig:信号编号(如 9/SIGKILL),若为 0 则仅检查进程是否存在(不发信号)
- pid:
- 返回值:成功返回 0,失败返回 – 1(errno 标识错误原因)




