一、命名管道

1. 命名管道的原理

有了匿名管道,理解命名管道就非常简单了。

对于普通文件而言两个进程打开同一个文件,OS是不会将文件加载两次的,这两个进程都会指向同一个文件,那么,也就享有同一份 inode 和 文件缓冲区。这在一定层面上也是实现了进程之间共享数据。但是,普通文件是需要向磁盘上进行刷新的

所以,才有了一种特殊的文件管道,管道(包含匿名管道和命名管道)是不需要向磁盘上进行刷新的,命名管道是文件系统中的一种特殊类型文件。管道就是解决进程之间共享数据的问题的OS会为每个进程分配一个 struct file 结构体的,但是它们指向同一个 struct pipe_inode_info ,这个结构体里包含环形缓冲区,这就保证了不同的进程之间可以访问同一份数据

2. 命名管道的操作

//命令
mkfifo //创建命名管道

在这里插入图片描述

可以看到,fifo 文件的大小是 0,这也说明了数据是不会刷新到磁盘上的

//系统调用
//mode代表权限
//成功返回0,失败返回-1
int mkfifo(const char* pathname, mode_t mode);//创建命名管道
//成功返回0,失败返回-1
int unlink(const char* pathname); //删除命名管道

3. 命名管道的实现

在这里插入图片描述

. client.cpp

#include "NamePipe.hpp"int main()
{NamePipe name_pipe(fifoname);name_pipe.OpenForWrite();std::string line;while (true){std::cout << "Please Enter# ";std::getline(std::cin, line);name_pipe.Write(line);}name_pipe.Close();return 0;
}

. common.hpp

#ifndef __COMMON_HPP__
#define __COMMON_HPP__#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>std::string fifoname = "fifo";
mode_t mode = 0666;#define SIZE 128#endif

. NamePipe.hpp

