当前位置: 首页 > news >正文

【Linux手册】共享内存:零拷贝实现共享的优势与实操指南


请添加图片描述


半桔:个人主页

 🔥 个人专栏: 《Linux手册》《手撕面试算法》《C++从入门到入土》

🔖人生若只如初见,何事秋风悲画扇。 -纳兰性德-

文章目录

  • 前言
  • 一. 什么是共享内存
  • 二. 共享内存的原理
  • 三. 共享内存的使用方法
    • 3.1 创建共享内存
    • 3.2 挂接共享内存
    • 3.3 共享内存的释放
  • 四. 共享内存demo代码
  • 五. 共享内存与管道对比

前言

在上一篇关于进程间通信的文章中,我们谈到了可以使用一个文件来让两个进程进行交互,但是直接使用外设上的文件进行IO的时候效率太低,而且进程间通信的目的就不是将信息存储到外设上,因此我们引入了一个内存级文件:管道文件;其中管道分为匿名管道和有名管道,分别来让有血缘关系的进程通信以及毫不相干的进程进行通信。

此篇将会讲解另一种通信的方式——共享内存,不需要依赖文件就可以实现通信。

本文将分为5个板块:

  1. 共享内存是什么;
  2. 共享内存的原理;
  3. 共享内存的使用方法;
  4. 共享内存demo代码;
  5. 共享内存与管道对比。

一. 什么是共享内存

进程间通信的主要目的就是让解决进程间独立的阻碍,让两个进程能够看到并使用同一份资源,而管道文件就是一种实现方法,让两个进程看到同一份文件,通过这一个文件进行交互。

两个进程使用的都是同一个物理内存,这块物理内存不就是他们共享的资源嘛,只不过将这块物理内存进行了划分,让不同进程使用不同的物理内存互不干扰。

能否直接在内存中开辟一块区域让两个进程都能使用???

通过这种方式来去除文件的概念,让进程间直接使用公共的内存空间。共享内存就是来实现多个进程如何访问同一块物理内存的。

二. 共享内存的原理

下图是共享内存的原理示意图:
请添加图片描述

共享内存实际上就是在物理内存中开辟一段公共空间,将该公共空间与两个要进行交互得进程建立联系,这样就可以实现让两个独立得进程看到同一份资源。

具体操作如下:

  1. 在物理内存中开辟一块空间;
  2. 让该公共内存分别映射到两个进程到两个进程得共享区;
  3. 让两个进程能获取到公共内存的起始地址。

以上就是共享内存的原理和具体需要实现的操作,通过以上操作后,两个进程分别得到一个指针,而该指针指向物理内存中的同一块区域,当一个进程对该区域进行操作时,另一个进程也能看到。

以上就是共享内存的原理,下面看一下操作系统提供了那些接口帮助我们实现:

三. 共享内存的使用方法

3.1 创建共享内存

首先要解决的是:如何在内存开辟一块空间供进程使用。

操作系统中提供了int shmget(int key , size_t size , int shmflg)

  • 创建失败返回-1,创建成功返回共享内存标识符,就是共享内存ID;
  • 第二个参数size,共享内存的大小,单位是字节;
  • 第三个参数shmflg,选项与open的选项类似,其中常用的有IPC_CREAT如果共享内存不存在就创建,如果已经存在就不再创建;IPC_CRAET|IPC_EXIT也是创建共享内存只不过如果共享内存已经存在就直接报错,保证返回的共享内存是最新创建的。

通信双方都需要调用shmget来获取共享内存的ID,但是只需要创建一次共享内存就可以了。

调用shmget时,如何分辨两个进程是否请求的是同一块物理内存,即如何保证两者能拿到同一块物理内存???

  • shmget第一个参数key来保证两个进程请求的是同一块资源;
  • key是数字,该数字在操作系统内核中是唯一的,用key来区分内核中不同的的共享内存。
  • 两个进程如果key值是相同的就说明要访问同一块物理内存。

key值在操作系统内是唯一的,那我在传参的时候如何传,我怎么知道自己的key有没有被使用???

在操作系统中该key值不需要用户来做,操作系统也提供了接口来让我们设置key值:

key_t ftok(const char* pathname , int proj_id)

  • 返回值,返回key值,失败返回-1;
  • 第一个参数:一个字符串是表示一个有效路径,第二个参数一个数字。

