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

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

在 Linux 里,如果将虚拟地址通过页表映射到物理页的映射关系称为正向映射,那么逆向映射(Reverse Mapping) 则是通过物理地址找到映射在物理页上的虚拟内存(VMA). 逆向映射在处理共享内存和页面回收时尤为重要. 一个物理内存页可能被多个进程共享,或者被映射到一个进程的多个虚拟地址, 当需要修改或回收这样的页面时,系统必须知道所有引用该物理页的页表项. 在 Linux 内存管理中,逆向映射的使用带来了一些显著的优点,但也伴随着一些缺点:

  • 提高内存回收效率: 逆向映射使得操作系统能够快速定位到所有映射到特定物理内存页的虚拟页,从而在进行内存回收时更加高效
  • 管理共享内存: 在多个进程共享内存的场景中,逆向映射有助于跟踪哪些虚拟页映射到同一物理页,便于管理和优化共享内存使用
  • 支持高级内存特性: 诸如拷贝写时(Copy-on-Write) 和内存压缩等高级内存管理技术,在有逆向映射的帮助下可以更高效地实现
  • 减少内存碎片: 逆向映射有助于内存压缩操作,减少内存碎片,从而提高内存利用率
  • 方便虚拟内存管理: 使操作系统更容易实现和优化诸如延迟分配和按需分页等虚拟内存管理策略
  • 增加内存开销: 为了实现逆向映射,操作系统需要维护额外的数据结构,这增加了内存的开销
  • 增加复杂性: 逆向映射的引入增加了内存管理的复杂性,可能导致更复杂的维护和调试工作
  • 性能开销: 在某些情况下,逆向映射的维护(如更新映射关系)可能带来性能开销,尤其是在频繁更新内存映射关系的场景中
  • 潜在的同步问题: 在多线程或多进程环境中,维护逆向映射的数据结构可能需要额外的同步机制,这可能导致性能下降

逆向映射在提高 Linux 内存管理效率和灵活性方面起到了重要作用,但同时也带来了一定的内存和性能开销,以及管理上的复杂性。在设计和实现内存管理策略时,需要平衡这些优缺点. 在 Linux 内存管理中,逆向映射的使用场景主要包括以下几个方面:

  • 共享内存管理: 当多个进程共享同一物理内存页时,逆向映射可以追踪所有映射到该物理页的虚拟地址. 这对于管理共享内存非常关键,尤其是在需要修改共享页面的访问权限或内容时
  • 内存回收(Page Reclaiming): 在内存紧张时,操作系统需要回收一些内存页以供其他进程使用. 逆向映射使得操作系统可以快速找到那些可以被回收的候选页面,特别是那些没有被频繁访问或可以被交换出去的页面
  • 拷贝写时机制: 在拷贝写时机制中,当一个进程尝试写入一个共享的内存页时,操作系统会创建该页面的一个副本以避免修改原始共享页. 逆向映射使得操作系统可以轻松识别所有映射到原始页面的虚拟地址,并据此更新这些映射以指向新的副本
  • 交换(Swapping): 在将内存页交换到磁盘时,操作系统需要知道哪些虚拟页映射到了被交换出去的物理页. 逆向映射提供了这种映射关系,从而在页面被再次访问时能够从磁盘中正确地恢复数据
  • 内存压缩(Memory Compaction): 为了减少内存碎片,Linux 可以进行内存压缩,将多个分散的小内存块合并成一个大的连续内存块. 逆向映射在这个过程中帮助识别那些可以移动的内存页
  • 虚拟内存管理优化: 通过逆向映射,操作系统可以更高效地管理虚拟内存,实现诸如延迟分配(Lazy Allocation) 和按需分配(Demand Paging)等高级内存管理技术

逆向映射的实现主要有两套逻辑,分别是匿名映射使用的逆向映射和文件映射使用的逆向映射,两套逻辑因为数据架构的差异导致实现逻辑上存在差异,也导致使用上也存在差异。对于文件映射使用的逆向映射,其在 mmap 系统将文件映射到地址空间时就为虚拟内存创建了逆向映射,可以通过上图逻辑,mmap 系统调用在 mmap_region 函数了调用 vma_link 函数建立链表和红黑树连接时,调用了 vma_interval_tree_insert 函数,该函数就是为文件映射建立了逆向映射. 而当进程首次访问文件映射的虚拟内存时,由于物理内存不存在触发缺页,缺页异常处理函数会为虚拟内存分配物理内存(PAGECACHE), 并将虚拟地址存储在物理页对应 STRUCT page 的 index 成员里,然后将 mapping 成员指向了文件对应的 STRUCT address_space.

对于匿名映射的虚拟内存,其在 mmap 系统调用时会为其分配逆向映射所需的 AV(STRUCT anon_vma),然后进程访问匿名内存时触发缺页异常,缺页异常处理函数为其分配物理内存,然后 page_add_new_anon_rmap 函数为其建立逆向映射,该函数将新物理页 STRUCT page 的 mapping 指向 AV,然后将虚拟地址存储在 index 成员.

匿名页与逆向映射

对于匿名映射,由于其不会与子进程共享内存的特点,那么其在实现逆向映射时需要记录映射到匿名页的 VMA 信息,匿名映射采用一颗红黑树维护这些信息,其实现逻辑如下:

  • AV: 每个 VMA 都有 anon_vma 成员,该成员属于 STRCUT anon_vma 数据结构,其内部维护了一颗红黑树, rb_root 指向红黑树的根节点. 此时 AV 所属的 VMA 称为父进程 VMA
  • AVC: AVC 属于 STRUCT anon_vma_chain 数据结构,其作为红黑树的节点加入到 AV 维护的红黑树里,其指向一个子进程的 VMA,其作为桥梁的作用,用于表示子进程的 VMA 与父进程 VMA 映射了同一个物理页
  • REMAP: 父进程 VMA 映射的物理页对应一个 STRUCT page 数据结构,该数据结构的 mapping 成员指向了父进程的 AV,并包含了 PAGE_MAPPING_ANON 标志
  • 插入: 当父进程通过 FORK 生成子进程时,内核会新建一个 AVC,然后将 AVC 的 vma 指向子进程的 VMA,然后将 AVC 插入父进程的 AV 红黑树里。另外将子进程 AV 的 parent 指向父进程的 AV
  • 查询: 匿名页 STRUCT page 通过 mmaping 成员找到 AV,然后遍历 AV 的红黑树既可知道映射到其身上的 VMA

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

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

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