让进程能够“相互沟通”的高级方式一:匿名管道

news/2025/2/16 6:45:24/

代码运行及测试环境:linux centos7.6
在阅读这篇文章时,需要掌握OS对文件管理的基础知识(文件打开表、文件描述符、索引结点…)

前言

我们都知道进程是具有独立性的,意味着进程之间无法相互通信。但在一些情况下,不得不让进程之间进行通信。通信的作用主要有:

  • 数据的传输。
  • 数据的共享。
  • 控制其他进程。

数据的传输、共享是非常容易理解的。为什么需要一个进程去控制另一个进程呢?做一个虚拟的假设,进程A负责检测地铁进站,进程B负责打开地铁门。进程B一直处于休眠状态,当进程A检测出地铁到站停靠后,进程A可以唤醒进程B,打开地铁门。可见,让一个进程去控制另外一个进程,在日常生活中是非常频繁的, 也是十分重要的。

如果要让两个相互独立的进程进行通信,那它们之间需要一个媒介。OS提供了这个媒介,并且对它们之间的通信进行管理。根据OS提供媒介的不同,产生了许多进程间通信的方式,例如:共享内存、消息队列、管道等等。我在这篇文章里只谈论管道通信——匿名管道。

匿名管道的底层原理

首先强调匿名管道只能用于 具有“血缘关系”的进程之间的通信。

当用户请求建立匿名管道时,操作系统会为进程创建一个管道文件,这个文件它并不需要文件名,因为OS建立管道文件后,就会直接把它的文件属性拷贝到打开文件表的一个条目内,用户可以获取到对应的文件描述符,进而读写管道。
在这里插入图片描述
它会让俩个相邻的打开文件表表项同时指向这个管道文件的inode, 是为了读写操作分离,一个文件描述符对应的是写入操作,另一个文件描述符对应的是读取操作。

虽然匿名管道的本质是一个文件,但和普通文件有许多差别。从另外一个角度来看,匿名管道更偏向于解释成一块缓冲区。用户并不是直接将数据写入磁盘上的数据块,也不会直接从磁盘上读取数据出来, 而是以内核缓冲区为媒介。
在这里插入图片描述
由OS再去调用数据从内核缓冲区写入磁盘和读到内核缓冲区的接口。

那么匿名管道是如何实现 具有血缘关系的进程 之间的通信呢?以父子进程间的管道通信为例。
在这里插入图片描述
首先父进程创建一个管道, 再创建子进程,子进程的PCB是以父进程为模板的,父子进程此刻的打开文件表是一样的, 所以父进程文件打开表有两个表项指向管道,子进程打开文件表也有两个表项指向该管道,并且文件描述符此刻是一样的。这样,相互独立的两个父子进程看到了同一份资源——“管道”, 管道自然而然充当了它们通信的媒介。

管道通信是单向通信的,只允许一端进行写入,另一端进行读取。如果要实现管道通信,需要根据情况,关闭对应的读写端。

匿名管道创建过程

我们将上述的过程简化一下
🍍第一步:父进程创建匿名管道
在这里插入图片描述
🥑第二步:父进程创建出子进程
在这里插入图片描述
🍉第三步:根据需要,关闭对应的读端和写端
(这里以父进程写入,子进程读取为例)
在这里插入图片描述

匿名管道的几种情况

创建匿名管道的系统调用pipe

#include <unistd.h>
int pipe(int pipefd[2]);
//pipefd是一个输出型参数:
//pipefd[0]对应读端的文件描述符 pipefd[1]对应写端
//如果创建管道成功返回0,否则返回-1

简单写一个父进程读取管道、子进程写数据进管道的例子。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main(void)
{//1.创建管道int pipefd[2];if(pipe(pipefd) != 0){perror("pipe");exit(-1);}//2.父进程创建出子进程if(fork() == 0){//child//3.1 让子进程写入数据进入管道 ,关闭读端close(pipefd[0]);const char *str = "i am ydy.";while(1){write(pipefd[1], str, strlen(str));sleep(1);}}else{//father//3.2 父进程从匿名管道种读取数据, 关闭写端close(pipefd[1]);while(1){char buffer[64] = {0};ssize_t s = read(pipefd[0], buffer, sizeof(buffer));if(s < 0){//读取失败break;}else if(s == 0){//写端关闭,且数据读取完毕printf("child quit...\n");break;}else{printf("child say# %s\n", buffer);}}}waitpid(-1, NULL, WNOHANG);return 0;
}

匿名管道的几种情况:
🌻读端读得慢,写端写得快
🌼读端读得快,写端写得慢
💐写端在写入数据,读端突然关闭
🌷读端在读取数据,写端突然关闭

这几种情况的特点,可以自行检测得出,我在这里直接写结论,当然这些结论的正确性我在此之前都用代码检验过了,毕竟“实践是检验真理的唯一标准”。

🌻读端读得慢,写端写得快

管道内有数据,读端就可以读取。由于写端写得比较快,所以按照趋势下去,管道一定会满。管道满了以后,写端进程会被阻塞,等待读端读取数据。你可能认为,管道满了后,读端读取一份数据,写端就会写入数据,事实并不是这样。实际上,写进程一旦被阻塞,只有管道能够提供一定的空闲空间大小(这个大小具体我并不知道是多少, 在我的机子上跑,测出来的是至少是1kb)后,写进程才会被唤醒。

