Linux相关概念和易错知识点(26)(命名管道、共享内存)

目录

1.命名管道

(1)匿名管道 -> 命名管道

①匿名管道

②命名管道

(2)命名管道的使用

①创建和删除命名管道文件

②命名管道文件的特性

③命名管道和匿名管道的区别

(3)用命名管道实现进程间通信

2.共享内存

(1)共享内存是什么

(2)shmget

①IPC_CREAT

②IPC_EXCL

③key

④shmid

(3)共享内存的生命周期及其释放

①指令释放

②代码释放

(4)shm的关联和去关联

①关联

②去关联

(5)shm申请空间的底层细节

(6)共享内存通信的特点

①优点

②缺点

(7)共享内存的保护

①临界与非临界、互斥同步

②信号量

(8)共享内存的底层


1.命名管道

(1)匿名管道 -> 命名管道

①匿名管道

匿名管道的原理是创建一个内存级文件pipe,让父子进程看到同一块文件缓冲区,利用同缓冲区、不同struct file的特性实现一个进程向缓冲区写入,一个向缓冲区读取。但这有个问题,匿名管道的创建前提就是通信的进程属于父子关系,但有的时候我们需要让不相关的进程进行通信,这应该怎么办呢?这就要引入命名管道了。

②命名管道

进程间通信的根本就是让两个进程看到同一块资源,因此我们需要找到一个让两个不相关的进程能够同时访问的资源。对于命名管道而言,这个公共资源就是一个真实的文件,以路径 + 文件名作为该文件的唯一标识。

(2)命名管道的使用

①创建和删除命名管道文件

创建命名管道文件:int mkfifo(const char *pathname, mode_t mode);

删除命名管道文件:int unlink(const char *pathname)

返回值:成功创建(删除)文件时返回0,失败返回-1并设置错误码

我们可以看到,命名管道文件是一个真实存在的文件,我们可以创建命名管道,通过对这个文件进行读写操作。具体方法就是通过路径 + 文件名操作。

需要注意的一个小细节就是创建、删除文件可以携带路径,通信的两个进程可以在任意位置,通过绝对/相对路径进行定位。

②命名管道文件的特性

为什么要专门设计这种文件?

其一是命名管道文件始终不会刷新写入内容到磁盘,都是写入内核缓冲区,等待读走。因此从效率上将要更快;

其二,就是命名管道文件存在一个特殊处理。即当创建好命名管道文件之后,会有读端和写端打开以进行通信。对于普通文件,读端写端可以直接打开已存在的文件;而对于命名管道文件而言,当一端打开而另一端还没有打开时,先打开的那一端就会被open阻塞,直到另一端打开,两者才会同时打开文件,之后read就会被阻塞(同步互斥保护机制)。这样做的目的是,当读端打开文件后会开始读文件,而如果此时没有写端打开,read会返回0,这就导致读端直接判断为“数据读完了”导致执行剩余代码,反之亦然。

因此普通文件虽然也能实现进程间通信,但会存在效率问题以及读写端时机导致的问题。显然我们离不开专门设计的命名管道文件。

③命名管道和匿名管道的区别

很明显,匿名、命名管道均利用文件内核缓冲区进行数据通信,但是匿名管道是因为父子进程才使得不同进程看到同一份资源,而命名管道是通过路径定位看到同一文件资源,所以进程A和进程B不需要为父子关系。除此之外,两种通信方式的原理一模一样,只有处理细节上的不同。

(3)用命名管道实现进程间通信

由于和匿名管道代码类似,这里我简要分析思路 + 代码展示即可。我们需要做的是先生成管道文件,这部分交给读端(服务端)处理,一般都是服务端等待客户端的接入,后续管道文件的销毁也是服务端处理。

common.hpp

#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>using namespace std;const string filename = "./myfifo";
const int count = 50;//允许通信消息数class myPipe
{
public:myPipe(){umask(0);int create_ret = mkfifo(filename.c_str(), 0600);if (create_ret == 0)cout << "创建命名管道文件成功!" << endl;else{cout << "创建命名管道文件失败!" << endl;exit(1);}}~myPipe(){int unlink_ret = unlink(filename.c_str());if (unlink_ret == 0)cout << "删除命名管道文件成功!" << endl;else{cout << "删除命名管道文件失败!" << endl;exit(1);}}};

client.hpp

