在多进程编程中,不同进程之间经常需要相互通信。例如,父子进程之间交换数据、一个进程将计算结果传递给另一个进程等。为了实现这些需求,操作系统提供了多种进程间通信(IPC, Inter-Process Communication)机制,其中最基础也最常用的一种就是——管道(Pipe)通信。
一、什么是管道通信
管道(Pipe)通信是通过内核提供的一段缓冲区实现的数据通道,一个进程可以将数据写入管道,另一个进程从管道中读取数据,实现数据的传输。管道是半双工的,即数据只能沿一个方向流动,如果需要双向通信,需要建立两个管道。
二、管道的分类
1. 无名管道(Anonymous Pipe)
适用于父子进程通信,通过 pipe() 创建,不需要文件路径。
代码示例(父进程写,子进程读):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main() {
int fd[2];
pid_t pid;
char buffer[128];
// 创建无名管道 pipe(fd) 会创建一个无名管道,用于进程间通信。fd[0] 是 读端,fd[1] 是 写端。
if (pipe(fd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
// 子进程:读
close(fd[1]); // 关闭写端
int n = read(fd[0], buffer, sizeof(buffer));
buffer[n] = '\0';
printf("子进程收到:%s\n", buffer);
close(fd[0]);
}
else {
// 父进程:写
close(fd[0]); // 关闭读端
const char *msg = "来自父进程的消息";
write(fd[1], msg, strlen(msg));
close(fd[1]);
wait(NULL); // 等待子进程结束
}
return 0;
}
2、有名管道(named pipe / FIFO)
适用于无亲缘关系的进程之间通信,使用文件系统中的一个路径,通过 mkfifo() 创建。
2.1 创建FIFO文件
mkfifo /tmp/myfifo
2.2 写入进程(终端 1)写数据到管道
// writer.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd = open("/tmp/myfifo", O_WRONLY);
const char *msg = "你好,来自写入进程";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
编译并运行:
gcc writer.c -o writer
./writer
2.3 读取进程(终端 2)从管道读取数据
// reader.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
char buffer[128];
int fd = open("/tmp/myfifo", O_RDONLY);
int n = read(fd, buffer, sizeof(buffer));
buffer[n] = '\0';
printf("读取进程收到:%s\n", buffer);
close(fd);
return 0;
}
编译并运行:
gcc reader.c -o reader
./reader
运行顺序:先运行 reader,再运行 writer,否则 writer 会阻塞等待读者出现。
tips
肯定会有人有疑问,mkfifo和touch的区别是什么?
区别如下:
项目 | mkfifo | touch |
---|---|---|
创建的文件类型 | 有名管道(FIFO) | 普通文件(regular file) |
文件用途 | 进程间通信(IPC)用的特殊文件 | 存储数据、代码、文本等 |
存储行为 | 在内存中,不保存数据,只用于传输、读取后消失 | 在硬盘中,可永久存储数据 |
文件类型标识(ls -l ) | 以 p 开头,如 prw-r--r-- | 以 - 开头,如 -rw-r--r-- |
文件命令识别(file ) | fifo (named pipe) | empty 或 ASCII text 等 |
数据传输方式 | 流式、顺序、进程之间传递 | 任意读写、可 seek 定位 |
阻塞特性 | 有阻塞:写/读端未打开会阻塞 | 无阻塞,普通 I/O 行为 |
示例使用场景 | 多个进程间通信(如生产者/消费者) | 创建空文件、日志、代码文件等 |
三、管道的工作机制
管道在内核中实现了一个循环缓冲区,包括两个文件描述符:
- 读端(fd[0]):从中读取数据。
- 写端(fd[1]):向其中写入数据。
当写入数据时,它会被写入管道的内核缓冲区;读取时,数据从缓冲区中被读取并移除。以下是关键行为:
情况 | 说明 |
---|---|
写端关闭,读端读取 | 读到 EOF,返回 0 |
读端关闭,写端继续写入 | 系统发送 SIGPIPE 信号,程序异常终止 |
缓冲区已满,继续写入 | 写操作会阻塞,直到缓冲区有空间 |
缓冲区为空,继续读取 | 读操作会阻塞,直到有数据可读 |
四、优点与缺点
优点:
- 简单高效,适合小量数据传递。
- 操作系统原生支持,性能开销小。
- 可用于父子进程之间快速构建通信通道。
缺点:
- 半双工通信(只能一个方向),若需双向需开两个管道。
- 无名管道仅适用于有亲缘关系的进程。
- 数据是字节流,没有结构化管理。
- 容量有限,适合传递小数据。
五、总结
管道是一种进程间通信(IPC)机制,用于在进程之间传递数据。它通过内核缓冲区实现数据的单向流动。
特性 | 无名管道 | 有名管道(FIFO) |
---|---|---|
是否具文件名 | 否 | 是(路径) |
使用函数 | pipe() | mkfifo() + open() |
是否跨进程关系 | 否(需要亲缘关系) | 是 |
使用难度 | 较低 | 稍高 |
使用场景 | 父子进程通信 | 任意进程间通信 |