参考文章:

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);
在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
  • 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化
  • 将新建的虚拟区结构(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博客