ftok()是一个算法,通过一个有效路径字符串和一个数字来获得一个冲突最小的key值。
只能说冲突最小,也有可能出现冲突,也就是说ftok()产生的key可能是已经存在的了,此时就会返回-1,通过调整路径或后面的数字重新尝试。

3.2 挂接共享内存

共享内存已经在物理内存中开辟出来了,但是要先对内存进行操作,还需要将物理内存与共享区建立映射关系,操作系统中通过:void* shmat(int shmid , const void* shmaddr , int shmflag)

  1. 第一个参数shmid决定挂接哪一个物理内存;
  2. 第二个参数shmaddr挂接的位置,可以直接填NULL,让操作系统自己分配到共享区上;
  3. 第三个参数shmflag表示以什么方式进行挂接:读还是写,一般使用0表示以读和写的方式挂接。
  4. 返回值,一个指针指向挂接后的虚拟地址。

操作系统也运行进程与共享内存接关联:int shmdt(const void* shmaddr),参数是共享内存的起始地址。

3.3 共享内存的释放

共享内存的生命周期并不是随进程,而是随内核,也就是说当进程退出后,共享内存也不会自动释放,不主动关闭就不会释放,除非操作系统关闭了。

int shmctl(int shmid , int cmd , struct shmid_ds *buf)

  • 该函数使用来控制共享内存的,使用shmctrl(shmid , IPC_RMID ,nulltr)实现对共享内存的释放。

四. 共享内存demo代码

下面简单实现两个程序,让他们能够进行交互,一个进程向另一个进程发送消息,接收方将信息打印出来即可:

先对各种接口进行封装,对获取shmid接口进行封装:封装获取key的接口以及换取ID的接口,其中创建一方要进行创建,而另一方直接打开,所以shmget()的选项是不一样的。

const string path_name_ = "/usr/bin";
const int pj_id_ = 100;
const int sz_ = 1024;// 获取key值
key_t Get_Key()
{key_t k = ftok(path_name_.c_str(), pj_id_);if (k < 0){cout << " ftok errro int " << __FILE__ << " at line :" << __LINE__ << endl;exit(1);}return k;
}// 创建共享内存
int _Base_shmid(int flag , int sz)
{int k = Get_Key();int id = shmget(k, sz, flag);if (id < 0){cout << " shmget errro int " << __FILE__ << " at line :" << __LINE__ << endl;exit(2);}return id;
}int Creat_shm(int sz = sz_)
{return _Base_shmid(IPC_CREAT | IPC_EXCL | 0666 , sz);
}int Get_shm(int sz = sz_)
{return _Base_shmid(IPC_CREAT , sz);
}

封装进行挂接,取消挂接以及释放共享内存的接口:

void* To_attach(int id)
{// 进行挂接void* pshm = shmat(id , nullptr , 0);return pshm;
}void To_Detach(const void* pshm)
{shmdt(pshm);
}void Del_shm(int id)
{if(shmctl(id , IPC_RMID , nullptr) < 0 ){perror("shmctl failed");exit(1);}
}

编写发送消息的一方,发送字符从A到Z:

int main()
{int id = Get_shm();void* pshm = To_attach(id);char* p = static_cast<char*>(pshm);for(int i = 0 ; i < 26 ; i++){p[i] = 'A' + i;p[i + 1] = 0;usleep(500000);}
}

接收方打印出接受到的信息:

int main()
{int id = Creat_shm();void* pshm = To_attach(id);char* p = static_cast<char*>(pshm);sleep(1);for(int i = 0 ; i < 26; i++){cout << " client send message :" << p << endl;usleep(500000);}To_Detach(pshm);Del_shm(id);return 0;}

以下是代码效果展示:
请添加图片描述

五. 共享内存与管道对比

  1. 共享内存没有同步互斥机制,可能出现两个进程同时访问共享资源,即同时进行写入和读取;
  2. 共享内存是所有进程间通信中最快的,因为其不需要进行多余的拷贝:管道通信要先将数据拷贝到管道文件中,读取时又要间信息从从管道从读出来,而共享内存,只要有一方写入另一方就可以进行读取;
  3. 共享内存内部的书记由用户自己维护,需要用户手动释放资源。

