【Linux】IPC:匿名管道、命名管道、共享内存

头像
⭐️个人主页:@小羊
⭐️所属专栏:Linux
很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

动图描述

目录

  • 1、管道
  • 2、进程池
  • 3、命名管道
  • 4、共享内存


1、管道

我们知道进程具有独立性,但是在一些场景中进程间也需要通信,那怎么实现进程间的通信呢?

进程间通信的核心是:由OS提供一份公共的内存资源。

进程间通过文件的内核缓冲区实现资源共享,这个过程并不需要磁盘参与,所以设计了一种内存级的文件来专门实现进程间通信,这个内存级文件就是管道。
什么是管道?

  • 管道是Unix中最古老的进程间通信的形式
  • 从一个进程连接到另一个进程的一个数据流称为一个“管道”

管道的原理:

管道只能进行单向通信。

必须要先打开文件,再创建子进程,不能先创建子进程,再打开文件。这个过程利用的是子进程会继承父进程相关资源的特性。

  • 为什么父进程打开文件的时候必须要以“读写”方式打开,不能只读或只写?
    因为父进程打开文件,创建子进程后,父子进程必须有一个写,一个读,不能两个都读或两个都写。

管道不需要路径,也就不需要名字,所以叫做匿名管道。

在这里插入图片描述

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

int main()
{
    //1、创建管道
    int fds[2] = {0};
    int n = pipe(fds);
    if (n != 0)
    {
        cerr << "pipe error" << endl;
        return 1;
    }

    //2、创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        cerr << "fork error" << endl;
        return 2;
    }
    else if (id == 0)
    {
        //子进程
        //3、关闭不需要的fd
        close(fds[0]);//0是读


        exit(0);
    }
    else
    {
        //父进程
        close(fds[1]);//1是写

        pid_t rid = waitpid(id, nullptr, 0);
        if (rid > 0)
        {
            cout << "father wait child success, id :" << rid << endl;
        }
    }
    return 0;
}

上面的操作只是让父子进程看到了同一份资源,但还没有实现通信。这个内存资源有OS提供,所以进程间通信也理应通过操作系统实现,也就是调用系统调用。

	//...
    else if (id == 0)
    {
        //子进程
        //3、关闭不需要的fd
        close(fds[0]);//0是读
        int cnt = 0;
        while (true)
        {
            string message = "hello world, hello ";
            message += to_string(getpid());
            message += ", ";
            message += to_string(cnt++);
            write(fds[1], message.c_str(), message.size());
            sleep(1);
        }
        exit(0);
    }
    else
    {
        //父进程
        close(fds[1]);//1是写
        char buffer[1024];
        while (true)
        {
            ssize_t n = read(fds[0], buffer, 1024);
            if (n > 0)
            {
                buffer[n] = 0;
                cout << "child->father, message: " << buffer << endl;
            }
        }
        pid_t rid = waitpid(id, nullptr, 0);
        if (rid > 0)
        {
            cout << "father wait child success, id :" << rid << endl;
        }
    }
    //...
  • 子进程写,父进程读。看待父子进程,就像看待文件一样。

在上面子进程sleep的过程中,父进程在做什么呢?在阻塞等待。
父进程在读完了子进程的数据后,OS就不要父进程读了,让其进入阻塞状态,等待子进程再次写入。这是为了保护共享资源,防止子进程写了一半父进程就读,或者父进程读了一半子进程就写。这个过程是管道内部自己做的。

现象:

  1. 管道为空&&管道正常,read会阻塞(read是一个系统调用)。
  2. 管道为满(管道资源是有限的)&&管道正常,write会阻塞。
  3. 管道写端关闭&&读端继续,读端读到0,表示读到文件结尾。
  4. 管道读端关闭&&写端继续,OS杀掉写入的进程。

在这里插入图片描述

特性:

  1. 面向字节流。不关心对面是如何写的,按需读取。
  2. 用来进行具有血缘关系的进程,进行IPC,常用于父子。
  3. 文件的声明周期随进程,管道也是。
  4. 单向数据通信。
  5. 管道自带同步互斥等保护机制。

2、进程池

退出进程池
当关闭写端,读端读到0,表示读到文件结尾,则结束进程。即将父进程所有的读端关闭,则相应的子进程就会结束,最后再由父进程等待回收。

void CleanProcessPool()
{
    //virsion1
    for (auto &c : _channels)
    {
        c.Close();
    }
    for (auto &c : _channels)
    {
        pid_t rid = waitpid(c.GetId(), nullptr, 0);
        if (rid > 0)
        {
            cout << "child: " << rid << "wait...success" << endl;
        }
    }
}

