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

共享内存【Linux操作系统】

文章目录

  • 共享内存
    • 共享内存的原理
    • 共享内存相关函数和系统调用--systemV
      • 系统调用:shmget
      • 系统调用:shmctl
      • 系统调用:shmat
      • 系统调用:shmdt
      • 系统调用:ftok
    • 共享内存相关函数和系统调用--POSIX
      • shm_open-- 创建或打开共享内存对象
      • ftruncate - 设置共享内存大小
      • mmap - 内存映射
      • munmap - 解除内存映射
      • shm_unlink - 删除共享内存对象
      • close-关闭文件描述符
      • 完整使用流程
        • 创建/写入进程
        • 读取进程
      • 常见错误处理
    • 深刻理解共享内存
      • 用户设置的key冲突了怎么办?
      • 共享内存的生命周期
      • 共享内存的key和shmid的区别
      • 操作系统对物理内存进行任何操作时,都是以固定大小(一般是4kb)为基本单位进行操作的
    • 共享内存的特点
      • 共享内存是最快的进程间通信的形式了
      • 使用共享内存需要我们自己进行同步互斥

共享内存

共享内存的原理

就是使用系统调用开辟一块物理内存,这块物理内存就是共享内存

然后把它的起始地址通过进程的页表,与要通信的进程的共享区建立映射关系

最后,一个进程向自己的共享区中的共享内存读,一个进程向自己的共享区中的共享内存写,就可以实现进程间通信了

以后不再使用共享内存时,就直接把进程页表里面的映射关系去掉,也就是去关联
再把struct vm_area_struct链表里面的节点释放
至于共享内存,它的生命周期随内核,即如果用户不使用系统调用释放它,直到操作系统关机之前它会一直存在

在这里插入图片描述

共享内存可以同时在物理内存中存在很多份
而且某一个共享内存正在被那些进程使用,某个共享内存什么时候释放等等问题
操作系统都要知道
所以操作系统也要对所有的共享内存进行管理
所以要对共享内存,先描述再组织
所以
共享内存=共享内存的内核数据结构+共享内存对应的物理内存块



共享内存相关函数和系统调用–systemV

系统调用:shmget

头文件:sys/ipc.hsys/shm.h
返回值:
①成功就返回一个共享内存标识符[shmid]
②失败返回-1

参数:
①key_t key:唯一标识共享内存

②size_t size:共享内存的大小

③int shmflag:int类型的位图标志位

  • .IPC_CREAT:单独使用的话如果对应的共享内存不存在,就创建。存在就返回shmid
    该选项主要给获取共享内存的进程使用

  • IPC_EXCL:不能单独使用
    但是
    IPC_CREAT | IPC_EXCL
    如果对应的共享内存不存在,就创建并在页表建立映射关系
    存在就报错
    该选项主要给创建共享内存的进程使用

3.共享内存的权限,也是rwx,用4个8进制位表示(例:0666)
共享内存创建的时候必须指定权限!!!
不然直接报错,根本用不了

作用:创建一块共享内存


系统调用:shmctl

头文件:sys/shm.h和sys/ipc.h
返回值:
①成功,返回共享内存的key
②失败,返回-1
参数表:
①int shmid:共享内存描述符
②int cmd:位图标志位
IPC_RMID:释放共享内存

③struct shmid_ds*bf:输出型参数,可以把共享内存的相关属性带出来

作用:
根据cmd标志位的不同,对共享内存进行不同操作


系统调用:shmat

头文件:sys/shm.h和sys/types.h
返回值:void*
①成功,返回共享内存的起始地址(类似malloc的返回值)
②失败,返回(viod*)-1

参数表:
①int shmid:共享内存描述符

②const void*shmaddr:用户指定的虚拟起始地址[不过我们一般不用,所有一般是nullptr]

③int shmflag:位图标志位,表示共享内存的读写等权限我们一般也不管,因为共享内存基本是用来通信的,所以设置成0就行

作用:
把指定的共享内存挂接到进程地址空间中


系统调用:shmdt

头文件:sys/shm.h和sys/types.h
返回值:int
①成功,
②失败,返回-1
参数表:
const void *shmaddr:共享内存的虚拟起始地址(也就是shmat的返回值)

作用:
把共享内存从当前进程的共享区中去掉,也就是去除页表映射


系统调用:ftok

头文件:sys/types.h和sys/ipc.h
返回值:
①成功,返回共享内存的key
②失败,返回-1
参数表:
①cosnt char*name:带路径的项目文件名

②int id:项目id
参数①和参数②都是用户自己随便设置

