初识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++;
    //子进程的pid
    pid_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时间缩短,准备让父进程进行回收子进程。

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


感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux(CentOS)设置防火墙开放8080端口,运行jar包,接收请求

1、查看防火墙状态 systemctl status firewalld 防火墙开启状态 2、运行 jar 包&#xff0c;使用8080端口 程序正常启动 3、使用 postman 发送请求&#xff0c;失败 4、检查端口是否开放&#xff08;需更换到 root 用户&#xff09; firewall-cmd --zonepublic --query-por…

window11安装elasticsearch+Kibana

1、下载elasticsearch与elasticsearch 下载elasticsearch 查看elasticsearch对应的Kibana版本 下载elasticsearch解压后文件目录如下 可执行脚本文件,包括启动elasticsearch服务、插件管理、函数命令等 bin配置文件目录,如elasticsearch配置、角色配置、jvm配置等 conf 默认…

[单例模式]

[设计模式] 设计模式是软件工程中的一种常见做法, 它可以理解为"模板", 是针对一些常见的特定场景, 给出的一些比较好的固定的解决方案. 不同语言适用的设计模式是不一样的. 这里我们接下来要谈到的是java中典型的设计模式. 而且由于设计模式比较适合有一定编程经…

MethodChannel插件的用法

文章目录 1 知识回顾2 示例代码3 经验总结我们在上一章回中介绍了通道相关的内容,本章回中将介绍其中的一种通道:MethodChannnel.闲话休提,让我们一起Talk Flutter吧。 1 知识回顾 我们在上一章回中介绍了通道的概念和作用,并且提到了通道有不同的类型,本章回将其中一种通…

Golang | Leetcode Golang题解之第554题砖墙

题目&#xff1a; 题解&#xff1a; func leastBricks(wall [][]int) int {cnt : map[int]int{}for _, widths : range wall {sum : 0for _, width : range widths[:len(widths)-1] {sum widthcnt[sum]}}maxCnt : 0for _, c : range cnt {if c > maxCnt {maxCnt c}}retur…

通讯录(C 语言)

目录 一、通讯录设计思路1. 伪代码设计思路2. 代码设计思路 二、代码实现三、程序运行演示四、整体分析 一、通讯录设计思路 1. 伪代码设计思路 通讯录可以用来存储 100 个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址。 提供方法&#x…

深入解析四种核心网络设备:集线器、桥接器、路由器和交换机

计算机网络系列课程《网络核心设备》 在现代网络技术中&#xff0c;集线器、桥接器、路由器和交换机扮演着至关重要的角色。本文&#xff0c;将深入探讨这四种设备的功能、工作原理及其在网络架构中的重要性。 集线器&#xff1a;基础网络连接设备 集线器&#xff08;Hub&…

01 Oracle 数据库存储结构深度解析:从数据文件到性能优化的全链路探究

文章目录 Oracle 数据库存储结构深度解析&#xff1a;从数据文件到性能优化的全链路探究一、Oracle存储结构的物理层次1.1 控制文件&#xff08;Control File&#xff09;1.2 联机重做日志文件&#xff08;Online Redo Log File&#xff09;1.3 数据文件&#xff08;Data File&…

Type-C转DP线方案

在现代数字化生活中&#xff0c;高清视频传输已成为日常需求的重要组成部分。无论是工作中的多屏协作&#xff0c;还是娱乐中的沉浸式体验&#xff0c;高清显示器都扮演着不可或缺的角色。然而&#xff0c;随着设备接口的多样化&#xff0c;如何高效地将Type-C设备连接至Displa…

【c++篇】:栈、队列、优先队列:容器世界里的秩序魔法 - stack,queue与priority_queue探秘

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;c篇–CSDN博客 文章目录 前言一.容器stack1.介绍2.成员函数3.模拟实现4.注意事项 二.容器qu…

Java基础——循环switch大数值更改器访问器深浅拷贝

目录 1.循环 2.switch多分支选择结构 3.大数值 4.浅拷贝&深拷贝 5.Arrays.sort排序 6.面向对象 7.更改器&访问器 1.循环 for-each循环 在 Java 中&#xff0c;for-each 循环&#xff08;也称为增强型 for 循环&#xff09;是一种用于遍历数组或集合&#xff08…

引入 axios,根据 api 文档生成调用接口

起步 | Axios Docs 安装 axios npm install axios 生成 api 调用接口【可选】 https://github.com/ferdikoomen/openapi-typescript-codegen 安装 npm install openapi-typescript-codegen --save-dev 然后执行生成代码 # http://localhost:8805/api/user/v3/api-docs&a…

wsl2+Ubuntu安装图形化界面(VNC方式)

本章教程,记录在wsl2+Ubuntu操作系统中安装可视化界面步骤。 一、安装桌面环境 尽量以root用户进行安装,避免因权限不足导致部分依赖无法安装。 sudo apt update sudo apt upgrade -y sudo apt install ubuntu-desktop由于可视化桌面程序安装包比较大,网速慢的小伙伴,需要耐…

Linux应用——线程池

1. 线程池要求 我们创建线程池的目的本质上是用空间换取时间&#xff0c;而我们选择于 C 的类内包装原生线程库的形式来创建&#xff0c;其具体实行逻辑如图 可以看到&#xff0c;整个线程池其实就是一个大型的 CP 模型&#xff0c;接下来我们来完成它 2. 整体模板 #pragma …

学习日记_241110_局部线性嵌入(Locally Linear Embedding, LLE)

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

【我的 Anti-AV 学习手札】DLL注入

无敌舍友s神免杀学了一个阶段&#xff0c;达者为师&#xff0c;向s师傅学习&#xff01;&#xff01; ps&#xff1a;我的基础实在薄弱&#xff0c;WIN编程甚至都没做过&#xff0c;所以笔记翔实些 一、注入思路 1.在进程中开辟一段空间 2.存入dll绝对路径地址的字符串 3.使用…

【HarmonyOS NEXT】一次开发多端部署(以轮播图、Tab栏、列表为例,配合栅格布局与媒体查询,进行 UI 的一多开发)

关键词&#xff1a;一多、响应式、媒体查询、栅格布局、断点、UI 随着设备形态的逐渐增多&#xff0c;应用界面适配也面临着很大问题&#xff0c;在以往的安卓应用开发过程中&#xff0c;往往需要重新开发一套适用于大屏展示的应用&#xff0c;耗时又耗力&#xff0c;而鸿蒙提供…

linux 安装 mongodb

选择MongoDB版本 https://www.mongodb.com/try/download/community-kubernetes-operator 我的系统是centos7.9 这里只能最高只能选择mongo7 复制下载链接&#xff1a;https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-7.0.15.tgz 获取安装教程&#xff1a; h…

《深入浅出Apache Spark》系列②:Spark SQL原理精髓全解析

导读&#xff1a;SQL 诞生于 20 世纪 70 年代&#xff0c;至今已有半个世纪。SQL 语言具有语法简单&#xff0c;低学习门槛等特点&#xff0c;诞生之后迅速普及与流行开来。由于 SQL 具有易学易用的特点&#xff0c;使得开发人员容易掌握&#xff0c;企业若能在其计算机软件中支…

PostgreSQL pg-xact(clog)目录文件缺失处理

一、 背景 前些天晚上突然收到业务反馈&#xff0c;查询DB中的一个表报错 Could not open file "pg-xact/005E": No such file or directory. 两眼一黑难道是文件损坏了...登录查看DB日志&#xff0c;还好没有其他报错&#xff0c;业务也反馈只有这一个表在从库查询报…