:上面关闭读端和等待子进程为什么要分开,关一个等待一个行吗?

在这里插入图片描述

根据上面的分析,所有的子进程的file_struct都会指向第一个管道,越往后的子进程指向的管道越多。所以我们只是把masterfile_struct中指向管道关闭,这个管道还有其他子进程的file_struct指向,因此读端不会读到0,子进程不会退出,就会一直阻塞。解决这个问题有两种办法:

1、倒着关闭
因为通过分析可知,越早创建的管道指向越多,最后一个管道只被指向一次,只要将最后一个进程关闭,则前面的所有管道被指向都会少1,因此倒着关闭就不会出现阻塞的问题。

//virsion2
for (int i = _channels.size()-1; i >= 0; i--)
{
    _channels[i].Close();
    pid_t rid = waitpid(_channels[i].GetId(), nullptr, 0);
    if (rid > 0)
    {
        cout << "child: " << rid << "wait...success" << endl;
    }
}

2、在子进程中关闭所有历史fd
因为父进程的3号文件描述符总为空,子进程只有3号文件描述符指向管道。在这之前子进程继承父进程对之前的管道的指向,所以只需要在子进程中把这些指向全部关掉就行。

在这里插入图片描述

// 3、建立通信信道
if (id == 0)
{
	//关闭历史fd
	for (auto &c : _channels)
	{
	    c.Close();
	}
	// 子进程
	//close(pipefd[1]);
	//dup2(pipefd[0], 0); // 子进程从标准输入读取
	//_work();
	//exit(0);
}

3、命名管道

我们知道了,匿名管道的原理,是让父子进程看到同一份资源,而父子进程看到同一份资源,是因为子进程继承了父进程的资源。所以不难得出,匿名管道两端必须是父子进程。而如果我们想在任意进程之间建立管道呢?首先可以肯定的是这任意两个进程之间也要能看到同一份资源,因为是任意进程之间所以这个资源不能继承而来,所以就牵扯出了命名管道

匿名管道是内存级的虚拟文件,而命名管道是真实存在的文件。

在这里插入图片描述
在这里插入图片描述

可以看到管道文件fifo的大小依旧为0,所以两个进程间通信的数据并没有刷新保存到磁盘中。

  • 命名管道的原理:
    为什么叫做命名管道,因为有名字,是真实存在的文件,既然是真实存在的文件,就一定有路径+文件名,而路径+文件名具有唯一性。这样不同的进程可以用同一个文件系统路径标志同一个资源,也就是不同的进程看到了同一个资源。

  • 命名管道和普通文件的区别:
    这么看来命名管道和普通文件好像除了创建方式不同外也没多大区别,而普通文件好像也能实现进程间通信,但是普通文件有两个问题,我们往普通文件中写入的数据会被刷新到磁盘中保存,另外普通文件也没有被特殊保护,也就是我们可以往里写大量的数据,在写的过程中也有可能被其他进程读,这两个问题是命名管道需要重点处理的,所以命名管道和普通文件有很大的区别,是特殊设计的。

  • 这个命名管道,该由谁创建?
    公共资源:一般要让指定的一个进程现行创建。一个进程创建&&使用,另一个进程获取&&使用。


4、共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
在这里插入图片描述

  • 共享内存 = 共享内存的内核数据结构 + 内存块。

  • 让两个进程通过各自的虚拟地址空间,映射同一块物理内存,叫做共享内存。共享内存的本质还是让不同的进程看到同一个资源。

在这里插入图片描述

  • IPC_CEEAT:单独使用,如果shm不存在则创建,如果存在则获取。保证调用进程就能拿到共享内存。
  • IPC_CEEAT | IPC_EXCL:组合使用,如果不存在则创建,如果存在则返回错误。只要成功,一定是新的共享内存。

key为什么必须要用户传入,为什么内核自己不生成?

  1. 任意进程间是独立的,由某一个进程内生成key,其他的进程是拿不到的。
  2. 理论上用户可以随意设置key,只要保证不冲突就可,为了保证key的唯一性有函数来减小冲突的概率。

在这里插入图片描述

定义全局的key,让进程间通过绝对路径都能看到,由某个进程设置进内核中,则其他进程也能够得到。
所以在应用层面,不同进程看到同一份共享内存是通过唯一路径+项目ID来确定的,类似命名管道也是通过文件路径+文件名来确定的。

在这里插入图片描述

在OS看来,由shmget函数创建的共享内存是OS创建的,所以共享内存的生命周期随内核。 和文件不同,文件的生命周期随进程。所以共享内存一旦创建出来,要么由用户主动释放,要么OS重启。

