news 2026/3/26 18:31:10

Linux system V 共享内存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux system V 共享内存

1.共享内存的原理

共享内存是最快的进程间通信IPC的方式,相对于管道而言,需要经历数据从用户态到内存,内存到用户态的两次拷贝,共享内存则是直接对物理内存进行操作,不需要拷贝,一旦这样的内存映射到共享它的进程的地址空间,这些进程间进行数据传递就不再涉及到内核,换句话来说,进程不再通过执行进入内核的系统调用来完成传递彼此的数据,如何理解呢?请看下图

进行进程间通信的本质就是要让不同进程看到同一份资源,那么共享内存又是如何做的呢?请看下图

我们先以进程A为例进行讲解,如果要使用共享内存,顾名思义,首先就要有内存,即先使用某种手段在物理内存上申请内存,当有了内存之后,那么就要将这个内存通过页表建立物理地址与虚拟地址的映射关系,映射到进程地址空间的共享区位置,然后向用户层返回共享内存的首地址,即在地址空间上起始的虚拟地址

那么接下来进程B就来了,它想要通过共享内存与进程A进行通信,此时共享内存已经有了,那么对于进程B来将就只需要获取这个共享内存,将这个内存通过页表建立物理地址与虚拟地址的映射关系,映射到进程地址空间的共享区位置,然后向用户层返回共享内存的首地址,即在地址空间上起始的虚拟地址,此时进程A和进程B作为不同进程就可以看到同一份资源进行通信了,通常的,共享内存也是进行单向通信的(共享内存可以进行双向通信,具体看用户需求)

如果我们要释放共享内存,那么步骤只需要和建立共享内存的步骤相反不就行了:即

1. 清除页表上关于共享内存建立的虚拟地址到物理地址的映射关系,2. 释放共享内存

操作系统不相信任何人,操作系统对上层提供系统调用接口,所以进程如果想要创建共享内存,挂接到进程地址上等操作都要通过系统调用接口告诉操作系统,让操作系统去做,那么也就会有很多进程会创建共享内存,所以操作系统也需要去管理这些共享内存,怎么管理,先描述,再组织,给每一个共享内存使用一个struct结构体(struct shmid_ds结构体)来描述属性,然后采取某个数据结构进行组织起来。

ps:共享内存的生命周期是随内核的,要是不去关闭它,就会一直存在,要么内核重启,要么用户手动关闭。

2.共享内存的接口

2.1 shmget 创建共享内存

其中sh的意思是share共享,m是memory内存,shmget的意思即为共享内存获取/分配的意思

int shmget(key_t key, size_t size, int shmflg);

返回值:共享内存标识符,在进程方面标记资源的唯一性

对于进程来说,对于共享内存的后续操作都需要通过返回值来进行操作

对于操作系统来说,可以通过返回值来管理共享内存的属性,大小,权限,关联进程数。

key:key_t 类型其实就是int类型,需要通过ftok函数获取,在ftok里面细说

size:代表申请的共享内存的大小,共享内存的大小一般都是设置为物理内存的一块大小的整数倍,物理内存是以块为基本单位的,如果你申请的假如是 4097,但是块的大小是 4096,物理内存给你的还是 4096*2,但是你具体使用只可以使用 4097。

shmflg:标志位,设置权限和申请方法,IPC_CREAT(单独使用):如果你申请的共享内存不存在,那么就创建。如果你申请的共享内存存在,那么就获取并返回。IPC_EXCL不单独使用,而是和IPC_CREAT结合使用。IPC_CREAT | IPC_EXCL:如果你申请的共享内存不存在,那么就创建。如果存在,那么就出错返回。这样可以确保我们申请了一个共享内存,这个共享内存一定是新的。

2.2 ftok函数

官方文档中:ftok的作用也就是将一个文件路径名(pathname)和一个项目标识符(project identifier)转换为一个 System V 进程间通信(IPC)的键值(key)。

