管道是(内核缓冲区,打开管道时会在内存中分配一块空间;伪文件--不占用磁盘空间)一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。例如在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。
open可以打开一切类型的文件,写入管道的永远在内存中,关闭管道文件,内存中的信息将会丢失
管道分为两部分:读端,写端,对应两个文件描述符。数据写端流入,读端流出;操作管道的进程被销毁之后,管道自动被释放;管道只读或只写打开时会阻塞,需要两个进程同时打开,一个读,一个写;当写端关闭时,读端不会阻塞
管道的实现原理:内部实现方式:队列(环形队列)。管道中有头指针:负责写。管道中有尾指针:负责读。刚开始头尾指针都指向头。写数据是头指针不断向后移动,管道写满后又回头部循环写入,遇到尾指针(管道满,写阻塞)则无法写入。当尾指针读时遇到头指针(管道空,读阻塞)则读取结束。缓冲区大小:默认4k,内核会适当的调整,但有一定范围,不会无限放大
基于队列的数据结构,管道数据只能读取一次,不能重复读取。半双工(双向,但不能同时工作)无名管道:适用于有血缘关系的进程
有名管道和无名管道的区别:
无名:只能在父子进程间进程通信。
有名:可在任意两个进程间通信,可以通过打开同一个管道文件实现。
数据在内存中存放,但是主要、有名的管道文件在磁盘
无名管道
# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>
int main()
{
int fd[2];
int ret = pipe(fd);
if(ret == -1)
{
perror("pipe error");
exit(0);
}
printf("pipe[0] = %d\n",fd[0]);
printf("pipe[1] = %d\n",fd[1]);
close(fd[0]);
close(fd[1]);
return 0;
}
无名管道通信
单个进程可以使用管道完成读写操作,先打开管道后fork子进程,在父子进程利用管道通信过程中,因为数据只能读取一次,如果某一进程执行写操作,则需关闭该进程的读端,如果某一进程执行读操作,则需关闭读端。
示例:父进程查询当前向管道中写入数据;子进程从管道中读取数据。
# include <stdio.h>
# include <errno.h>
# include <unistd.h>
# include <stdlib.h>
# include <sys/types.h>
# include <sys/stat.h>
int main()
{
int fd[2];
pipe(fd);//fd[0]读 fd[1]写
pid_t pid = fork();//创建子进程
if(pid == -1)//子进程创建失败
{
printf("fork error");
exit(0);
}
//子进程查询管道中数据 只读不写
if(pid == 0)
{
char buff = {0};
close(fd[1]);//不写,关掉写
read(fd[0],buff,127);
printf("child buff = "%s\n",buff);
close(fd[0]);//读完关掉读
}
//父进程 只写不读
else
{
close(fd[0]);//不读,关掉读
sleep(1);
write(fd[1],"hello",5);
close(fd[1]);//写完关掉写
exit(0);
}
//父子同时close才会关掉
close(fd[0]);
close(fd[1]);
return 0;
}