#include "common.hpp"const int gdefaultfd = -1;
class NamePipe
{
public:NamePipe(std::string &name, int fd = gdefaultfd) : _name(name), _fd(fd){}bool Create(){// server 创建管道int n = mkfifo(fifoname.c_str(), mode);if (n < 0){perror("mkfifo fail");return false;}return true;}bool OpenForRead(){// 命名管道的操作特点:打开一端,另一端没打开的时候,open会阻塞_fd = open(fifoname.c_str(), O_RDONLY);if (_fd < 0){perror("server open fail");return false;}return true;}bool OpenForWrite(){_fd = open(fifoname.c_str(), O_WRONLY);if (_fd < 0){perror("client open fail");return false;}return true;}bool Read(std::string *out){char buffer[SIZE] = {0};ssize_t num = read(_fd, buffer, sizeof(buffer) - 1);if (num > 0){buffer[num] = 0;// std::cout 也有缓冲区,需要刷新*out = buffer;}else if (num == 0){// client quit,read读到文件末尾,返回0return false;}else{return false;}return true;}void Write(const std::string &in){write(_fd, in.c_str(), in.size());}void Close(){close(_fd);}void Remove(){unlink(fifoname.c_str()); // 删除管道文件}~NamePipe(){}private:std::string _name;int _fd;
};

. server.hpp

#include "NamePipe.hpp"int main()
{NamePipe name_pipe(fifoname);name_pipe.Create();name_pipe.OpenForRead();std::cout << "open file success" << std::endl;std::string message;while (true){bool res = name_pipe.Read(&message);if(!res)break;std::cout << "Client Say@ " << message << std::endl;}name_pipe.Close();name_pipe.Remove();return 0;
}

. Makefile

.PHONY:all
all:client serverclient:client.cppg++ -o $@ $^ -g -std=c++11
server:server.cppg++ -o $@ $^ -g -std=c++11.PHONY:clean
clean:rm -f clientrm -f server

命名管道主要解决毫无关系的进程之间,进行文件级进程通信

匿名管道和命名管道剩下的特点是一样的。

server 和 client 之所以能够看到同一份资源,是因为它们打开的是同一份文件,该文件路径相同,inode相同,所以能够共享数据

看到这里,大家应该明白了,匿名管道和命名管道是非常相似的匿名管道是内存级文件,是内存模拟出来的,而命名管道是磁盘上的一种特殊文件类型,是有名字的。正是因为有名字,所以才叫做命名管道

二、System V共享内存

1. System V的原理

还记得动态库的加载吗进程会把自己依赖的动态库加载到物理内存里,通过页表建立虚拟地址和物理地址的映射关系,而动态库就被映射到了虚拟地址空间中的共享区中

那么,一个进程需要的动态库会被映射到自己的共享区中,另一个进程也需要这个动态库呢?那么,这个进程也会把动态库映射到自己的共享区中,OS不会将这个动态库加载两次,两个进程用的是同一个动态库。这从某种意义上来说,不就是OS给动态库开辟了一块物理内存,这块物理内存被映射到了进程虚拟地址空间中的共享区上吗。这不就是两个进程之间共享内存了嘛

所以,我们想实现共享内存,就必须

1.创建共享内存

2.建立虚拟地址和物理地址的映射关系

3.删除共享内存

动态库的详细加载过程请看这篇文章:从ELF到进程间通信:剖析Linux程序的加载与交互机制。

在这里插入图片描述

假设进程A,进程B之间通过共享内存通信,如果进程C,D也需要通信呢,许多进程之间都需要共享内存通信呢?那么,是不是会有许多共享内存,OS要不要进行管理呢?肯定是要的。先描述再组织

2. System V的操作

//key表示共享内存段的唯一键值(类似文件名)
//size是共享内存段的大小,一般建议是4KB的整数倍
//shmflg 创建方式和访问权限
//成功返回共享内存标识符,失败返回-1(类似于文件描述符fd)
//shmflg的选项,介绍两个
//IPC_CREAT,单独使用,共享内存不存在,则创建,已存在,直接获取
//IPC_EXCL,不能单独使用
//一起使用,共享内存不存在,则创建,已存在,返回出错
int shmget(key_t key, size_t size, int shmflg)//创建或获取一个共享内存
//这两个参数是可以随便写的
//成功返回 key,失败返回-1
key_t ftok(const char* pathname, int proj_id)//形成唯一的键值,传入shmget函数中

共享内存的生命周期不随进程,随内核

所以,共享内存需要我们自己手动释放资源。可以使用以下命令查看共享内存的信息以及删除共享内存

ipcs -m //查看所有共享内存段的信息
ipcrm -m shmid //删除指定的共享内存段

在这里插入图片描述

可以看到,第一次共享内存是创建出来的,第二次就创建失败,这也证明了共享内存的生命周期不随进程。当删除了指定的共享内存,就可以再次创建共享内存了

但这种删除共享内存的方式还是太麻烦了,我们希望进程结束时就删除掉共享内存。

文件 = 文件属性 + 文件内容

共享内存 = 内存块 + 共享内存的 struct(shm的属性)

//shmid就是共享内存标识符
//cmd可以使用IPC_RMID,表示立即删除
//buf设置为NULL
//0表示成功, -1表示失败
int shmctl(int shmid, int cmd, struct shmid_ds* buf); //对共享内存的属性做操作
//shmaddr 设置为 nullptr,让内核自动选择映射的虚拟地址
//shmflg 设置为 0,缺省,继承共享内存的权限
//成功了返回共享内存映射到当前进程虚拟地址空间的具体地址,失败返回-1
void* shmat(int shmid, const void* shmaddr, int shmflg); //将共享内存与虚拟地址进行映射

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

这是为什么呢?要想挂接成功,就必须对共享内存设置权限才可以

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

可以看到,映射成功了,并且共享内存有了权限,nattch 挂接的进程数量也变为了 1

在删除共享内存之前,虚拟地址空间依然和共享内存有着映射关系,但这并不是我们想要的,所以,在删除共享内存之前,需要先将虚拟地址空间和共享内存去关联,然后再删除共享内存

//成功返回0,失败返回-1
int shmdt(const void* shmaddr); //将共享内存与虚拟地址空间进行去关联

我们说过,OS内会有许多共享内存,所以对于共享内存是需要进行管理的,在用户层面上提供了一种结构体来描述共享内存。我们来看一下。

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

我们可以获取里面的属性看一看。不过在此之前,需要了解 shmctl 函数的一个选项。

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

共享内存的特征总结

1.生命周期随内核

2.共享内存是 IPC 中速度最快的(因为共享内存写入数据和读取数据不需要使用系统调用,只需要使用指针就可以完成,系统调用也是有成本的)

3.共享内存没有同步互斥机制,来对多个进程的访问进行协同

3. 共享内存的实现

. client.cpp

#include "Shm.hpp"int main()
{SharedMemory shm;shm.Get();// sleep(5);shm.Attach();// sleep(5);// 通信shm.SetZero();char ch = 'A';int cnt = 10;while(cnt--){std::cout << "client 开始写入" << std::endl;shm.AddChar(ch);ch++;sleep(1);}shm.Detach();// sleep(5);return 0;
}

. Shm.hpp

#ifndef __SHM_HPP__
#define __SHM_HPP__#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define gdefaultsize 4096std::string gpathname = ".";
int gproj_id = 0x66;class SharedMemory
{
private:bool CreateHelper(int flags){// 形成唯一的键值_key = ftok(gpathname.c_str(), gproj_id);if (_key < 0){perror("ftok fail");return false;}printf("形成键值成功:0x%x\n", _key);// 获取共享内存_shmid = shmget(_key, _size, flags); // 创建全新的共享内存if (_shmid < 0){perror("shmget fail");return false;}printf("shmid:%d\n", _shmid);return true;}public:SharedMemory(size_t size = gdefaultsize) : _key(0), _size(size), _shmid(-1),_start_addr(nullptr), _windex(0), _rindex(0),_num(nullptr), _data_start(nullptr){}bool Create(){return CreateHelper(IPC_CREAT | IPC_EXCL | 0666);}bool Get(){return CreateHelper(IPC_CREAT);}bool RemoveShm(){// 删除共享内存int n = shmctl(_shmid, IPC_RMID, NULL);if (n < 0){perror("shmctl fail");return false;}std::cout << "删除shm成功" << std::endl;return true;}bool Attach(){// 将共享内存映射到虚拟地址空间中//共享内存需要权限才能挂接在指定的进程_start_addr = shmat(_shmid, nullptr, 0);if ((long long)_start_addr == -1){perror("shmat fail");return false;}_num = (int *)_start_addr;_data_start = (char *)_start_addr + sizeof(int);std::cout << "共享内存映射到进程的虚拟地址空间中" << std::endl;return true;}bool Detach(){// 共享内存与虚拟地址空间进行去关联int n = shmdt(_start_addr);if (n < 0){perror("Detach fail");return false;}std::cout << "虚拟地址空间与共享内存进行去关联" << std::endl;return true;}void SetZero(){*_num = 0;}void AddChar(char ch){if (*_num == _size - sizeof(int))return;((char *)_data_start)[_windex++] = ch;_data_start[_windex] = 0;_windex %= (_size - sizeof(int));(*_num)++;// std::cout << "Debug: " << _data_start[_windex - 1] << " _num = " << *_num << std::endl;}void PopChar(char *out){// std::cout << " _num = " << *_num;if (*_num == 0)return;*out = _data_start[_rindex++];_rindex %= (_size - sizeof(int));(*_num)--;// std::cout << "client read: " << _data_start[_rindex - 1] << std::endl;}void PrintAttr(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);if(n < 0){perror("PrintAttr shmctl fail");return;}printf("key:0x%x\n", ds.shm_perm.__key);printf("shm_atime:%ld\n",ds.shm_atime);printf("shm_nattch:%ld\n",ds.shm_nattch);}int GetCount(){return *_num;}~SharedMemory(){}private:key_t _key;size_t _size;int _shmid;void *_start_addr;int *_num;char *_data_start;int _windex;int _rindex;
};#endif

. server.cpp

#include "Shm.hpp"int main()
{SharedMemory shm;shm.Create();// sleep(5);shm.Attach();shm.PrintAttr();sleep(2);// 通信char ch;while (true){if(!shm.GetCount()) break;shm.PopChar(&ch);printf("server getchar:%c\n", ch);sleep(1);}shm.Detach();// sleep(5);shm.RemoveShm();// sleep(5);return 0;
}

. Makefile

.PHONY:all
all:client serverclient:client.cppg++ -o $@ $^ -g -std=c++11
server:server.cppg++ -o $@ $^ -g -std=c++11.PHONY:clean
clean:rm -f clientrm -f server

今天的文章分享到此结束,觉得不错的给个一键三连吧。

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

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

相关文章

如何将数据库快速接入大模型实现智能问数,实现chatbi、dataagent,只需短短几步,不需要配置工作流!

智能问数系统初始化操作流程 一、系统初始化与管理员账号创建登录与初始化提示&#xff1a;首次访问系统登录页&#xff0c;若系统未初始化&#xff0c;会弹出 “系统未完成初始化&#xff0c;请初始化管理员账号” 提示&#xff0c;点击【去创建】。填写管理员信息&#xff1a…

告别手写文档!Spring Boot API 文档终极解决方案:SpringDoc OpenAPI

在前后端分离和微服务盛行的今天&#xff0c;API 文档是团队协作的“通用语言”。一份清晰、准确、实时同步的文档&#xff0c;能极大提升开发和联调效率。然而&#xff0c;手动编写和维护 API 文档&#xff08;如 Word、Markdown 或 Postman&#xff09;是一场永无止境的噩梦—…

N4200EX是一款全智能超声波检测仪产品简析

N4200EX是一款全智能超声波检测仪&#xff0c;适用于石油、石化、天然气、气体生产等行业的压力管路、阀门、设备的各种防爆场合气体泄漏、真空泄漏、阀门内漏检测。●本安防爆设计&#xff0c;防爆、防尘、防水、抗摔。●适应恶劣环境&#xff0c;可在-25℃超低温环境检测&…

NestJS @Inject 装饰器入门教程

一、核心概念解析 1.1 依赖注入&#xff08;DI&#xff09;的本质 依赖注入是一种设计模式&#xff0c;通过 IoC&#xff08;控制反转&#xff09;容器管理对象生命周期。在 NestJS 中&#xff0c;Injectable() 标记的类会被容器管理&#xff0c;而 Inject() 用于显式指定依赖项…

网络地址详解

子网划分详解&#xff1a;从 IP 地址结构到实际应用 在计算机网络中&#xff0c;子网划分是一项关键的技术&#xff0c;它能帮助我们更高效地管理 IP 地址资源&#xff0c;优化网络性能。要深入理解子网划分&#xff0c;首先需要从 IP 地址的基本结构说起。 一、IPv4 地址的基…

吾日三省吾身 | 周反思 8.19

上周一览总体来说&#xff0c;上个周是一个被项目驱使而险些丧失自主思考能力的危险阶段。相比任何有机械化工作经验的读者都有类似的体验&#xff0c;在手上打螺丝的无尽循环中&#xff0c;自己的脑子就会逐渐丧失对自身的感知以及自主思考的能力。而这个负循环一旦开始&#…

08.19总结

连通性 在无向图中&#xff0c;若任意两点间均存在路径相连&#xff0c;则该图称为连通图。 若删除图中任意一个顶点后&#xff0c;剩余图仍保持连通性&#xff0c;则该图为点双连通图。 若删除图中任意一条边后&#xff0c;图仍保持连通性&#xff0c;则该图为边双连通图。 在…

车e估牵头正式启动乘用车金融价值评估师编制

8月13日&#xff0c;汽车金融行业职业能力评价规范编制启动工作会议在广州圆满落幕。本次会议由中国机械工业联合会机械工业人才评价中心主办&#xff0c;广州穗圣信息科技有限公司&#xff08;车e估&#xff09;承办。会议汇聚了众多行业精英&#xff0c;包括中国机械工业联合…

清空 github 仓库的历史提交记录(创建新分支)

想在 现有仓库中创建一个新分支 master&#xff0c;删除原来的 main&#xff0c;然后把 master 重命名为 main&#xff0c;并且清空历史。可以用下面一条完整的命令序列操作&#xff1a; # 1. 创建一个没有历史的新分支 master git checkout --orphan master# 2. 添加当前所有文…

使用B210在Linux下实时处理ETC专用短程通信数据(2)-CPU单核高速数据处理

在上一篇文章中&#xff0c;使用Octave初步验证了ETC车联数据的格式。然而&#xff0c;Octave无法实时处理20M的采样带宽。我们本节通过C语言&#xff0c;重写 Octave程序&#xff0c;实现实时处理&#xff0c;涉及下面三个关键特点。 文章目录1. 全静态内存2. 使用环状缓存3 无…

Spark 运行流程核心组件(二)任务调度

1、调度策略参数默认值说明spark.scheduler.modeFIFO调度策略&#xff08;FIFO/FAIR&#xff09;spark.locality.wait3s本地性降级等待时间spark.locality.wait.processspark.locality.waitPROCESS_LOCAL 等待时间spark.locality.wait.nodespark.locality.waitNODE_LOCAL 等待时…

Orbbec---setBoolProperty 快捷配置设备行为

在奥比中光&#xff08;Orbbec&#xff09;SDK&#xff08;通常称为ob库&#xff09;中&#xff0c;setBoolProperty函数是用于设置设备或传感器的布尔类型属性的核心接口。它主要用于开启/关闭设备的某些功能或模式&#xff0c;是配置设备行为的重要方法。 函数原型与参数解析…

[OWASP]智能体应用安全保障指南

1.关键组件定义 KC1 生成式语言模型&#xff08;Generative Language Models&#xff09; KC1.1 大语言模型&#xff08;LLMs&#xff09;&#xff1a;作为代理的“大脑”&#xff0c;基于预训练基础模型&#xff08;如 GPT 系列、Claude、Llama、Gemini&#xff09;&#xff…

【Vivado TCL 教程】从零开始掌握 Xilinx Vivado TCL 脚本编程(三)

【Vivado TCL 教程】从零开始掌握 Xilinx Vivado TCL 脚本编程&#xff08;三&#xff09; 系列文章目录 1、VMware Workstation Pro安装指南&#xff1a;详细步骤与配置选项说明 2、VMware 下 Ubuntu 操作系统下载与安装指南 3、基于 Ubuntu 的 Linux 系统中 Vivado 2020.1 下…

AI与大数据驱动下的食堂采购系统源码:供应链管理平台的未来发展

在数字化浪潮不断加速的今天&#xff0c;很多企业和机构都在追求一个目标&#xff1a;如何把“效率”与“成本”做到最佳平衡。对于学校、企事业单位的食堂来说&#xff0c;采购环节就是重中之重。往小了说&#xff0c;它关系到食堂员工的工作体验&#xff1b;往大了说&#xf…

HarmonyOS 实战:学会在鸿蒙中使用第三方 JavaScript 库(附完整 Demo)

摘要 在鸿蒙&#xff08;HarmonyOS NEXT / ArkTS&#xff09;开发中&#xff0c;我们大部分业务逻辑和 UI 都是用 ArkTS 写的。不过在做一些数据处理、网络请求、工具函数或者复杂算法时&#xff0c;完全没必要“重复造轮子”。这时候就可以直接引入 JavaScript 的第三方库。鸿…

C++实现教务管理系统,文件操作账户密码登录(附源码)

教务管理系统项目介绍 项目概述 这是一个基于C开发的教务管理系统&#xff0c;提供了学生、教师和系统管理员三种角色的功能模块&#xff0c;实现了教务信息的录入、查询、修改和删除等基本操作。系统采用文件存储方式保存数据&#xff0c;具有简单易用、功能完备的特点。 项…

《C++进阶之STL》【二叉搜索树】

【二叉搜索树】目录前言&#xff1a;------------概念介绍------------1. 什么是二叉搜索树?2. 二叉搜索树的性能怎么样&#xff1f;------------基本操作------------一、查找操作思想步骤简述二、插入操作目标步骤简述三、删除操作目标步骤简述------------代码实现--------…

Orange的运维学习日记--47.Ansible进阶之异步处理

Orange的运维学习日记–47.Ansible进阶之异步处理 文章目录Orange的运维学习日记--47.Ansible进阶之异步处理Playbook 执行顺序原理可选执行策略调整并发连接数&#xff1a;forks 参数查看与修改 forks性能调优建议分批执行全局任务&#xff1a;serial 关键字serial 用法示例应…

从一个ctf题中学到的多种php disable_functions bypass 姿势

题目介绍 题目是Lilctf2025 的php-jail-is-my-cry 比赛链接&#xff1a;https://lilctf.xinshi.fun/ 题目环境前半部分是 php最近的phar 新 trick 大佬的原理分析 https://fushuling.com/index.php/2025/07/30/%e5%bd%93include%e9%82%82%e9%80%85phar-deadsecctf2025-baby-we…