初识Linux · 匿名管道

目录

前言:

匿名管道

理解为什么?

理解是什么?

理解怎么做?


前言:

引入管道之前,我们引入几个问题,进程通信的相关问题。

第一个是进程之间为什么要通信,对于进程间通信来说,进程是具有独立性的,而进程 = 内核数据结构 + 代码数据,进程通信就是因为需要协同,协同的本质是通过数据的的流动来协同的。所以第二个问题,进程如何通信?

进程间通信是通过数据进行通信的,那么也就是说A进程给某些数据,B进程需要接受到这个数据,可是以什么作为数据流通的平台呢?此时管道就出场了,管道可以说是作为信息的载体保证两个进程之间可以通信。对于进程间的通信常见的方式有消息队列,共享内存,信号量,后面介绍。

使用管道通信是直接复用的内核代码,这样不仅可以简单一点,还可以降低成本。

可是说了这么多,管道究竟是什么呢?

两个进程之间想要通信一定要看到同一份资源,或者是同一份内存空间,所以管道实际上就是OS开辟的堆区和栈区之间的那一块共享区的资源。

管道分为匿名管道和有名管道,我们从匿名管道开始介绍,到下篇文章介绍的进程池的小项目,到最后的命名管道,这是管道的介绍顺序,那么直接进入主题吧!


匿名管道

理解为什么?

我们通过这个图简单理解一下为什么?为什么要存在管道?

假设现在有两个进程,A进程将文件输入到了内核级文件缓冲区,然后数据通过OS到了磁盘,B现在通过read方法,读取到了A进程write的数据,这个过程看起来好像没有什么槽点?

实际上,为什么我们不能直接让A进程输入的数据直接给B呢?

那么这个过程是不需要重新设计一个通信端口的,太麻烦了,我们需要一个fork函数 close函数什么的我们就可以实现这样一个功能:

