pipe管道
实现原理: 内核借助环形队列机制,使用内核缓冲区实现。
特质: 1. 伪文件
2. 管道中的数据只能一次读取。
3. 数据在管道中,只能单向流动。
局限性: 1. 自己写,不能自己读。
2. 数据不可以反复读。
3. 半双工通信。
4. 血缘关系进程间可用。
pipe 函数:
创建,并打开管道: int pipe(int fd[2]);
参数: fd[0]: 读端。
fd[1]: 写端。
返回值: 成功: 0
失败: -1 errno
管道的读写行为:
读管道:
1. 管道有数据,read 返回实际读到的字节数。
2. 管道无数据:1)无写端,read 返回 0 (类似读到文件尾)
2)有写端,read 阻塞等待。
写管道:
1. 无读端, 异常终止。 (SIGPIPE 导致的)
2. 有读端: 1) 管道已满, 阻塞等待
2) 管道未满, 返回写出的字节个数。
例:使用管道实现父子进程间通信,完成:ls | wc -l 假定子进程(兄)实现 ls,子进程(弟)实现 wc
ls 命令正常会将结果集写到 stdout。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str){
perror(str);
exit(1);
}
int main(int argc, char *argv[]){
int fd[2]; // 建立管道数组
int ret, i;
pid_t pid;
ret = pipe(fd); // 建立管道
if(ret == -1) sys_err("pipe error"); // 建立失败
for(i = 0; i < 2; i++){
pid = fork(); // 多进程
if(pid < 0) sys_err("fork error");
if(pid == 0) break; // 子进程直接退出循环
}
if(i == 0){ // 兄进程
close(fd[0]); // 关闭读端
dup2(fd[1], STDOUT_FILENO); // 将标准输出写入管道
execlp("ls", "ls", NULL); // 执行ls命令,执行成功就会跳转到其他程序不继续执行当前程序
sys_err("ls error");
}
else if(i == 1){ // 弟进程
close(fd[1]); // 关闭写端
dup2(fd[0], STDIN_FILENO); // 将读管道写入到标准输入中
execlp("wc", "wc", "-l", NULL); // 执行wc命令
sys_err("ls error");
}
else if(i == 2){ // 父进程
close(fd[0]);
close(fd[1]);
wait(NULL); // 回收进程
wait(NULL);
}
return 0;
}
fifo命名管道
管道的优劣:
优点:简单,相比信号,套接字实现进程通信,简单很多。
缺点:1.只能单向通信,双向通信需建立两个管道。
2.只能用于有血缘关系的进程间通信。该问题后来使用 fifo 命名管道解决。
fifo 管道:可以用于无血缘关系的进程间通信。
命名管道: mkfifo
无血缘关系进程间通信:
读端,open fifo O_ RDONLY
写端,open fifo O_ WRONLY
fifo 操作起来像文件:
建立fifo文件:mkfifo("fifo_name", 0644);
向管道写数据:fd = open("fifo_name", O_WRONLY); write(...);
向管道读数据:fd = open("fifo_name", O_RDONLY); read(...);
*当一个写端多个读端的时候,由于数据一旦被读走就没了,所以多个读端的并集才是写端的写
入数据。
*在阻塞模式下使用open打开FIFO文件的时候,read端会阻塞等待write端open打开文件,直到write进程也使用open打开FIFO的时候,read进程中的open才会返回,反过来也是一样。