参考文章:

Linux——详解共享内存shared memory_就要 宅在家的博客-CSDN博客

什么是共享内存?在内存中的具体位置?共享内存相关API,mmap 的具体使用原理、以及C++应用案例?_共享内存在内存哪个区_JMW1407的博客-CSDN博客

目录

一、定义

1.1 定义

1.2 共享内存是在用户空间还是内核空间?——用户空间。

1.3 生命周期特

1.4 优点和缺点

二、映射方式:mmap机制

2.1原理(见上)

2.2 调用过程(三个阶段):

(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

(二)调用内核空间的系统调用函数mmap()(不同于库函数mmap()),实现文件物理地址和进程虚拟地址的一一映射关系

(三)真正数据写入发生在如果要读写阶段:进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

3.1.2、优点总结

三、映射方式:shmget(待补充)


一、定义

1.1 定义

共享内存是一种进程间通信的机制,即不同进程的虚拟内存空间(虚拟内存地址不需要相同),映射到相同的物理内存中。如果某个进程向共享内存写入数据,所做的改动将立即影响到】【可以同时访问】(加了锁的就不算“同时可以访问")同一段共享内存的任何其他进程。

 在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

1.2 共享内存是在用户空间还是内核空间?——用户空间。

回顾:我们知道,在使用消息队列或管道通信时,消息队列和管道都在内核空间中。所以客户端或服务器端在调用read/write时,会发生:

  • 调用read():OS把数据从用户态空间拷贝到内核空间。
  • 调用write(): OS把数据从内核空间拷贝到用户态空间。

即数据拷贝、用户态和内核态的转换。有开销。

在这里插入图片描述

 

与之相对,共享内存机制则是通过一个映射函数(如Linux提供了内存映射函数mmap) 把要共享的文件内容映射到进程的虚拟内存上, 通过对这段内存的读取和修改实现对文件的读取和修改。共享内存分配在用户空间中。所以进程可以以访问内存的方式对文件进行访问不需要其他系统调用(read,write)去操作。

在这里插入图片描述

 由上图可以看出,进程的虚拟地址空间,由多个虚拟内存区域构成。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分(共享区)

好了,知道了内存分区,那么OS怎么管理这些存分区呢?

在这里插入图片描述

 linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问。

分配一个新的共享内存,即通过mmap函数创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。这也是mmap函数的作用。

1.3 生命周期特

1.4 优点和缺点

优点:

  1. 不需要数据拷贝带来的开销。
  2. 大大提高了通信速度。

缺点:

  1. 多个进程竞争共同的资源(如同时读且删除、同时写)会造成数据的错乱。改进:用信号量互斥访问,保护共享资源。确保任意时刻只能有一个进程访问共享资源。
  2. 用于进程间通信时,共享内存本身不支持阻塞等待操作。这是因为当读端读取数据后,数据并不会在内存中清空。因此读端和写端可以同时访问内存空间,即全双工。因为共享内存本质是进程直接访问内存,无法主动停止读取,如果读端不加以限制,那么将持续读取数据。同理,写端也会持续写入数据。换句话说,共享内存本身没有访问控制。 
     

二、映射方式:mmap机制

2.1原理(见上)

2.2 调用过程(三个阶段):

mmap内存映射的实现过程,总的来说可以分为三个阶段:

(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

  1. 进程在用户空间调用库函数mmap()
    原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  2. wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==
    在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
  3. 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化
  4. 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

(二)调用内核空间的系统调用函数mmap()(不同于库函数mmap()),实现文件物理地址和进程虚拟地址的一一映射关系

  1. 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。
  2. 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。
  3. 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。
  4. 通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

(三)真正数据写入发生在如果要读写阶段:进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
 

注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

  1. 进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常
  2. 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
  3. 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。
  4. 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

3.1.2、优点总结
 

1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。

2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。

3、提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。

如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
4、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。

 

三、映射方式:shmget(待补充)

具体可以参考:

 Linux——详解共享内存shared memory_就要 宅在家的博客-CSDN博客

什么是共享内存?在内存中的具体位置?shmget的具体使用原理以及其他关联函数(shmat ( ),shmdt ( ),shmctl ( ))、以及C++应用案例?mmap和shm的区别?_shmget 原理_JMW1407的博客-CSDN博客

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