ftok里面的两个参数全部都交给用户来决定,根据用户给与的参数会生成一个整数,进程就可以拿到这个整数做为ftok的返回值。

但是针对这两个pathname以及proj_id也是有要求的,其中pathname路径要求这个路径是存在的,proj_id要求不为0

所以针对用户想要进行通信的两个进程,用户约定,这两个进程使用的都是同一个pathname以及proj_id,那么同时ftok函数使用同一套算法进行计算,所以计算出来的整数一定是相同的,并且这个key值具有唯一性。

对于进程来说,进程需要通过key来找到同一份共享内存

对于操作系统来说,需要通过key来确定共享内存是否已经建立

所以shmget的返回值就像是共享内存的操作符,ftok的返回值就像是共享内存的身份证

2.3 shmat和shmdt

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmat作用是建立共享内存物理内存与进程虚拟地址空间的关系

shmid:shmget的返回值。

shmaddr:想要让物理内存挂接到你的虚拟地址空间共享区的哪一块地方,填 nullptr,让操作系统来分配。

shmflg:想要以什么权限来挂接这一块共享内存,可以设置为只读什么的,填 0的话就会使用我们在shmget是设置的权限。

int shmdt(const void *shmaddr);

解除共享内存于进程虚拟地址空间的关系。

2.4 shmctl

对共享内存的操作函数,执行查询,修改,删除等操作

shmid:shmget的返回值

cmd:执行的操作

着重看一个第三个参数:

观察这个struct shmid_ds类型(元数据)的结构体中还嵌套有一个类型为struct ipc_perm的结构体,这个嵌套的结构体中包含共享内存的部分属性,包括key,mode权限等,那么struct shmid_ds类型的结构体中还包含共享内存的大小shm_segsz等等,其实里面也会有共享内存的所在块的信息,在使用shmat时候,不是建立了物理内存与虚拟地址空间的关系了吗?所以这个结构体里面其实也记载了物理内存的位置。

所以对于共享内存要进行删除的话

shmctl(shmid,IPC_RMID,nullptr);

第三个参数就没有什么用,也就是第三个参数设为nullptr,因为操作系统会根据shmid来映射找到对应共享内存段的struct shmid_ds结构体,获取里面的属性,找到对应的物理内存,将其标记为待删除(还记得我们之前提到的,在操作系统中,删除就是支持覆盖),其他的进程想要连接上这个共享内存也就不行了,当struct shmid_ds里面的连接数为0时,这块物理内存也就可以被其他进程使用了,然后将struct shmid_ds里面数据清空。

2.5 命令行查看共享内存

ipcs -m可以查看共享内存的一些属性

ipcrm -m shmid 删除共享内存

3.共享内存的建立到删除

  1. 生成 IPC 键值,进程调用 ftok(pathname, proj_id) 生成唯一的 key_t 键值,作为共享内存的全局标识,进程可以通过生成的key_t 键值来找到共享内存。
  2. 创建/获取共享内存段,进程调用 shmget(key, size, IPC_CREAT | 权限位) ,操作系统会根据key来判断共享内存是否存在,之后内核创建共享内存元数据( struct shmid_ds ),返回 shmid ;此时仅分配元数据,未分配物理内存。
  3. 映射共享内存到进程地址空间,进程调用 shmat(shmid, NULL, 0) ,内核按需分配物理内存块的位置,建立进程虚拟地址与物理页帧的映射,返回映射起始地址 shm_addr ;多个进程映射同一 shmid 会指向同一块物理内存,元数据也会记录物理内存和虚拟地址空间的映射。
  4. 写入/读取共享内存内容,进程通过 shm_addr 直接读写物理内存(用户态操作,无数据拷贝),如 strcpy(shm_addr, "hello shared memory") ;其他映射的进程可直接读取到更新后的内容。
  5. 解除地址映射,进程不再使用时,调用 shmdt(shm_addr) ,断开当前进程虚拟地址与物理内存的关联;内核减少 struct shmid_ds 中的 shm_nattch (关联进程数)计数。、
  6. 标记共享内存删除,任意进程调用 shmctl(shmid, IPC_RMID, NULL) ,内核将该共享内存标记为待删除状态,新进程无法再通过原 key 获取此段。
  7. 物理内存与元数据回收,当 shm_nattch 计数减至 0(所有进程都已解除映射),内核回收共享内存对应的物理页帧和元数据,共享内存内容彻底消失。

