目录
LazyALLOC 惰性分配导论
LazyALLOC 使用场景
LazyALLOC 方式分配匿名内存
LazyALLOC 方式分配共享内存
LazyALLOC 方式分配文件映射内存
LazyALLOC 方式分配 PFNMAP 映射内存
LazyALLOC 方式分配 MMIO 映射内存
LazyALLOC 方式分配 DMA 内存
LazyALLOC 方式分配 VMALLOC 内存
LazyALLOC 方式分配透明大页内存
LazyALLOC 方式分配 HugeTLB 大页内存
LazyALLOC 方式分配 DAX 内存
LazyALLOC 惰性分配导论
LazyALLOC 分配方式是用户进程分配虚拟内存的一种方式,其在分配时只分配虚拟内存,并不分配虚拟内存,而是进程首次访问虚拟内存时通过触发缺页异常,缺页异常处理函数负责分配物理内存和建立页表映射,待缺页异常处理返回之后,进程再次执行发生缺页异常的指令,此时进程可以正确访问虚拟内存. 与预分配内存相比,其具有如下特点:
- 内存利用率高: 由于物理内存只在实际需要时才分配,这提高了内存的整体利用率, 不会为未使用的内存区域占用物理资源
- 降低启动成本: 应用程序启动时,由于不需要立即分配所有内存,因此可以减少启动延迟
- 支持大内存空间: 允许分配大于物理内存大小的虚拟内存空间,从而为应用程序提供更大的地址空间
- 避免内存浪费: 如果程序分配了内存但未实际使用,惰性分配避免了不必要的物理内存浪费
- 缺页异常的开销: 每次物理内存分配都需要处理缺页异常,这可能会增加运行时的性能开销,特别是在内存频繁访问的情况下
- 延迟性能问题: 当程序访问未映射的内存时,会触发缺页异常,导致运行时延迟,可能影响性能
- 内存分配不确定性: 在物理内存紧张的情况下,可能无法满足后续的内存请求,导致内存分配失败
- 交换空间依赖: 在物理内存不足时,系统可能需要使用交换空间(Swap Space),这可能导致显著的性能下降
因此开发者在选择LazyALLOC 分配方式之前需要对自己的场景充分考虑其带来的优点和缺点. 惰性分配适用于多数普通应用程序,特别是那些内存使用模式不确定或分布广泛的情况。它可以有效地使用有限的物理内存资源,并提供灵活的内存管理。然而对于性能敏感的应用程序,特别是那些对延迟有严格要求的情况,可能需要考虑更精细的内存管理策略,以减少缺页异常处理的开销.
Linux 提供的 mmap 系统调用可以实现 LazyALLOC 分配方式重要接口, 应用程序可以使用该接口为其分配虚拟内存,但不分配物理内存,只有进程首次访问虚拟内存时,才通过缺页异常处理函数分配物理内存和建立页表. mmap 系统调用为了能够实现 LazyALLOC 方式的分配虚拟内存,那么 6 个参数应该按如下要求进行设置:
- 参数一: 该参数可以设置从进程地址空间的指定位置分配虚拟内存,如果应用程序有这个需求可以通过该参数实现.
- 参数二: 该参数用于设置分配虚拟内存的大小
- 参数三: 该参数用于设置虚拟内存的访问权限,目前支持 PROT_READ、PROT_WRITE、PROT_EXEC, 三者可以混合搭配使用,也支持 PROT_NONE 将虚拟内存设置为即不可读也不可写, 但这些搭配也要符合系统的要求.
- 参数四: 该参数用于选择虚拟内存的映射方式和特殊需求,参数通过指定的标志进行选择,例如 MAP_SHARED 表示采用共享映射、MAP_PRIVATE 表示采用匿名映射等,对于 LazyALLOC 最重要的不能带 MAP_POPULATE 标志, 否则就是预分区的配置.
- 参数五: 当虚拟内存用于映射文件,那么需要将文件的 FD 填入该位置,否则该参数保持 “-1”.
- 参数六: 该虚拟内存需要映射到指定物理区域时,可以将指定物理区域的起始地址填入到该位置; 反之如果支持映射到随机的物理页上,那么该参数设置为 0.
LazyALLOC分配的虚拟内存支持映射到多种物理区域上,有的可以通过采用合适的 mmap 参数就可以实现,有的需要借助特殊系统提供的文件,有的则需要设备驱动提供专门的接口,但无论采用何种接口都需要 mmap 系统调用完成,那么对于不同类型的物理内存其在惰性分配场景下,mmap 的参数逻辑如下:
- OSMEM: 对于映射系统管理的物理内存,只需在 mmap 系统调用里不采用 MAP_POPULATE 标志即可, 其他参数任意
- RSVDMEM: 对于映射系统预留物理内存,需要通过设备驱动提供的特殊文实现,由设备驱动实现缺页逻辑,最后由于是通过文件映射的方式,因此不能采用 MAP_ANONYMOUS 标志.
- MMIO: 对于映射 MMIO 区域,需要通过设备驱动提供的特殊文件实现,并由设备驱动实现缺页逻辑,由于是通过文件映射的方式,因此不能采用 MAP_ANONYMOUS 标志.
- PMEM: 对于映射 PMEM 内存,需要采用 DAX 方式进行映射,因此需要借助特殊的文件系统和文件,mmap 系统调用不采用 MAP_POPULATE 标志,另外由于通过文件映射的方式,因此不能采用 MAP_ANONYMOUS 标志.
- HUGELTB: 对于映射 HUGETLB 物理大页,可以通过匿名的方式映射 HUGETLB 大页,那么需要添加 MAP_ANONYMOUS 和 MAP_HUGETLB 标志; 反之如果通过 Hugetlbfs 文件系统的方式映射 HUGETLB 大页,那么需要打开指定的文件即可. 无论采用哪一种映射 HUGETLB 物理大页,如果采用 LazyALLOC 方式分配内存时,禁止使用 MAP_POPULATE 标志.
- THP: 对于映射透明大页,由于 THP 有很多中类型,这里以匿名透明大页为例进行说明,需要采用 MAP_ANONYMOUS 和 MAP_PRIVATE 标志,另外虚拟内存大小要超过 2MiB,并且如果指定虚拟地址,那么虚拟地址也要按 2MiB 对齐,最后就是采用 LazyALLOC 方式映射禁止带上 MAP_POPULATE 标志.
- VMALLOC: VMALLOC 分配的内存可以与用户进程实现共享,那么用户进程需要基于私有的驱动模块来实现 VMALLOC 的映射,此时驱动模块会提供相应的文件,因此不能带有 MAP_ANONYMOUS 标志,另外缺页逻辑有设备驱动提供.
- DMA: DMA 内存的特点就是大块连续的物理内存,因此进程可以借助私有驱动程序将 DMA 内存映射到进程地址空间,并且设备驱动负责提供缺页逻辑,因此 mmap 不能带 MAP_ANONYMOUS 和 MAP_POPULATE 标志.
- PAGECACHE: PAGECACHE 其实对应的是文件映射,由于文件映射的特点,因此不能待 MAP_ANONYMOUS 标志,文件映射为了实现惰性分配禁止带上 MAP_POPULATE.
- SHMEM: 共享内存支持文件映射方式和匿名方式进行映射,当使用匿名方式时需要带上 MAP_SHARED 和 MAP_ANONYMOUS 标志,而对于文件映射方式则仅仅带上 MAP_SHARED 标志即可。共享内存为了实现惰性分配,那么禁止带上 MAP_POPULATE 即可.