Linux管道 有名管道(FIFO)工作机制全解:从理论到实践
有名管道(重要)
有名管道/命名管道,主要用于没有血缘关系进程间的通信
当然也支持有血缘关系的情况,只是如果有血缘关系,没有必要使用有名管道,无名管道效果更佳
引入
好了,现在使用条件有了,该如何实现呢?
不同进程都有自己的独立空间,那如何将不同进程联系起来呢?
可以通过 磁盘内的文件(用于磁盘映射),物理内存,寄存器,数据库,第三方电脑等等,只要是不同进程都可以识别,并不是但属于一个进程的即可。
当然利用不同方式实现肯定是不同的方法,这里主要是带大家一步步地去实现有名管道。
这里我们实现有名管道的方式 是借助物理内存。
那么,问题又来了:物理内存是属于底层的,该如何操作物理内存呢?
物理内存偏底层,我们直接操作,我们目前做不到,也不是我们的重点。
我们说过,在Linux一切皆可视为文件,因此在这里我们通过将物理内存抽象为文件名,两个进程通过操作这个文件名,来实现进程间的通信。
因此这时候的物理内存就是一个管道,抽象出来的文件名,也就是有名管道,有名的来源。
这里简单说一下虚拟区别和物理内存的区别
在每个进程开始,系统会为每一个进程分配4GB的虚拟内存空间,这4GB是虚拟的。
而物理内存是切实存在的。
大家可以使用下面这张图来理解
有名管道的概述
特点
- 半双工,同一时刻只能在一个方向上流动
- 写入FIFO中的数据遵循先入先出的规则
- 传送数据无格式,事先必须约定好数据格式,如多少个字节算一个消息等
- FIFO在文件系统中作为一个特殊的文件而存在,但是它的实际内存却存放在物理内存
- 管道在内存中对应一个缓冲区,系统不同其大小也不同
- FIFO读取数据是一次性的,数据一旦被读取,就会被FIFO抛弃,释放其空间以存放更多数据
- 使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便于以后使用
- FIFO 有名字,不相关的进程可以通过打开命名管道进行通信
有名管道的API
函数介绍
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo( const char *pathname, mode_t mode);
功能介绍
用于有名管道文件的创建(将物理内存抽象为文件)
参数
pathname:FIFO的路径名+文件名
mode:mode_t类型的权限描述符
返回值
成功:返回0
失败:返回-1,并设置errno
代码案例
00_read.c
在我的文件中文件名是 00_Bob.c
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//创建有名管道文件
mkfifo("my_fifo",0666);
//作为收端,以读的方式打开文件
//这里因为文件是我们创建的,已知文件一定存在,不需要添加O_CREAT行为标志
int fd = open("my_fifo",O_RDONLY);
if(fd == -1)
{
perror("open");
_exit(-1);
}
printf("收端已准备就绪\n");
//读数据
while(1)
{
char buf[128] = "";
read(fd,buf,sizeof(buf));
printf("读到的数据为:%s\n",buf);
if(strcmp(buf,"bye") == 0)
{
break;
}
}
//关闭文件
close(fd);
unlink("my_fifo");
return 0;
}
00_write.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//打开有名管道文件 这里我建议读写端都打开文件,因为我们不知道哪个进程会先打开
//又由于mkfifo文件重复 会返回-1 因此这里我们不进行打开错误判断
mkfifo("my_fifo",0666);
//Lucy负责发 以写的方式打开该文件
int fd = open("my_fifo",O_WRONLY);
if(fd == -1)
{
perror("open");
_exit(-1);
}
printf("发端准备就绪\n");
//写入数据
while(1)
{
//手动输入数据
printf("输入数据:");
char buf[128] = "";
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = 0;
write(fd,buf,strlen(buf));
if(strcmp(buf,"bye") == 0)
{
break;
}
}
//关闭文件
close(fd);
unlink("my_fifo");
return 0;
}
代码运行结果:
在我的文件中文件名是 00_Lucy.c
有名管道的读写特点
1、open 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO
2、open 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO。
3、open 以只读、只写方式打开 FIFO 时会阻塞,调用 read 函数从 FIFO 里读数据时 read 也会阻塞。
4、通信过程中若写进程先退出了,则调用 read 函数从 FIFO 里读数据时不阻塞;若写进程又重新运行,则调用 read 函数从 FIFO 里读数据时又恢复阻塞。
5、通信过程中,读进程退出后,写进程向命名管道内写数据时,写进程也会(收到 SIGPIPE 信号)退出。
6、调用 write 函数向 FIFO 里写数据,当缓冲区已满时 write 也会阻塞。
解释
打开通道时,必须读端和写端都要打开
打开后
关闭读端,写端一旦写入,会导致写进程退出
关闭写端,读端可以继续读(此时被阻塞),写端重新运行,读端可以读,但会被阻塞
这里大家可以理解为一个水管,写为进水口,读为出水口
出水口堵住了,再向内灌水,水管就会坏,因此会导致进水口也关闭
进水口堵住了,但是出水口仍可以打开,最差的结果也就是不出水
代码思想补充
我们可以知道上面的例子中代码重复部分过多,会显得内容优点冗余,因此为了降低代码的冗余度,我们将上面两个代码合二为一,使用的是条件编译 对代码进行删减
今天时间原因,不给大家进行代码演示了,将在下篇分享中为大家补充,谢谢理解!
我们从上面学习中可以知道管道读写段一旦确定无法修改,我将在下篇博客中 将实现管道双向通信
结束
代码重在练习!
代码重在练习!
代码重在练习!
今天的分享就到此结束了,希望对你有所帮助,如果你喜欢我的分享,请点赞收藏夹关注,谢谢大家!!!