🌼读端读得快,写端写得慢

由于写的速度赶不上读取的速度, 所以管道极可能某个时刻没有数据。读进程会被阻塞,等待写进程写入数据,直到管道有数据。

💐写端在写入数据,读端突然关闭

管道的作用就是为了让两个进程通信,读端关闭了,写进程又不需要拿取数据,所以读进程不在,管道就没有意义了。所以读端关闭,写进程也会退出。

🌷读端在读取数据,写端突然关闭

写端关闭了,管道内可能还会有数据。读进程是需要拿数据的,管道里的东西还有用,所以读端把数据全部读完后,读进程再退出。

读进程读取得慢,写进程写入得快,会出现这种现象的原因本质上是因为管道是有大小的,在Linux往管道写入64KB数据的时候,写进程就不再写入了,而超过4KB的时候,OS就不再保证管道的写入具有原子性了。对于一端关闭的现象,间接阐述了管道机制必须要有确定对方存在的协调能力。

匿名管道特点

  • 仅限具有血缘关系的进程使用
    根据匿名管道的创建过程可知,匿名管道的本质是一个文件,没有文件名,由用户请求,OS帮助建立后,直接把文件属性拷贝到进程的打开文件表内。血缘关系的进程PCB都是以“父进程”模板创建的,所以子进程以及有血缘关系的进程,打开文件表内都有关于匿名管道的属性。由于没有文件名,其他进程无法自行打开文件,因此非血缘关系的进程间无法使用匿名管道。
  • 匿名管道的生命周期是随进程的
    因为下一次,进程无法打开上一个管道文件(没有文件名)。
  • 管道是单向通信的。
  • 管道是面向字节流的
  • 管道机制必须能够拥有互斥、同步和确定对方存在的协调能力

http://www.ppmy.cn/news/108837.html

相关文章

八大排序:直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序

文章目录 排序概念常见的排序算法常见排序算法的实现直接插入排序希尔排序选择排序堆排序冒泡排序快速排序递归实现Hoare版本挖坑法前后指针法 非递归实现Hoare版本挖坑法前后指针法 快速排序俩个优化 归并排序递归实现非递归实现外排序 计数排序 常见排序算法的性能分析 排序概…

MySQL_8 相当牛逼的索引机制

目录 一、索引机制的引入 1.索引机制&#x1f402;B在哪里&#xff1f; 2.索引机制提高查询速度的原理 : 二、索引的创建 1.索引分类 : 2.使用格式 : 3.代码演示 : 三、索引的删除 1.格式 : 2.演示 : 四、索引的查询 1.格式 : 2.演示 : 五、索引的使用规则 一、索…

bat批处理一键安装、卸载mysql数据库

文章目录 1 一键安装1.1 启动延迟扩展模式1.2 切换盘符到bat所在路径1.3 新建data文件夹1.4 设置变量1.5 新建my.ini并写入配置1.6 安装mysql服务并初始化数据库1.7 初始化文件1.8 添加环境变量1.9 全部代码如下&#xff0c;复制放到与bin目录同级即可使用 2 一键卸载 1 一键安…

随机梯度下降法

梯度下降法有两个比较大的缺点&#xff1a; --计算花时间 --容易陷入局部最优解 比如以下形状的函数&#xff0c;最优解取决于初始值的选取。 梯度下降法的表达式如下&#xff0c;这个表达式使用了所有训练数据的误差&#xff1a; 随机梯度下降法表达式&#xff1a; 在随机梯…

Ctfshow基础二刷(1)

前言&#xff1a; 前两天的信安给我整emo了&#xff0c;头一回打正经比赛&#xff0c;结果发现基础太差&#xff0c;代码审计烂得一踏糊涂。 寻思寻思&#xff0c;从头整一遍基础。又买了安恒出的新书。争取7号去吉林打省队选拔不给导儿丢脸吧呜呜 文件包含 web78: 这题一…

数字滚动插件的使用

数字滚动插件的使用 安装插件 $ npm i vue-count-to使用数字滚动插件 使用count-to组件来代替要显示的数字 <span>组织总人数</span> <!-- 起始值 终点值 滚动时间 --> <count-to:start-val"0":end-val"228":duration"1000&…

PointNet++ 源码解读

1.从main函数开始&#xff1a; 1.1 确定使用的哪个GPU. 1.2 保存训练时的参数和日志 2. 加载数据 先找到存放训练和测试数据的目录&#xff0c;接下来加载相关的数据参数&#xff1a; 下面是执行的结果&#xff1a; 接下来为训练样本开始做准备&#xff1a; 给不同标签做上标记…

【网络编程二】UDP与TCP协议你学会了吗~

目录 &#x1f31f;需要知道 1、什么是网络编程&#xff1f; 2、怎么进行网络编程&#xff1f; 3、TCP与UDP的区别&#xff1f; &#xff08;面试题&#xff09; &#x1f31f;一、UDP &#x1f308;1、UDP数据报套接字编程 &#x1f308;2、实现一个简单的UDP回显服务…