共享内存的管理指令:

  • ipcs -m:查看共享内存信息
  • ipcrm -m shmid:删除共享内存

需要注意的是,删除共享内存只能通过shmid删除,不能通过key删除。

shmid VS key:

  1. shmid:仅供用户使用的shm标识符(类似文件描述符fd)
  2. key:仅供内核区分不同shm唯一性的标识符(类似文件地址)

除了指令删除shm,还可以通过函数删除:

在这里插入图片描述

共享内存也有权限。

栈区、堆区、共享区等地址空间,是用户空间,我们不需要调用系统调用就可以直接使用。

| 共享内存的特点:

  • 不需要调用系统调用,通信速度快。
  • 让两个进程在各自的用户空间共享内存块,是真正的共享资源,但是不像管道,共享内存没有任何保护。
  • 共享内存的保护机制,需要用户自己完成。

在这里插入图片描述


本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

头像

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

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

相关文章

Pyside6(PyQT5)中的QTableView与QSqlQueryModel、QSqlTableModel的联合使用

QTableView 是QT的一个强大的表视图部件&#xff0c;可以与模型结合使用以显示和编辑数据。QSqlQueryModel、QSqlTableModel 都是用于与 SQL 数据库交互的模型,将二者与QTableView结合使用可以轻松地展示和编辑数据库的数据。 QSqlQueryModel的简单应用 import sys from PySid…

DeepSeek学术题目选择效果怎么样?

论文选题 一篇出色的论文背后&#xff0c;必定有一个“智慧的选题”在撑腰。选题足够好文章就能顺利登上高水平期刊&#xff1b;选题不行再精彩的写作也只能“当花瓶”。然而许多宝子们常常忽视这个环节&#xff0c;把大量时间花在写作上&#xff0c;选题时却像抓阄一样随便挑一…

Linux的权限和一些shell原理

目录 shell的原理 Linux权限 sudo命令提权 权限 文件的属性 ⽂件类型&#xff1a; 基本权限&#xff1a; chmod改权限 umask chown 该拥有者 chgrp 改所属组 最后&#xff1a; 目录权限 粘滞位 shell的原理 我们广义上的Linux系统 Linux内核Linux外壳 Linux严格…

【HarmonyOS之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(一)

目录 1 -> 概述 1.1 -> 整体架构 2 -> 文件组织 2.1 -> 目录结构 2.2 -> 文件访问规则 2.3 -> 媒体文件格式 3 -> js标签配置 3.1 -> pages 3.2 -> window 3.3 -> 示例 4 -> app.js 4.1 -> 应用生命周期 4.2 -> 应用对象6…

计算机的错误计算(二百二十二)

摘要 利用大模型化简计算 实验表明&#xff0c;虽然结果正确&#xff0c;但是&#xff0c;大模型既绕了弯路&#xff0c;又有数值计算错误。 与前面相同&#xff0c;再利用同一个算式看看另外一个大模型的化简与计算能力。 例1. 化简计算摘要中算式。 下面是与一个大模型的…

Ubuntu介绍、与centos的区别、基于VMware安装Ubuntu Server 22.04、配置远程连接、安装jdk+Tomcat

目录 ?编辑 一、Ubuntu22.04介绍 二、Ubuntu与Centos的区别 三、基于VMware安装Ubuntu Server 22.04 下载 VMware安装 1.创建新的虚拟机 2.选择类型配置 3.虚拟机硬件兼容性 4.安装客户机操作系统 5.选择客户机操作系统 6.命名虚拟机 7.处理器配置 8.虚拟机内存…

基于单片机的智能小区门禁系统设计(论文+源码)

1总体架构 智能小区门禁系统以STM32单片机和WiFi技术为核心&#xff0c;STM32单片机作为主控单元&#xff0c;通过WiFi模块实现与手机APP的连接&#xff0c;构建整个门禁系统。系统硬件包括RFID模块、指纹识别模块、显示屏、按键以及继电器。通过RFID绑定IC卡、APP面部识别、指…

03_使用同一个函数创建不同的任务

一、声明 这个程序执行的任务就是在一个函数里面可以执行几个不同的任务&#xff08;好吧&#xff0c;我到现在也没学会怎么添加自己的视频&#xff09; 我们这个程序使用到的外设只有OLED屏幕 二、CubeMx的配置 注意要选一下TIM4 挂一个I2C&#xff0c;用来放OLED的屏幕 再开…

高频 SQL 50 题(基础版)_620. 有趣的电影

