LINUX14 进程间的通信 - 管道
管道
12.1命名管道
12.1.1命名管道(Named Pipe/FIFO)
命名管道克服了普通管道只能在亲缘进程间使用的限制。
特点:
- 在文件系统中有对应的文件名(遵循UNC命名规范)
- 允许无亲缘关系的进程通信
- 通过 mkfifo() 系统调用创建
- 支持多对多通信模式
创建命名管道方法:
#include <sys/stat.h>// 创建命名管道mkfifo("/tmp/myfifo", 0666);
使用命名管道方法
#include<fcntl.h>int fd=open("/tmp/myfifo",O_WRONLY);//发送数据端int fd=open("/tmp/myfifo",O_RDONLY);//写入数据端
12.1.2管道读写规则深度理解
- 读端关闭:如果所有读端关闭,写端进程会收到SIGPIPE信号,导致进程终止
- 写端关闭:读端读取完所有数据后,read返回0,表示文件结束
- 原子性保证:当写入数据量不大于PIPE_BUF(通常512B-4KB)时,Linux保证写入的原子性。
示例两个进程进行通信:
接收端a.c文件如下:
int fd=open("fifo",O_RDONLY);printf("fd=%d\n",fd);char buff[128]={0};while(1){int n=read(fd,buff,127);if(n==0)break;printf("buff=%s",buff);}
发送端b.c文件如下:
void fun(int sig){printf("sig = %d \n",sig);signal(SIGPIPE,SIG_DFL);}int main(){signal(SIGPIPE,fun);mkfifo("fifo",0600);int fd=open("fifo",O_WRONLY);printf("fd=%d\n",fd);char buff[128]={0};while(1){fgets(buff,127,stdin);write(fd,buff,sizeof(buff));}return 0;}
12.1.3管道的工作原理与核心特性
- 单向通信:数据只能从写端 ( fd[1] ) 流向读端 ( fd[0] )。要实现双向通信,需要建立两个管道。
- 内核缓冲:数据在内核缓冲区中暂存。读进程从缓冲区读取数据,写进程向缓冲区写入数据。
- 同步与阻塞:
- 当读进程尝试读取一个空管道时,它会阻塞,直到有数据写入。
- 当写进程尝试向一个已满管道写入数据时,它会阻塞,直到有空间可用。
- 如果所有读端都已关闭,写进程会收到 SIGPIPE 信号,通常导致进程终止。
- 字节流导向:管道不维护消息边界,数据被视为连续的字节流。多次写入可能被一次读取,单次写入也可能被多次读取。
12.2 匿名管道
12.2.1匿名管道(Anonymous Pipe)
匿名管道(Anonymous Pipe)是Linux/Unix系统中一种最基本的进程间通信机制,主要用于具有亲缘关系的进程间数据传递,匿名管道通过内核缓冲区实现单向数据流,其本质是一个内核维护的环形队列(先进先出)。
关键特性:
- 单向通信:数据只能从写端流向读端
- 血缘关系限制:传统匿名管道只能用于父子进程等有亲缘关系的进程间
- 内核缓冲区:默认大小通常为4KB或64KB,数据存储在内核内存中
- 同步机制:读空管道会阻塞,写满管道也会阻塞
12.2.2匿名管道工作原理
创建与使用流程
创建管道:
int pipefd[2];//// pipefd[0]读端,pipefd[1]写端pipe(pipe1); // 父→子pipe(pipe2); // 子→父// 1. 创建管道if (pipe(pipefd) == -1) {perror("pipe创建失败");return 1;}
父子进程通信:
// 2. 创建子进程pid_t pid = fork();if (pid == 0) {// 子进程:关闭写端,读取数据close(pipefd[1](@ref);read(pipefd[0], buffer, sizeof(buffer));printf("子进程收到: %s\n", buffer);close(pipefd;} else {// 父进程:关闭读端,写入数据close(pipefd;write(pipefd[1], "Hello from parent!", 18);close(pipefd[1](@ref);}
文件描述符继承机制:当父进程调用 fork() 创建子进程时,子进程会继承父进程的文件描述符表,因此父子进程可以通过相同的文件描述符访问同一个管道。
12.2.3匿名管道关键特性
1. 缓冲区与容量
- 默认大小:通常为4KB或64KB,可通过系统配置调整
- 原子操作:写入量小于 PIPE_BUF (通常4KB)时可保证操作的原子性
2. 阻塞行为
匿名管道的读写操作具有特定的阻塞特性:
情况 | 读端行为 | 写端行为 |
---|---|---|
管道空 | 阻塞等待 | 正常写入 |
管道满 | 正常读取 | 阻塞等待 |
写端关闭 | 读取剩余数据后返回0 | - |
读端关闭 | - | 收到SIGPIPE信号 |
3. 四种特殊情况的处理
- 所有写端关闭:读端读完数据后, read 返回0
- 写端未关闭但无数据:读端阻塞等待
- 所有读端关闭:写端会收到 SIGPIPE 信号
- 读端未关闭但管道满:写端阻塞等待
管道对比
特性维度 | 匿名管道 (PIPE) | 命名管道 (FIFO) | 共同点 |
---|---|---|---|
本质 | 内核缓冲区 | 内核缓冲区(但有文件名) | 都是内存中的缓冲区,以文件形式抽象 |
通信方向 | 半双工,单向流动 | 半双工,单向流动 | 单向通信,双向需两个管道 |
进程关系 | 必须具有亲缘关系 | 无需亲缘关系 | - |
数据模式 | 字节流,无消息边界 | 字节流,无消息边界 | 数据是连续的字节流 |
同步机制 | 阻塞I/O,读写空/满管道会阻塞 | 阻塞I/O,读写空/满管道会阻塞 | 内核提供同步,保证数据安全 |
生命周期 | 随进程创建和销毁 | 在文件系统中持久存在,直到被显式删除 | 进程退出会关闭其持有的描述符 |
创建方式 | pipe() 系统调用 | mkfifo() 函数或命令 | - |
可见性 | 仅限继承文件描述符的进程 | 文件系统可见,任何进程可访问 |
选择依据:
- 需要在有亲缘关系的进程(如父子进程)间传递数据时,优先考虑匿名管道。
- 需要在无亲缘关系的任意进程间通信时,必须使用命名管道。
- 对于需要高性能、大数据量共享的场景,可考虑共享内存;对于需要可靠、结构化消息传递的场景,可考虑消息队列或套接字。