Linux IPC 进程间通信:无名管道与有名管道详解
·
1. 为什么需要 IPC
进程是程序执行的过程,操作系统会为每个进程分配独立的内存资源与 CPU 资源。Linux 通过虚拟内存和 MMU(内存管理单元)实现了进程之间的隔离:A 进程无法直接访问 B 进程的内存空间,这保证了安全性和权限管理。
但实际开发中,进程与进程之间往往存在数据共享或交换的需求。于是就有了 IPC(Interprocess Communication,进程间通信)。
IPC 的常见种类大致可以分为三类:
- 古老的通信方式:无名管道、有名管道、信号
- System V IPC 对象:消息队列、共享内存、信号量集
- socket 通信:用于网络通信
本文只聚焦其中最基础、也最常用的两种:无名管道和有名管道。
2. 管道的本质
管道的底层实现是队列。它有几个共同特性,理解这些特性对后面写代码非常关键:
- 管道是半双工的工作模式,实际编程中通常当作单工使用(一端只读、一端只写)。
- 所有管道都是特殊文件,不支持定位操作(
lseek对管道无效)。 - 管道读写首选文件 IO(
open、read、write、close),也可以使用带缓冲区的标准 IO(fgets、fread、fgetc)。
3. 无名管道(unnamed pipe)
无名管道只能用于有亲缘关系的进程之间通信(典型场景是父子进程),因为它没有文件名,无法被无关进程通过路径打开。
3.1 创建无名管道
#include <unistd.h>
int pipe(int pipefd[2]);
- 功能:创建并打开一个无名管道
- 参数:
pipefd[0]是固定的读端,pipefd[1]是固定的写端 - 返回值:成功返回 0,失败返回 -1
3.2 编程顺序
创建管道 → fork 出子进程 → 父子进程分别读写管道 → 关闭管道。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
int fd[2];
if (pipe(fd) == -1) {
perror("pipe");
return -1;
}
pid_t pid = fork();
if (pid == 0) {
/* 子进程:关闭读端,只写 */
close(fd[0]);
const char *msg = "hello from child";
write(fd[1], msg, strlen(msg));
close(fd[1]);
} else if (pid > 0) {
/* 父进程:关闭写端,只读 */
close(fd[1]);
char buf[128] = {0};
ssize_t n = read(fd[0], buf, sizeof(buf) - 1);
if (n > 0) {
printf("parent recv: %s\n", buf);
}
close(fd[0]);
}
return 0;
}
3.3 管道的四种典型行为
- 读端存在,一直写:写入超过 64K 后,写操作会阻塞(写得太快)。
- 写端存在,读空管道:管道为空时读操作会阻塞(读得太快)。
- 管道破裂:读端已关闭,此时再写管道,会导致写端进程退出(收到 SIGPIPE)。
- 写端关闭:
read返回 0,表示进程间通信结束。
4. 有名管道(FIFO)
有名管道在文件系统中可见,有文件名,因此可以用于任意单机进程之间的通信,不要求有亲缘关系。
4.1 创建有名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- 功能:在指定路径下创建一个权限为
mode的有名管道文件 - 参数:
pathname为路径 + 名称,mode为八进制文件权限 - 返回值:成功返回 0,失败返回 -1
4.2 编程步骤
创建有名管道 → 打开有名管道 → 读写管道 → 关闭管道 → 卸载(删除)有名管道。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
int main(void)
{
const char *fifo = "/tmp/myfifo";
/* 创建有名管道,若已存在则忽略错误 */
if (mkfifo(fifo, 0666) == -1) {
perror("mkfifo");
}
pid_t pid = fork();
if (pid == 0) {
/* 子进程写 */
int fd = open(fifo, O_WRONLY);
const char *msg = "hello fifo";
write(fd, msg, strlen(msg));
close(fd);
} else if (pid > 0) {
/* 父进程读 */
int fd = open(fifo, O_RDONLY);
char buf[128] = {0};
read(fd, buf, sizeof(buf) - 1);
printf("parent recv: %s\n", buf);
close(fd);
}
unlink(fifo);
return 0;
}
4.3 与无名管道的异同
有名管道的读写特性与无名管道完全一致,唯一的额外特性是:如果有一端没有打开,open 函数会阻塞,直到另一端也被打开为止。
5. 无名管道与有名管道对比
| 对比项 | 无名管道 | 有名管道 |
|---|---|---|
| 创建函数 | pipe() | mkfifo() |
| 文件系统中是否可见 | 否 | 是 |
| 适用进程 | 有亲缘关系的进程 | 任意单机进程 |
| 打开方式 | 创建即打开 | 需要 open |
| 半双工 | 是 | 是 |
| 支持定位 | 否 | 否 |
| 一端未打开时 | 不涉及 | open 阻塞 |
6. 小结
- 管道底层是队列,半双工、不支持定位,读写优先使用文件 IO。
- 无名管道用
pipe()创建,只能用于有亲缘关系的进程,通常配合fork使用。 - 有名管道用
mkfifo()创建,在文件系统中可见,可用于任意单机进程,但一端未打开时open会阻塞。 - 使用管道时要注意读写阻塞、管道破裂(SIGPIPE)以及写端关闭后
read返回 0 这几种边界情况。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)