4.代码实现

share.hpp

#include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <iostream> #include <string> #include<stdio.h> #include<unistd.h> using namespace std; const string path="/home/wjp/sharemem"; int proj_id=0x66; const int SIZE=4096; int Getkey() { int k=ftok(path.c_str(),proj_id); if(k<0) { exit(1); } return k; } int GetSharemem(int flag)//创建一块物理内存 { int k=Getkey(); int shmid=shmget(k,SIZE,flag); //shmid:内核对共享内存的唯一标识符 if(k<0) { exit(2); } return shmid; } int Create() { return GetSharemem(IPC_CREAT|IPC_EXCL|0666); } int Get() { return GetSharemem(IPC_CREAT); }

creat.cpp

一个进程创建共享内存并一直打印共享内存的数据,负责共享内存的删除

#include"./share.hpp" int main() { int shmid=Create(); char*shmaddr=(char*)shmat(shmid,nullptr,0); if((long long)shmaddr==-1) { perror("shmat"); return 1; } while(true) { cout<<"say @ "<<shmaddr<<endl; sleep(1); } shmdt(shmaddr); shmctl(shmid,IPC_RMID,nullptr); return 0; }

get.cpp

一个进程负责连接共享内存,并且从键盘接收数据打印到共享内存。

#include"./share.hpp" int main() { int shmid=Get(); char*shmaddr=(char*)shmat(shmid,nullptr,0); while(true) { cout<<"Please enter@ "<<endl; fgets(shmaddr,SIZE,stdin); } shmdt(shmaddr); return 0; }

运行结果:

这里要注意要先运行./create,先运行./get,后续运行./create就会因为(IPC_CREAT|IPC_EXCL|0666);共享内存存在而出错返回。

可以看出来现在的共享内存是没有同步和互斥的。进程create作为读端没有等资源就绪就开始读了。

5.代码改进

#ifndef __SHAREMEM_HPP__ #define __SHAREMEM_HPP__ #include <sys/stat.h> #include <sys/types.h> #include <sys/ipc.h> #include <iostream> #include <sys/shm.h> #include <stdlib.h> #include <string> #include <unistd.h> #include <fcntl.h> using namespace std; const int size = 4096; const string pathname = "/home/wjp/sharemem/makefile"; const int proj_id = 0x6666; key_t Getkey() { int k = ftok(pathname.c_str(), proj_id); if (k < 0) { exit(1); } return k; } int Getsharemem(int flag) { int k = Getkey(); int shmid = shmget(k, size, flag); if (shmid < 0) { exit(2); } return shmid; } int Create() { return Getsharemem(IPC_CREAT | IPC_EXCL | 0666); } int Get() { return Getsharemem(IPC_CREAT); } #define FIFO_FILE "./mkfifo" #define MODE 0664 enum { FIFO_FILE_CREATE_ERR = 1, FIFO_DELETE_ERR, FIFO_OPEN_ERR, }; class Init { public: Init() { int n = mkfifo(FIFO_FILE, MODE); if (n == -1) { cout<<"1"<<endl; perror("mkfifo"); exit(FIFO_FILE_CREATE_ERR); } } ~Init() { int m=unlink(FIFO_FILE); if(m==-1) { perror("unlink"); exit(FIFO_DELETE_ERR); } } }; #endif
#include"sharemem.hpp" int main() { int shmid=Get(); char*shmaddr=(char*)shmat(shmid,nullptr,0); int fd=open(FIFO_FILE,O_WRONLY); if(fd<0) { cout<<"error"<<endl; } while(true) { cout<<"please enter@ "<<endl; fgets(shmaddr,4096,stdin); write(fd,"c",1); } shmdt(shmaddr); close(fd); return 0; }
#include"sharemem.hpp" int main() { Init init; int shmid=Create(); char*shmaddr=(char*)shmat(shmid,nullptr,0); if ((long long)shmaddr == -1){ perror("shmat"); return 1; } int fd=open(FIFO_FILE,O_RDONLY); if(fd<0) { exit(FIFO_OPEN_ERR); } while(true) { char c; ssize_t s=read(fd,&c,1); if(s<=0) break; cout<<"say @ "<<shmaddr<<endl; // sleep(1); } shmdt(shmaddr); shmctl(shmid,IPC_RMID,nullptr); close(fd); return 0; }