操作系统也要对共享内存进行管理,在内核中由一个结构体struct shmid_ds专门用来描述共享内存:

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct shmid_ds {struct ipc_perm		shm_perm;	/* operation perms */int			shm_segsz;	/* size of segment (bytes) */__kernel_time_t		shm_atime;	/* last attach time */__kernel_time_t		shm_dtime;	/* last detach time */__kernel_time_t		shm_ctime;	/* last change time */__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */unsigned short		shm_nattch;	/* no. of current attaches */unsigned short 		shm_unused;	/* compatibility */void 			*shm_unused2;	/* ditto - used by DIPC */void			*shm_unused3;	/* unused */
};

如果在程序中想要获取一个共享内存的属性,也可以使用之前说的shmctl()来获取:

shmctl(id , IPC_STAT , &shmds)其中选项使用IPC_STAT,第三个参数是输出型参数,调用之后内部就保存共享内存的各种属性。


文章转载自:

http://mzRMCP5g.mjyrg.cn
http://UnuGW73H.mjyrg.cn
http://k6R3Q2RP.mjyrg.cn
http://N3kAkrxp.mjyrg.cn
http://jMh1Sp1b.mjyrg.cn
http://VXu2bvH2.mjyrg.cn
http://rD1fk9Yi.mjyrg.cn
http://ugewM1Cx.mjyrg.cn
http://bDNXk4LV.mjyrg.cn
http://WeWD6RVz.mjyrg.cn
http://RtoCo13z.mjyrg.cn
http://1bUNXy8J.mjyrg.cn
http://WjAYXume.mjyrg.cn
http://XVEyWm4d.mjyrg.cn
http://DeobtSqH.mjyrg.cn
http://iGIJKQ5t.mjyrg.cn
http://Mku9rYgF.mjyrg.cn
http://08sXOL6x.mjyrg.cn
http://zYogZopK.mjyrg.cn
http://KBCamm9A.mjyrg.cn
http://PeslY0LL.mjyrg.cn
http://KKBnvR1M.mjyrg.cn
http://nm7wpINU.mjyrg.cn
http://aPmbTwF7.mjyrg.cn
http://9hhEwtfB.mjyrg.cn
http://U8C9QH61.mjyrg.cn
http://3qEQQOHq.mjyrg.cn
http://HmZ9q7K2.mjyrg.cn
http://TpBdrHTG.mjyrg.cn
http://LRDUCXc5.mjyrg.cn
http://www.dtcms.com/a/382758.html

相关文章:

  • ARM的TrustZone
  • 返利app排行榜的缓存更新策略:基于过期时间与主动更新的混合方案
  • springboot+zookeeper+(2025最新)Dubbo-admin实现分布式
  • 缓存与数据库一致性实战手册:从故障修复到架构演进
  • 基于 Linux 内核模块的字符设备 FIFO 驱动设计与实现解析(C/C++代码实现)
  • 【C++】类和对象(下):初始化列表、类型转换、Static、友元、内部类、匿名对象/有名对象、优化
  • JSON、Ajax
  • 第2课:Agent系统架构与设计模式
  • Python上下文管理器进阶指南:不仅仅是with语句
  • Entities - Entity 的创建模式
  • 用html5写王者荣耀之王者坟墓的游戏2deepseek版
  • 【Wit】pure-admin后台管理系统前端与FastAPI后端联调通信实例
  • godot+c#使用godot-sqlite连接数据库
  • 【pure-admin】pureadmin的登录对接后端
  • tcpump | 深入探索网络抓包工具
  • scikit-learn 分层聚类算法详解
  • Kafka面试精讲 Day 18:磁盘IO与网络优化
  • javaweb CSS
  • css`min()` 、`max()`、 `clamp()`
  • 超越平面交互:SLAM技术如何驱动MR迈向空间计算时代?诠视科技以算法引领变革
  • Win11桌面的word文件以及PPT文件变为白色,但是可以正常打开,如何修复
  • 【系统架构设计(31)】操作系统下:存储、设备与文件管理
  • Flask学习笔记(三)--URL构建与模板的使用
  • 基于单片机的电子抢答器设计(论文+源码)
  • TCP与UDP
  • 【WebSocket✨】入门之旅(六):WebSocket 与其他实时通信技术的对比
  • 华为防火墙隧道配置
  • 使用 Matplotlib 让排序算法动起来:可视化算法执行过程的技术详解
  • 【C++深学日志】C++编程利器:缺省参数、函数重载、引用详解
  • 晶体管:从基础原理、发展历程到前沿应用与未来趋势的深度剖析