图片无法显示,请右键点击新窗口打开图片

BiscuitOS 内存管理之分页大专题订阅入口

图片无法显示,请右键点击新窗口打开图片

在开启分页(Paging) 之后,用户进程只能看到自己的虚拟地址空间和内核空间,并且用户进程认为系统只有自己和内核线程在运行,无法看到其他进程,这样就形成了进程之间的资源隔离。但在有的场景,需要进程之间进行通行,其中一种方式就是通过共享内存实现.

共享内存(Shmem) 分类

图片无法显示,请右键点击新窗口打开图片

在 Linux 中,共享内存是进程间通信(IPC) 的一种机制,允许多个进程访问和操作同一块物理内存区域。这种机制可以实现高效的数据交换和通信,因为数据不需要在进程之间进行复制,只需要直接在共享内存区域中进行读写. 在 Linux 中共享内存有以下几种实现方式:

  • System V 共享内存: 该方法基于早期 UNIX 系统的 IPC 机制, 使用 shmget、shmat、shmdt 等函数来创建、附加和操作共享内存
  • POSIX 共享内存: 比 System V 更现代的方法,基于 POSIX 标准。它使用 shm_open、mmap、munmap 等函数,与 System V 共享内存不同,POSIX 共享内存是基于文件的,通常会在 “/dev/shm” 目录中看到这些共享内存的文件表.
  • 同名共享内存: 通过 mmap 系统调用映射文件时,可以使用 MAP_SHARED 标志来创建一个可以被多个进程共享的内存映射。虽然这通常用于文件映射,但也可以用于进程间的共享内存.
  • 匿名共享内存: 通过 mmap 系统调用分配一段内存,内存带有 MAP_SHARED 和 MAP_ANONYMOUS 属性,该方法从系统公共的内存池子中分配一段内存充当共享内存.
  • Tmpfs/HugeTmpfs: tmpfs 是一个基于内存的文件系统,通常挂载在 “/dev/shm” 目录。实际上 tmpfs 文件系统是通过 shmem 子系统实现的,这也是一个共享内存机制。通过在 tmpfs 上创建文件,进程可以使用 mmap() 来映射这些文件并在进程之间共享数据.
  • DMA-BUF 共享内存: 外设之间共享 DMA 内存的机制,可以指向多个设置之间直接共享 DMA 内存而无需 CPU 介入.

虽然共享内存的实现方式不同,但它们的共同点都是可以在不同进程之间通过共享内存交换数据. 共享内存的优点是高效(无需数据复制)和低延迟。但它也带来了挑战,例如需要同步和协调进程以避免数据竞争和不一致,通常使用互斥锁、信号量或其他同步原语来实现。

图片无法显示,请右键点击新窗口打开图片

相比匿名内存(Anonymous Memory)只能进程私有化使用,而共享内存(Shmem)通过将多个进程的虚拟内存映射到同一个物理内存上,并通过特定的同步手段实现资源的共享。匿名内存匿名共享内存虽然名字中带有匿名,但两种是不同类型的内存,匿名内存使用带 MAP_PRIVATE 和 MAP_ANONYMOUS 的 mmap 函数分配, 其通过匿名映射到一块物理内存上; 而匿名共享内存则使用带有 MAP_SHARED 和 MAP_ANONYMOUS 的 mmap 函数分配,且其通过文件映射到共享内存的伪文件上.

图片无法显示,请右键点击新窗口打开图片

Linux 里分配共享内存的函数提供,其中以 mmap 系统调用最为常见,mmap 提供了很多参数可以灵活设置共享内核,具体如下:

  • ARG0: MAP_VADDR 可以设置共享内存的虚拟地址,该值为 NULL 是代表分配一个随机的虚拟地址,当该值为指定值时代表从指定的地址分配虚拟内存,该地址可能已经被分配出去,使用时调用者需要确保虚拟地址被覆盖.
  • ARG1: MAP_SIZE 指明了需要分配共享内存的大小,长度为 PAGE_SIZE 的倍数,不能为 0.
  • ARG2: 指明了共享内存的访问权限,其值可以是这些标志的合集: PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行) 和 PROT_NONE(不可访问)
  • ARG3: 指明映射类型,但采用匿名映射的共享内存,该字段必须包含是 MAP_SHARED 和 MAP_ANONYMOUS 标志. 如果采用文件映射的共享内存,该字段只能包括 MAP_SHARED, 不能包括 MAP_ANONYMOUS 标志,否则变成匿名内存.
  • ARG4: 映射文件描述符,对于匿名映射的共享内存该这段必须为 -1. 对于文件映射的共享内存该字段必须是文件描述符 fd.
  • ARG5: 指明映射的物理区域,由于共享内存映射的随机物理内存,因此该字段必须为 0.

图片无法显示,请右键点击新窗口打开图片

在 Linux 里,共享内存的数据架构如上图,用户进程的虚拟区域(VMA) 设置为共享内存之后,其 vm_ops 设置为 shmem_vm_ops, 因此可以使用 vma_is_shmem() 判断是否为共享内存. 每个共享内存 VMA 的 vm_file 指向一个有名或者匿名的虚拟文件,虚拟文件对应的 struct inode,该 inode 是共享内存与文件系统和物理内存的桥梁,通过 i_sb 可以获得虚拟文件挂载点和 VFS 相关的信息,所有的虚拟内存都挂载 shm_mnt 下; 通过通过 mapping 获得虚拟文件与物理内存的映射关系,其使用 struct address_space 数据结构维护,其通过 i_pages 维护的 XARRAY 用于映射文件偏移与共享内存(PAGECACHE) 之间的关系,并通过 i_mmap 维护一颗区间树(特殊功能的红黑树),可以知道有多少进程的 VMA 映射到该虚拟文件. 另外每个共享物理内存的 mapping 指向了 MAPPING(struct address_space), 这样可以通过逆向映射知道共享物理内存上有哪些进程 VMA 映射.

图片无法显示,请右键点击新窗口打开图片

图片无法显示,请右键点击新窗口打开图片

BiscuitOS 内存管理之分页大专题订阅入口

图片无法显示,请右键点击新窗口打开图片