作用:使用参数1和参数2并结合一定算法,求出一个key返回



共享内存相关函数和系统调用–POSIX

相较于传统的 System V 共享内存,POSIX 共享内存接口更简洁且符合现代编程习惯
而且,POSIX 共享内存是基于文件描述符的


shm_open-- 创建或打开共享内存对象

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>int shm_open(const char *name, int oflag, mode_t mode);
  • 作用:创建或打开一个共享内存对象(类似文件操作)
  • 参数
    • name:共享内存对象名称,必须以 / 开头(如 /my_shm
    • oflag:标志位
      O_CREAT | O_RDWR:如果对应的共享内存不存在,就创建。存在就返回shmid
      O_RDONLY:获取共享内存
    • mode:权限位(如 0666
  • 返回值:成功返回文件描述符(fd),失败返回 -1
  • 示例
    int fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666); 创建共享内存
    

ftruncate - 设置共享内存大小

#include <unistd.h>int ftruncate(int fd, off_t length);
  • 作用:调整共享内存对象的大小(必须调用,否则无法映射)
  • 参数
    • fdshm_open 返回的文件描述符
    • length:共享内存大小(字节)

mmap - 内存映射

#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • 作用:将共享内存映射到进程的虚拟地址空间
  • 参数
    • addr:映射起始地址(通常设为 NULL,由内核选择)
    • length:映射长度(应与 ftruncate 设置的一致)
    • prot:保护模式(如 PROT_READ | PROT_WRITE
    • flags:映射标志(MAP_SHARED 用于共享内存)
    • fdshm_open 返回的文件描述符
    • offset:偏移量(通常为 0
  • 返回值:成功返回映射后的内存地址,失败返回 MAP_FAILED
  • 示例
    char *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    

munmap - 解除内存映射

#include <sys/mman.h>int munmap(void *addr, size_t length);
  • 作用:解除内存映射,释放进程内的虚拟内存关联
  • 参数
    • addrmmap 返回的地址
    • length:映射长度

shm_unlink - 删除共享内存对象

#include <sys/mman.h>int shm_unlink(const char *name);
  • 作用删除共享内存对象(类似 unlink 文件)
  • 参数
    • name:共享内存对象名称(与 shm_open 一致)
  • 示例
    shm_unlink("/my_shm");
    

close-关闭文件描述符

#include <unistd.h>int close(int fd);
  • 作用:关闭 shm_open 返回的文件描述符(解除进程与共享内存的关联)。
  • 示例
    close(fd);
    

完整使用流程

创建/写入进程
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {// 1. 创建共享内存对象int fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);if (fd == -1) { perror("shm_open"); return 1; }// 2. 设置共享内存大小if (ftruncate(fd, 4096) { perror("ftruncate"); return 1; }// 3. 映射到进程地址空间char *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) { perror("mmap"); return 1; }// 4. 写入数据strcpy(ptr, "Hello from Process A!");// 5. 解除映射(可选,进程退出时自动解除)munmap(ptr, 4096);close(fd);return 0;
}
读取进程
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>int main() {// 1. 打开共享内存对象int fd = shm_open("/my_shm", O_RDONLY, 0);if (fd == -1) { perror("shm_open"); return 1; }// 2. 映射到进程地址空间char *ptr = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) { perror("mmap"); return 1; }// 3. 读取数据printf("Received: %s\n", ptr);// 4. 清理资源munmap(ptr, 4096);close(fd);shm_unlink("/my_shm"); // 由最后一个使用进程删除return 0;
}

常见错误处理

错误场景处理方法
shm_open 失败检查名称格式(是否以 / 开头)、权限是否足够
ftruncate 失败确保文件描述符有效且有写入权限
mmap 失败检查 length 是否与 ftruncate 设置一致


深刻理解共享内存

共享内存和命名管道一样,需要一个进程使用系统调用先行创建

共享内存也有和文件一样有拥有者所属组rwx权限,而且也是使用3个8进制表示
共享内存如果不指定权限,创建之后默认权限为000,即啥都干不了

systemV共享内存的key是由用户自己提供的
为什么要用户提供key,而不是内核自己维护?
因为物理内存中的共享内存不止一块
假设物理内存中一共3块共享内存,分别为共享内存1,2,3
进程A又创建了一块共享内存,是共享内存4

但是进程B想和进程A通信,进程B调用shmget的时候,得先知道进程A创建的共享内存是哪一块呀!是共享内存1,2还是3?

所以只能通过共享头文件把key告诉进程B
由操作系统自己维护的话,进程B调用shmget的时候,操作系统怎么知道该给进程B那块共享内存?
更何况要与它通信的进程可能都还没创建出来


为什用户提供的key,两个要通信的进程都能看到呢?
因为用户是通过:
①创建一个共享头文件,在两个进程编译之前,就在头文件里面定义了项目路径和项目id

②要通信的进程只需要在编译链接的时候都包含头文件,就可以看到相同的项目路径和项目id,运行的时候传给ftok就可以获得同一个key了


用户设置的key冲突了怎么办?

如果与已经存在的共享内存的key冲突了
就只能由程序员自己手动修改源代码

但是
用户设置的key一般也不是随便设置的
而是通过ftok函数生成,所以冲突的概率其实很低
在这里插入图片描述

如果使用ftok函数生成的key还冲突了,就只能由用户手动修改共享头文件中保存的项目id或者项目路径了


共享内存的生命周期

共享内存的生命周期随内核,即如果用户不使用系统调用释放它,直到操作系统关机之前它会一直存在
所以进程使用系统调用创建了共享内存,就必须再使用系统调用释放共享内存
不然就会内存泄露


共享内存的key和shmid的区别

①shmid:是只给用户[用户的进程]操作共享内存提供的唯一标识符

②key:是只给操作系统内核操作共享内存的唯一标识符


操作系统对物理内存进行任何操作时,都是以固定大小(一般是4kb)为基本单位进行操作的

如果用户申请的物理内存不是4kb的整数倍(比如用户申请了5kb),那么操作系统依然会以4kb的整数倍给用户(即给用户8kb的物理内存)但是会以类似权限的方式,让用户看不到,用不了那多出来的空间(即用户确实只能使用8-3=5kb的空间

因为操作系统不想背锅,用户让操作系统干嘛操作系统就干嘛



共享内存的特点

共享内存是最快的进程间通信的形式了

  1. 因为一旦共享内存区形成,进程使用共享内存就和使用自己进程地址空间的堆区内存一样了
    不再涉及操作系统内核,也就是进程通信时,不需要使用系统调用

  2. 因为拷贝次数少
    管道通信时,从外设来的数据要拷贝到进程的地址空间中
    要使用write把用户进程地址空间(栈区/堆区等)中的数据拷贝到内核文件缓冲区
    再使用read拷贝到进程B的进程地址空间中,再拷贝到外设
    需要4次拷贝
    而共享内存通信
    只需要把外设来的数据拷贝到共享区中的共享内存,进程B就直接可以通过页表看见这个数据了,不需要再拷贝了
    最后再拷贝到外设
    只需要2次拷贝


使用共享内存需要我们自己进行同步互斥

通过共享内存的原理我们可以知道

共享内存是真正地让两个进程共用同一份资源(内存块),并且它没有对共享内存做任何类似管道的保护机制

要对共享内存中的共享资源进行保护,就会有数据不一致问题
得由用户(进程)自己手动保护[利用信号量,条件变量]

http://www.xdnf.cn/news/6937.html

相关文章:

  • Go语言语法---输入控制
  • Node.js 源码架构详解
  • [system-design] ByteByteGo_Note Summary
  • 如何开发专业小模型
  • 强化学习赋能医疗大模型:构建闭环检索-反馈-优化系统提升推理能力
  • 数据库实验报告 数据定义操作 3
  • 【leetcode】逐层探索:BFS求解最短路的原理与实践
  • 使用Python和Selenium打造一个全网页截图工具
  • CSS- 4.1 浮动(Float)
  • Echart地图数据源获取
  • hysAnalyser 从MPEG-TS导出ES功能说明
  • 【C++详解】string各种接口如何使用保姆级攻略
  • Kotlin变量与数据类型详解
  • C++ - 仿 RabbitMQ 实现消息队列(2)(Protobuf 和 Muduo 初识)
  • DeepSeek 赋能军事:重塑现代战争形态的科技密码
  • Florence2代码实战
  • 初识计算机网络。计算机网络基本概念,分类,性能指标
  • 终端和shell , 以及XShell 用ssh命令登陆主机的过程
  • Spring三级缓存的作用与原理详解
  • 内网环境下如何使用ntpdate实时同步时间
  • java+selenum专题(一)
  • Java 与 面向对象编程(OOP)
  • dify知识库支持图文回复实践
  • 【Win32 API】 lstrcpynA()
  • 浮动静态路由配置实验
  • 使用 Cookie 实现认证跳转功能
  • 用Python绘制梦幻星空
  • 5.9/Q1,GBD数据库最新文章解读
  • 华三H3C交换机配置NTP时钟步骤 示例
  • STM32F103经SPI总线向写Micro SD卡