管道的双方是存在同步性的,在读写端正常时,一段关闭,另一端就会阻塞,可以在create进程中,让进程去读取文件,由于get进程没有向文件中写入数据就会阻塞,只有等到get进程启动,向向共享内存写入数据,然后向文件中写入数据,create进程才会去共享内存读取数据。

ps:当我们要使用ctrl+c结束进程时,一定要在get端使用,不能在create端,否则操作系统杀死进程时,并没有执行shmctl对共享内存的删除,也就意味着共享内存还存在。

运行结果:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/22 6:19:19

大规模数据集处理:TensorFlow Data API高级用法

大规模数据集处理&#xff1a;TensorFlow Data API高级用法 在训练一个图像分类模型时&#xff0c;你是否遇到过这样的场景&#xff1f;GPU 利用率长期徘徊在20%以下&#xff0c;监控显示计算设备频繁“空转”&#xff0c;而日志却提示数据加载耗时远超前向传播。这并非模型设计…

作者头像 李华
网站建设 2026/3/23 18:15:11

2h看完了cpp的46宗罪

笑傻了 还是太有生活了&#xff0c;感兴趣的可以自己去搜原视频cpp写的我都快用回c了&#xff0c;algo用的还是cpp习惯了&#xff0c;尝试用rust写了几天&#xff0c;还是决定放过自己至于项目 感觉什么语言不是特别重要&#xff0c;哎反正能构造出想要的东西就行&#xff0c;寒…

作者头像 李华
网站建设 2026/3/15 13:49:53

OpCore Simplify:终极黑苹果配置完整指南

OpCore Simplify&#xff1a;终极黑苹果配置完整指南 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 还在为复杂的OpenCore EFI配置而烦恼吗&#xff…

作者头像 李华
网站建设 2026/3/18 18:15:34

一文说清GRBL如何在Arduino Uno上实现G代码解析

GRBL如何在Arduino Uno上实现G代码解析&#xff1f;一文讲透核心机制 你有没有想过&#xff0c;一块不到十块钱的Arduino Uno&#xff0c;是如何驱动一台CNC雕刻机精准走刀、完成复杂轨迹加工的&#xff1f;答案就藏在一个叫 GRBL 的开源固件里。 它不是简单的“串口转发器…

作者头像 李华
网站建设 2026/3/20 5:46:05

yuzu模拟器中文乱码一键修复完全指南:从入门到精通

yuzu模拟器中文乱码一键修复完全指南&#xff1a;从入门到精通 【免费下载链接】yuzu-downloads 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu-downloads 你是否在yuzu模拟器中遭遇了令人沮丧的中文显示问题&#xff1f;无论是方块字、乱码还是字符缺失&…

作者头像 李华
网站建设 2026/3/24 4:18:58

123云盘解锁脚本:全面体验会员特权的最佳方案

123云盘解锁脚本&#xff1a;全面体验会员特权的最佳方案 【免费下载链接】123pan_unlock 基于油猴的123云盘解锁脚本&#xff0c;支持解锁123云盘下载功能 项目地址: https://gitcode.com/gh_mirrors/12/123pan_unlock 还在为123云盘的下载限制和广告烦恼吗&#xff1f…

作者头像 李华