int main()
{pid_t id = fork();if(id == 0){//子进程准备work...}//父进程准备work...return 0;
}

实现这个功能之前,我们需要了解到管道通信的文件描述符是如何的?

先看一段代码:

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{pid_t id = fork();if (id == 0){std::cout << "I am a child process!" << std::endl;}if(id > 0){std::cout << "I am a father process!" << std::endl;}return 0;
}

我们思考一个现象,为什么父子进程默认的都是打印在了1上?

进程打开的时候我们知道是默认打开了三个流,但是我们是否思考过为什么默认打开了吗?前文提及到了历史原因是存在的。所以当我们启动了Linux机器的时候,bash进程已经启动了,此时bash进程的三个流已经打开了,我们后面启动的所有进程都是bash进程的子进程,子进程的三个流也默认打开了,那么如果我们子进程close到0 1 2呢?

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{pid_t id = fork();if (id == 0){close(0);close(1);close(2);std::cout << "I am a child process!" << std::endl;}if(id > 0){std::cout << "I am a father process!" << std::endl;}return 0;
}

现象就是父进程能正常打印,所以关闭了fd实际上不会影响自己的父进程,所以我们利用这个点

可以实现管道单向通信的功能,可是为什么实现的是单向的呢?因为如果是双向的,也就是父进程子进程的数据全部都在管道,读取的时候不经过一些操作肯定是要出错的,所以我们先简单就看看单向的。

为什么这里我们能得出的结论是子进程能继承父进程的文件描述符表,为了实现单向的管道通信我们需要关闭文件描述符。


理解是什么?

我们实现管道的时候,需要用到的函数是pipe:

对于该函数来说,我们使用的时候不使用那个结构体,使用的int pipe(int pipefd[2])即可,结构体暂时先不管,而对于pipefd[2]这是个输出型参数,管道开辟成功之后,fd[1]是管道的写入文件描述符,fd[0]是文件描述符的读端。

而为什么管道叫做匿名管道是因为我们得到该文件描述符甚至不需要文件名,不需要文件路径,所以叫做匿名管道。

这是创建管道最开始的模样,最后需要我们手动的关闭几个文件描述符,至于为什么单向,为什么要关,是否可以不关等问题这里不做讨论,因为上文已经介绍了。

我们今天的重点是放在怎么做上。


理解怎么做?

由前文的是什么为什么,我们知道了基本操作是需要我们创建管道,使用pipe函数,开辟好管道之后,我们需要手动将两个文件描述符关闭,因为子进程会继承父进程的文件描述符表,所以对于父进程来说我们同样需要关闭对应的文件描述符表。

对于0 1是读还是写来说,我们结合形状吧,0是张开了嘴巴,所以是读取,1就是另一个了。

怎么做我们从三个部分开始,第一个是创建管道,第二个是子进程写入数据,第三个是父进程读取数据。

如果成功创建了管道,返回的就是0,如果不等于0我们就可以cerr了。

    int pipefd[2];int n = pipe(pipefd);if(n){std::cerr << "errno:" << errno << ":"\<< "errstring is :" << strerror(errno) << std::endl; }std::cout << "pipefd[0]:" << pipefd[0] << " pipefd[1]:" << pipefd[1] << std::endl;sleep(1);

创建管道部分,如果返回值不是0的话也就是创建失败了,所以我们打印出来具体的错误信息,使用到的是前面学习到的errno和strerror,一个是错误码,一个是错误码对应的字符串,然后打印出来0 1对应的文件描述符,就算是管道创建成功了。

现在就是子进程的写入数据部分,我们写对应的代码之前,简单思考一下大体的写入思路是什么样的?

首先是创建子进程,创建之后,关闭不需要的fd,然后子进程开始work,对应的工作做完之后,关闭掉对应的文件描述符,然后子进程退出,父进程回收即可,这个过程文件描述符肯定都是要关闭的,因为管道这个内存是一个引用计数的空间,所以如果不关闭,导致的结果就是内存泄漏,毕竟是空间都没有释放。

整体代码为:

    //2.创建子进程pid_t id = fork();if(id == 0){//子进程开始准备工作std::cout << "子进程准备开始写入数据了..." << std::endl;sleep(1);close(pipefd[0]);SubProcessWrite(pipefd[1]);close(pipefd[1]);exit(0);} 

然后就是子进程的subProcessWrite函数了:

std::string getOtherMessage()
{//消息次数static int cnt = 0;std::string message = std::to_string(cnt);cnt++;//子进程的pidpid_t self_id  = getpid();std::string stringpid = std::to_string(self_id);std::string info = "messageid: ";message += message;message += " My pid is :";message += stringpid;return message;
}
void SubProcessWrite(int wfd)
{int pipesize = 0;std::string message = "Father,I am your son process! ";char charactor = 'A';while(true){std::cout << "+++++++++++++++++++++++++++++++++++++++++++++++++" << std::endl;//得到数据std::string info = message + getOtherMessage();//开始写入数据write(wfd,info.c_str(),info.size());std::cerr << info << std::endl;}std::cout  << "child quit……" << std::endl;
}

写入数据的同时通过cerr打印到显示器上,并且写入的时候我们通过函数GetOtherMessage获取到子进程的Pid和写入了多少次的字符串。

这是子进程的写入函数部分。

子进程写入完毕之后是父进程开始读取数据:

void ProcessFatherRead(int rfd)
{char inbuffer[SIZE];while(true){//休眠一会儿开始读取sleep(2);std::cout << "---------------------------------------------------" << std::endl;sleep(500);ssize_t n = read(rfd,inbuffer,sizeof(inbuffer) - 1);if(n > 0){inbuffer[n] = 0;// == '\0'std::cout << inbuffer << std::endl;}else if(n == 0) //如果n == 0代表读到了文件结尾{std::cout << "client quit, father get return val: " << n << " father quit tool" << std::endl;break; }else if(n > 0){std::cout << "Read error!" << std::endl;break;}}
}

父进程使用函数read,这里不妨温习一下read函数:

返回值是ssize_t ,读取count个字符,读取到buf数组里面。

如果返回值是0,代表读取到了文件的末尾,如果返回的是-1代表read出错了,> 0的代表的是success。

然后是主函数的父进程开始读取数据部分函数,大体思路仍然先关闭掉不需要的文件描述符,读取完之后,需要等待子进程退出,为了收集子进程的退出信息,并且我们可以打印出来:

    //3.父进程开始读取 std::cout << "父进程关闭不需要的fd, 准备接收消息了..." << std::endl;sleep(1);close(pipefd[1]);ProcessFatherRead(pipefd[0]);std::cout << "5s,father close fd" << std::endl;sleep(5);close(pipefd[0]);//4.父进程开始等待子进程int status = 0;pid_t rid = waitpid(id,&status,0);if(rid > 0){std::cout << "wait child process done, exit sig: " << (status&0x7f) << std::endl;std::cout << "wait child process done, exit code(ign): " << ((status>>8)&0xff) << std::endl;}

目前看来是正常写入,但是父进程是否读取到了我们并不知道,所以我们打算让子进程write到一定程度的时候break:

    while(true){std::cout << "+++++++++++++++++++++++++++++++++++++++++++++++++" << std::endl;//得到数据std::string info = message + getOtherMessage();//开始写入数据write(wfd,info.c_str(),info.size());std::cerr << info << std::endl;sleep(1);write(wfd,&charactor,1);std::cout << "pipesize: " << ++pipesize << " write charactor is: " << charactor++ << std::endl;if(charactor == 'H') break;}

此时,子进程退出之后,子进程的状态成功变成了僵尸状态,我们将父进程的sleep时间缩短,准备让父进程进行回收子进程。

匿名管道粗略的到这里吧,,后面等着二刷。


感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/11249.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL数据库:本地部署数据库以及安装彩虹猫【Navicat】

文章目录 一.安装前准备工作1.下载并解压文件2.修复电脑缺失的文件 二.本地部署MySQL1.先解压mysql-8.0.25-winx64.zip&#xff0c;并把文件放到安装需要的位置&#xff0c;再把my.ini文件放到mysql-8.0.25-winx64的根目录2.修改注册表的根目录信息为自己的安装装路径3.进命令符…

计算机网络作业一

一共8次作业&#xff0c;都挺难的&#xff0c;只能在老师的要求下尽力尝试。 任务&#xff1a;探测Internet (IPv4和IPv6) 1. 探测并估计有多少地址是活动的&#xff0c;解释你的方法并估计误差范围 2. 找到尽可能多的关键地址&#xff0c;然后解释它们是什么&#xff0c;为…

联通10010 阿里滑块 231 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我删…

DAY58||110.字符串接龙 |105.有向图的完全可达性 |106.岛屿的周长

110.字符串接龙 110. 字符串接龙 题目描述 字典 strList 中从字符串 beginStr 和 endStr 的转换序列是一个按下述规格形成的序列&#xff1a; 1. 序列中第一个字符串是 beginStr。 2. 序列中最后一个字符串是 endStr。 3. 每次转换只能改变一个字符。 4. 转换过程中的中间字符串…

爬虫补环境案例---问财网(rpc,jsdom,代理,selenium)

目录 一.环境检测 1. 什么是环境检测 2.案例讲解 二 .吐环境脚本 1. 简介 2. 基础使用方法 3.数据返回 4. 完整代理使用 5. 代理封装 6. 封装所有使用方法 jsdom补环境 1. 环境安装 2. 基本使用 3. 添加参数形式 Selenium补环境 1. 简介 2.实战案例 1. 逆向目…

《TCP/IP网络编程》学习笔记 | Chapter 8:域名及网络地址

《TCP/IP网络编程》学习笔记 | Chapter 8&#xff1a;域名及网络地址 《TCP/IP网络编程》学习笔记 | Chapter 8&#xff1a;域名及网络地址域名系统什么是域名&#xff1f;DNS 服务器IP 地址和域名之间的转换使用域名的必要性利用域名获取 IP 地址利用 IP 地址获取域名 基于 Wi…

Liunx:简易版进程池

进程向系统申请资源存在一定的效率问题。系统调用在底层是有成本的。频繁的向操作系统申请资源会造成一定的开销。解决办法是一次性向系统申请你需要的资源&#xff0c;将这些资源在用户层管理维护起来&#xff0c;减少程序频繁的陷入内核。这就是池化的意思&#xff0c;可以简…

百亿AI数字人社会初现:Project Sid展示智能代理文明进化路径

项目背景 Project Sid 是一项开创性的AI代理人文明实验,旨在通过新开发的认知架构 PIANO 探讨AI代理人是否能够在大规模数字社会中实现文明的演进。这项实验不仅展示了社会进步、角色分化、治理体系及文化传播等特征,还揭示了一个包含百亿“数字人类”的社会可能性。 PIANO…

CoCa: Contrastive Captioners are Image-Text Foundation Models

Jiahui Yu† Zirui Wang†{jiahuiyu, ziruiw}google.comVijay Vasudevan Legg Yeung Mojtaba Seyedhosseini Yonghui WuGoogle Research 参考代码链接&#xff1a;https://github.com/lucidrains/CoCa-pytorch 模型效果对比网址&#xff1a;CoCa: Contrastive Captioners are …

HarmonyOS一次开发多端部署三巨头之功能级一多开发和工程级一多开发

功能级一多开发与工程级一多开发 引言功能级一多开发SysCaps机制介绍能力集canlUse接口 工程级一多开发三层架构规范 引言 一次开发多端部署 定义&#xff1a;一套代码工程&#xff0c;一次开发上架&#xff0c;多端按需部署 目标&#xff1a;支撑开发者快速高效的开发多终端设…

c中的文件管理

大家好&#xff0c;今天我们来看看语言中的文件管理&#xff0c;聊到这个&#xff0c;我们就得先说说文件的特点。 1.文件是一种让数据持久化的方法&#xff0c;使用文件可以将数据直接存放在电脑的硬盘上&#xff0c;做到数据持久化。 那么什么是文件呢&#xff1f; 硬盘上…

ElasticSearch的Python Client测试

一、Python环境准备 1、下载Python安装包并安装 https://www.python.org/ftp/python/3.13.0/python-3.13.0-amd64.exe 2、安装 SDK 参考ES官方文档: https://www.elastic.co/guide/en/elasticsearch/client/index.html python -m pip install elasticsearch一、Client 代…

python中常见的8种数据结构之一数组的应用

在Python中&#xff0c;数组是一种常见的数据结构&#xff0c;用于存储一系列相同类型的元素。在实际应用中&#xff0c;数组可以用于解决各种问题。 以下是数组在Python中的一些常见应用&#xff1a; 1. 存储和访问数据&#xff1a;数组可以用于存储和访问一组数据。可以通过…

Android 实现柱形图

在 Android 中实现柱状图&#xff0c;可以使用流行的图表库 MPAndroidChart&#xff0c;它支持多种类型的图表&#xff0c;包括柱状图、折线图、饼图等。下面是一个基本的柱状图实现步骤&#xff0c;具体分为以下几个部分&#xff1a; 1. 添加依赖 首先&#xff0c;你需要在 …

python基础

1.python的第一个程序 2.代码注释 3.交互模式 4.变量与常量 电影文件是有文件类型&#xff1a;MP4&#xff0c;avi 图片文件&#xff1a;jpeg&#xff0c;png&#xff0c;jpg 5.数据类型 python类型决定了当前变量在内存中的存储体积 字符串&#xff0c;字符&a…

13.UE5流星火雨,引导施法技能制作

2-15 流星火雨&#xff0c;引导施法技能制作、随机数_哔哩哔哩_bilibili 目录 1.为流星火雨添加按键映射 2.创建流星火雨的动画蒙太奇 3.实现播放动画蒙太奇的逻辑 ​编辑 4.定义发射一波流星火雨的发射物 5.使用动画通知释放流星火雨 1.为流星火雨添加按键映射 创建名为流…

【python程序】恢复曾经删除的QQ说说

是否还能想起曾经的QQ说说&#xff0c;是否还想知道自己以前删除了什么 今天就给大家介绍下这个可以恢复以前删除的QQ说说的 小工具 这个工具是由python编写的&#xff0c;也已经打包好了小程序&#xff0c;一键运行 具体下载地址&#xff1a;https://pan.quark.cn/s/b3f41e3…

Springboot 整合 Java DL4J 打造企业知识图谱构建系统

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

goroutine 介绍

引子&#xff1a; 线程比如打开腾讯视频然后开始下载多个视频&#xff0c;下载任务就是线程 但是这并不是同时进行的&#xff0c;只是时间片比较短切换的比较快 进程和线程的关系 有些程序可以多进程有些可能不支持 并发和并行 并发和并行的根本区别是&#xff1a;并发在同一时…

Ubuntu[无桌面]——修改Docker镜像源文件

下载镜像的时候&#xff0c;一般有两种方式&#xff1a; &#xff08;1&#xff09;在宿主主机配置相应的文件/etc/docker/daemon.json&#xff0c;配置镜像源环境地址 &#xff08;2&#xff09;进入https://quay.io/search中&#xff0c;输入搜索需要下载的镜像名称&#xff…