高频 SQL 50 题&#xff08;基础版&#xff09;_620. 有趣的电影 一级目录 表&#xff1a;cinema id 是该表的主键(具有唯一值的列)。 每行包含有关电影名称、类型和评级的信息。 评级为 [0,10] 范围内的小数点后 2 位浮点数。 编写解决方案&#xff0c;找出所有影片描述为 …

iOS开发设计模式篇第二篇MVVM设计模式

目录 一、什么是MVVM 二、MVVM 的主要特点 三、MVVM 的架构图 四、MVVM 与其他模式的对比 五、如何在iOS中实现MVVM 1.Model 2.ViewModel 3.View (ViewController) 4.双向绑定 5.文中完整的代码地址 六、MVVM 的优缺点 1.优点 2.缺点 七、MVVM 的应用场景 八、结…

PyCharm接入DeepSeek实现AI编程

目录 效果演示 创建API key 在PyCharm中下载CodeGPT插件 配置Continue DeepSeek 是一家专注于人工智能技术研发的公司&#xff0c;致力于开发高性能、低成本的 AI 模型。DeepSeek-V3 是 DeepSeek 公司推出的最新一代 AI 模型。其前身是 DeepSeek-V2.5&#xff0c;经过持续的…

基于自然语言处理的垃圾短信识别系统

基于自然语言处理的垃圾短信识别系统 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 设计题目设计目的设计任务描述设计要求输入和输出…

类和对象(4)——多态:方法重写与动态绑定、向上转型和向下转型、多态的实现条件

目录 1. 向上转型和向下转型 1.1 向上转型 1.2 向下转型 1.3 instanceof关键字 2. 重写&#xff08;overidde&#xff09; 2.1 方法重写的规则 2.1.1 基础规则 2.1.2 深层规则 2.2 三种不能重写的方法 final修饰 private修饰 static修饰 3. 动态绑定 3.1 动态绑…

JavaScript使用toFixed保留一位小数的踩坑记录:TypeError: xxx.toFixed is not a function

JavaScript的toFixed函数是用于将一个数字格式化为指定的小数位数的字符串。其语法如下: numObj.toFixed([digits]) 其中,numObj是需要格式化的数字,digits是保留的小数位数。digits参数是一个可选参数,默认值为0,表示不保留小数位。 计算后需要保留一位小数,于是使用…

网络仿真工具Core环境搭建

目录 安装依赖包 源码下载 Core安装 FAQ 下载源码TLS出错误 问题 解决方案 找不到dbus-launch 问题 解决方案 安装依赖包 调用以下命令安装依赖包 apt-get install -y ca-certificates git sudo wget tzdata libpcap-dev libpcre3-dev \ libprotobuf-dev libxml2-de…

深入 Rollup:从入门到精通(三)Rollup CLI命令行实战

准备阶段&#xff1a;初始化项目 初始化项目&#xff0c;这里使用的是pnpm&#xff0c;也可以使用yarn或者npm # npm npm init -y # yarn yarn init -y # pnpm pnpm init安装rollup # npm npm install rollup -D # yarn yarn add rollup -D # pnpm pnpm install rollup -D在…

volatile之四类内存屏障指令 内存屏障 面试重点 底层源码

目录 volatile 两大特性 可见性 有序性 总结 什么是内存屏障 四个 CPU 指令 四大屏障 重排 重排的类型 为什么会有重排&#xff1f; 线程中的重排和可见性问题 如何防止重排引发的问题&#xff1f; 总结 happens-before 和 volatile 变量规则 内存屏障指令 写操作…

力扣算法题——11.盛最多水的容器

目录 &#x1f495;1.题目 &#x1f495;2.解析思路 本题思路总览 借助双指针探索规律 从规律到代码实现的转化 双指针的具体实现 代码整体流程 &#x1f495;3.代码实现 &#x1f495;4.完结 二十七步也能走完逆流河吗 &#x1f495;1.题目 &#x1f495;2.解析思路…

RK3568 adb使用

文章目录 一、adb介绍**ADB 主要功能****常用 ADB 命令****如何使用 ADB****总结** 二、Linux下载adb**方法 1&#xff1a;使用包管理器&#xff08;适用于 Ubuntu/Debian 系统&#xff09;****方法 2&#xff1a;通过 Snap 安装&#xff08;适用于支持 Snap 的系统&#xff09…

【ES实战】治理项之索引模板相关治理

索引模板治理 文章目录 索引模板治理问题现象分析思路操作步骤问题程序化方案索引与索引模板增加分片数校验管理 彩蛋如何查询Flink on Yarn 模式下的Task Manager日志相关配置查询已停止的Flink任务查询未停止的Flink任务 问题现象 在集群索引新建时&#xff0c;索引的分片比…