#include "common.hpp"class Client
{
public:Client(): _fd(-1){_fd = open(filename.c_str(), O_WRONLY);if (_fd > 0)cout << "客户端通信已连接!" << endl;else{cout << "客户端通信连接失败!" << endl;exit(1);}}int WriteMessage(){string str;getline(cin, str);write(_fd, str.c_str(), str.size());return str.size();//内核缓冲区中字符串结尾不需要\0}~Client(){if (close(_fd) == 0)cout << "通信结束,客户端已断开!" << endl;elsecout << "通信结束,但客户端断开失败!" << endl;}private:int _fd;
};

server.hpp

#include "common.hpp"myPipe mypipe;class Server
{
public:Server(): _fd(-1){_fd = open(filename.c_str(), O_RDONLY);if (_fd > 0)cout << "服务端通信已连接!" << endl;else{cout << "服务端通信连接失败!" << endl;exit(1);}}int ReadMessage(string& str){char arr[100] = { 0 };read(_fd, arr, 99);str = arr;return str.size();//内核缓冲区中字符串结尾不需要\0}~Server(){if (close(_fd) == 0)cout << "通信结束,服务端已断开!" << endl;elsecout << "通信结束,但服务端断开失败!" << endl;}private:int _fd;
};

client.cc


#include "client.hpp"int main()
{Client client;int client_count = count;while (client_count--){printf("客户端输入(剩余通信消息数%d):", client_count + 1);if(client.WriteMessage() == 0)client_count++;}return 0;
}

server.cc


#include "server.hpp"int main()
{Server server;while (1){string str;server.ReadMessage(str);if(str.size() == 0){cout << "客户端已退出且消息已全部接收" << endl;break;}printf("服务端接收信息:");cout << str << endl;}return 0;
}

2.共享内存

(1)共享内存是什么

进程地址空间的共享区除了映射共享库,还可以映射内存空间,这些空间叫共享内存。其原理就是利用共享区偏移量定位的特性让同一块物理空间映射到多个进程的地址空间的共享区,让它们看到同一块空间。

管道通信(文件内核缓冲区)基于文件,共享内存(共享区)属于system V标准。

抓住进程通信本质——让不同进程看到同一份资源。对于system V标准来说,通信的流程为:某个进程创建共享内存,并将它挂接到其它进程的进程地址空间中(堆栈之间)。

和共享内存关联和去关联的过程就是与页表映射和清除映射的过程。共享内存可以存在很多个,需要管理。共享内存 = 共享内存的内核数据结构 + 内存块。通常是由使用的进程来创建共享内存的。

(2)shmget

函数参数:int shmget(key_t key, size_t size, int shmflg),其中size是开辟空间大小

shmflg是宏定义选项,有IPC_CREAT和IPC_EXCL

①IPC_CREAT

创建内存,单独使用时,表示如果shm不存在,就创建,如果已经存在,就直接获取并返回,保证调用进程能拿到共享内存,无论是新建还是已经有了的

②IPC_EXCL

单独使用无意义,需要和IPC_CREAT联合使用。如果shm存在就出错返回,主要用于新建共享内存,函数只要成功,就一定是新的共享内存,不会使用已经创建的共享内

③key

共享内存可以有多个,怎么确定是哪一个共享内存?因此共享内存要有唯一的标识符key。创建共享内存的key必须是用户输入的,因为它是函数参数之一。

但是这个key为什么要用户自己输入,不能系统内核自己生成吗?假设这个key是系统自己生成的,那么当shm的key交给进程A之后,进程B怎么拿到key(进程A和B独立)?因为进程B和shm没有任何关系,和进程A也没关系,所以内核层面无法将shm挂接到进程B上。我们需要用户介入,让用户一开始就自定义key,这样当创建shm后,进程A和B都知道这个key是多少(采用公共的头文件即可实现该操作)。

总的来说,key由用户层去设置更容易管理,但key怎么设置呢?理论上只要不冲突,就可以随便设。如果冲突,就会出错,需要手动改,如果我们不希望自己去设置,可以使用key_t ftok(const char *pathname, int proj_id);,这个函数会根据函数参数自动生成一个唯一值,尽量减少冲突。其中pathname最好填和文件名相关的路径,proj_id也是自己指定,公共的项目ID。函数参数并没有强制要求,只是生成唯一数的一个函数,我们尽量按照其设计来填。ftok成功就返回key,失败返回-1。

我们可以说key的唯一性是由项目ID + 路径实现的,和命名管道(文件路径)有异曲同工之妙,这也是系统的树状存储结构带来的优势。

④shmid

shmid是shmget函数的返回值。shmid不是key,但shmid和key都具有唯一性,两者的区别是key是内核使用的,shmid是只给用户使用的标志shm的标识符。我们只在创建时自定义一次key,后续都使用shmid来管理shm。

这里很难理解,为什么要设置一个shmid,不是我们已经知道key了吗?

我们可以理解为shmid是一个底层的标志,我们用户第一次设置了之后就被系统拿去做唯一标识符了。从安全性、可维护性角度出发,系统直接掌握的东西是不会直接交给用户使用的。就好比物理内存地址和虚拟地址一样,我们设置虚拟地址就是为了让数据存储更易管理,有个中间层要方便、安全得多。再如fd和struct file,我们本来知道struct file的地址即可,却还要设置一个fd作为用户层的管理,也都是有安全性、可维护性的原因的。shmid就是这么诞生的。虽然由于规则的特殊性,我们用户一来就知道了key的值,但由于这个值会直接用于系统的使用,因此还是要一个中间层来进行管理,即shmid。我们直接将shmid理解为fd,key理解为struct file对应的地址即可。

shmid的出现是有理有据的,只不过由于特殊需求,用户知道了key的值,但系统的底层逻辑不允许人们直接拿着这个key去管理内存,这里需要我们花时间理解。

(3)共享内存的生命周期及其释放

共享内存属于系统侧的内存空间,其生命周期随内核,因此它需要手动释放或是OS重启才会回收。相比之下匿名管道随进程,命名管道也要保证随进程的生命周期。

释放共享内存的方式有指令释放、代码释放。

①指令释放

ipcrm -m (shmid)可以释放共享内存,注意这个shmid在进程结束后依然存在,且一直保持唯一。

在命令行中,共享内存的管理指令为ipcs -m,它可查看系统中的共享内存的各种属性(包括shmid)

ipcs -m查看的perms相当于权限,共享内存也有权限,shmget函数的shmflg可以 | mode设置共享内存的读写权限,如int shmget(key_t key, size_t size, int shmflg | 0666)会使得三个组都获得所有权限。perms可以设置一次,如果没有权限,相应进程无法挂接内存,挂接成功后nattch会++,表示引用计数。

②代码释放

参数:int shmctl(int shmid, int cmd, shmid_ds* buf)

shmid_ds类型含有shm的各种属性,cmd是通过位图标识各种命令操作。ipcs -m的底层实现就是使用这个函数。

不同的cmd对应不同操作,IPC_STAT就是拷贝属性到buf,可以以此实现ipcs -m命令,查看对应内存的属性;IPC_RMID就是让对应内存空间标记释放,shmid_ds* buf传nullptr,表示不获取相应的属性。例如shmctl(0, IPC_RMID, nullptr)可以利用代码释放共享内存,这就是ipcrm -m 0的底层代码实现。

(4)shm的关联和去关联

①关联

要使用shm,需要将其挂接到进程地址空间的共享区。

函数参数:void *shmat(int shmid, const void *_Nullable shmaddr, int shmflg);

shmaddr是用户指定挂接到什么虚拟地址,对我们来说直接设置为nullptr即可,表示不指定具体挂接位置。shmflg也是位图,表示访问权限,这里我们可以直接设置为0。shmat的返回值是虚拟起始地址,由返回值 + 偏移量我们即可完全访问这块空间,这和malloc类似,shmat挂接错误的话返回(void*)-1。

我们发现挂接后得到的返回值就相当于malloc的返回值,我们可以强转指针类型为不同类型,对这片空间进行不同方式的访问,如此这样这块公共空间就利用起来了。

②去关联

函数参数:int shmdt(const void *shmaddr);

只要知道共享内存的虚拟起始地址,就能取消映射,对应的内存空间引用计数nattch--。

通信的过程就在shmat和shmdt函数的调用之间。使用和malloc一模一样。

(5)shm申请空间的底层细节

使用shmget申请空间的过程中,操作系统是按照页为单位申请的:1KB、2KB、4MB等。操作系统会根据用户的需要来给内存,但其底层可能会浪费。

(6)共享内存通信的特点

①优点

当挂接好之后,我们拿着共享内存的虚拟地址就能直接往内存读写数据,因此共享内存速度最快。另外,从拷贝角度上讲,共享内存拷贝的次数最少,当拷贝数据到共享内存时,两个进程就能马上看到数据。

②缺点

两个进程在各自用户空间共享内存块,和malloc一样可以自由使用。但由于这块共享内存没有加任何保护机制,需要用户自己完成共享内存的保护——信号量、命名管道等。

(7)共享内存的保护

①临界与非临界、互斥同步

共享资源大多都要被保护,这些叫临界资源。常见保护方式有互斥、同步。任何时刻只允许进行一个执行流访问的资源,叫做互斥,即系统中某些资源一次只允许一个人使用。同步是指多个执行流访问临界资源时必须要有一定的顺序。

在进程中代码涉及到临界资源,这部分代码就叫临界区。代码 = 临界区的代码 + 非临界区的代码,临界区访问临界资源,非临界区访问非临界资源。对共享资源进行保护的本质是对访问共享资源的代码进行保护,就是保护临界区的代码。

②信号量

信号和信号量没有任何关系。

信号量的特性是IPC(系统资源),必须手动删除。信号量本质是一个计数器,这个计数器可以是一个位图、数字。我们将资源分成不同块,不同进程都使用同一块空间的不同部分,针对每一个小块,保证互斥访问,但当一个数据块读时,另一个在写,整体上看并行度提高。

从一个例子出发,当我们买了电影票,即便我们不去,这个位置也是留给我们的。并且从不会多卖出去票,10个座位不会卖出11张票。我们可以理解,买票的本质是对资源的预定机制,进程也是!

信号量就是一个计数器,这个计数器也是资源的预定。多个进程划分资源都是抢计数器而不是直接去抢空间。信号量、信号灯都是对资源进行预定的计数器。二元信号量,1表示被使用,0没被使用。要实现信号量,我们可以用位图标识每块资源的使用情况,也可以直接用数字count统计剩余资源的个数,总之需要保证内部划分合理且不会出现冲突。

(8)共享内存的底层

共享内存本质也是文件,这个文件必须被映射到进程地址空间。共享内存文件创建后,vm_area_struct会进行映射,把文件映射到内存中了。和动态库一样是内存都是被映射到文件中的。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【时时三省】(C语言基础)对比一组函数

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 对比一组函数 比如对比一下scanf fscanf sscanf和printf fprintf sprintf scanf 针对标准输入的格式化的输入语句 其实它针对的是stdin fscanf 针对所有输入流的格式化的输入语句 它是针对s…

Leecode刷题C语言之组合总和②

执行结果:通过 执行用时和内存消耗如下&#xff1a; int** ans; int* ansColumnSizes; int ansSize;int* sequence; int sequenceSize;int** freq; int freqSize;void dfs(int pos, int rest) {if (rest 0) {int* tmp malloc(sizeof(int) * sequenceSize);memcpy(tmp, seque…

汽车网络信息安全-ISO/SAE 21434解析(下)

目录 第十二~十四章 - 后开发阶段 1. 十二章节 - 生产 2. 十三章节 - 运营与维护 网络安全事件响应 更新 3. 十四章节 - 结束网络安全支持和停用 结束网络安全支持 报废 第十五章 - TARA分析方法 1. 概述 2. 资产识别 3. 威胁场景识别 4. 影响评级 5. 攻击路径分…

[java] java基础-字符串篇

目录 API String 创建字符串对象的两种方式&#xff1a; Java的内存模型 字符串常量池&#xff08;串池&#xff09;存放地址 两种构造方法的内存分析 String的常用方法 号比较的是什么 字符串比较&#xff08;比较字符串的数据值&#xff09; 遍历字符串 StringBui…

Unity中关于实现 管道水流+瀑布流动+大肠蠕动效果笔记

Unity中关于实现 管道水流瀑布流动大肠蠕动效果笔记 效果展示&#xff1a; 参考资料及链接&#xff1a; 1、如何在 Unity 中创建水效果 - 水弯曲教程 https://www.youtube.com/watch?v3CcWus6d_B8 关于补充个人技能中&#xff1a;顶点噪波影响网格着色器配合粒子实现水特效 …

Couchbase UI: Indexes

在Couchbase中&#xff0c;索引的这些指标可以帮助你评估索引的性能和状态。下面是每个指标的详细解释&#xff0c;以及如何判断索引的有效性&#xff1a; 1. Index Name&#xff08;索引名称&#xff09; 描述&#xff1a;每个索引都有一个唯一的名称。这个名称通常会包括表…

redis的分片集群模式

redis的分片集群模式 1 主从哨兵集群的问题和分片集群特点 主从哨兵集群可应对高并发写和高可用性&#xff0c;但是还有2个问题没有解决&#xff1a; &#xff08;1&#xff09;海量数据存储 &#xff08;2&#xff09;高并发写的问题 使用分片集群可解决&#xff0c;分片集群…

第一届“启航杯”网络安全挑战赛WP

misc PvzHE 去这个文件夹 有一张图片 QHCTF{300cef31-68d9-4b72-b49d-a7802da481a5} QHCTF For Year 2025 攻防世界有一样的 080714212829302316092230 对应Q 以此类推 QHCTF{FUN} 请找出拍摄地所在位置 柳城 顺丰 forensics win01 这个软件 云沙盒分析一下 md5 ad4…

ThreadLocal概述、解决SimpleDateFormat出现的异常、内存泄漏、弱引用、remove方法

①. ThreadLocal简介 ①. ThreadLocal是什么 ①. ThreadLocal本地线程变量,线程自带的变量副本(实现了每一个线程副本都有一个专属的本地变量,主要解决的就是让每一个线程绑定自己的值,自己用自己的,不跟别人争抢。通过使用get()和set()方法,获取默认值或将其值更改为当前线程…

蓝桥杯模拟算法:多项式输出

P1067 [NOIP2009 普及组] 多项式输出 - 洛谷 | 计算机科学教育新生态 这道题是一道模拟题&#xff0c;我们需要分情况讨论&#xff0c;我们需要做一下分类讨论 #include <iostream> #include <cstdlib> using namespace std;int main() {int n;cin >> n;for…

LLM - 大模型 ScallingLaws 的设计 100B 预训练方案(PLM) 教程(5)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/145356022 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 Scalin…

深度解析:基于Vue 3的教育管理系统架构设计与优化实践

一、项目架构分析 1. 技术栈全景 项目采用 Vue 3 TypeScript Tailwind CSS 技术组合&#xff0c;体现了现代前端开发的三大趋势&#xff1a; 响应式编程&#xff1a;通过Vue 3的Composition API实现细粒度响应 类型安全&#xff1a;约60%的组件采用TypeScript编写 原子化…

计算机组成原理(2)王道学习笔记

数据的表示和运算 提问&#xff1a;1.数据如何在计算机中表示&#xff1f; 2.运算器如何实现数据的算术、逻辑运算&#xff1f; 十进制计数法 古印度人发明了阿拉伯数字&#xff1a;0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#…

(详细)Springboot 整合动态多数据源 这里有mysql(分为master 和 slave) 和oracle,根据不同路径适配不同数据源

文章目录 Springboot 整合多动态数据源 这里有mysql&#xff08;分为master 和 slave&#xff09; 和oracle1. 引入相关的依赖2. 创建相关配置文件3. 在相关目录下进行编码&#xff0c;不同路径会使用不同数据源 Springboot 整合多动态数据源 这里有mysql&#xff08;分为maste…

AI如何帮助解决生活中的琐碎难题?

引言&#xff1a;AI已经融入我们的日常生活 你有没有遇到过这样的情况——早上匆忙出门却忘了带钥匙&#xff0c;到了公司才想起昨天的会议资料没有打印&#xff0c;或者下班回家还在纠结晚饭吃什么&#xff1f;这些看似微不足道的小事&#xff0c;往往让人疲惫不堪。而如今&a…

一分钟搭建promehteus+grafana+alertmanager监控平台

为什么要自己搭建一个监控平台 平时进行后端开发&#xff0c;特别是微服务的后端可开发&#xff0c;一定少不了对接监控平台&#xff0c;但是平时进行一些小功能的测试又没有必要每次都手动安装那么多软件进行一个小功能的测试&#xff0c;这里我使用docker-compose搭建了一个…

深入MapReduce——计算模型设计

引入 通过引入篇&#xff0c;我们可以总结&#xff0c;MapReduce针对海量数据计算核心痛点的解法如下&#xff1a; 统一编程模型&#xff0c;降低用户使用门槛分而治之&#xff0c;利用了并行处理提高计算效率移动计算&#xff0c;减少硬件瓶颈的限制 优秀的设计&#xff0c…

前端【10】jQuery DOM 操作

目录 jquery捕获查取 获得内容 - text()、html() 以及 val() 获取属性 - attr() ​编辑 jQuery 修改/设置内容和属性 设置内容 - text()、html() 以及 val() 设置属性 - attr() jQuery添加元素 jQuery - 删除元素 前端【9】初识jQuery&#xff1a;让JavaScript变得更简…

进程控制的学习

目录 1.进程创建 1.1 fork函数 1.2 fork函数返回值 1.3 写时拷贝 1.4 fork 常规用法 1.5 fork 调用失败的原因 2. 进程终止 2.1 进程退出场景 2.2 进程常见退出方法 2.2.1 从main 返回 2.2.2 echo $&#xff1f; 查看进程退出码 2.2.2.1 我们如何得到退出码代表的含…

数据结构与算法分析:专题内容——人工智能中的寻路7之AlphaBeta(代码详解)

一、算法描述 在考虑到对手的可能走法之后&#xff0c;Minimax算法能够较为恰当地找出玩家的最优走法。但是&#xff0c;在生成博弈树时&#xff0c;这个信息却没有使用&#xff01;我们看看早先介绍的BoardEvaluation评分函数。回忆一下下图Minimax的探测&#xff1a; 这是从…