在操作系统中,内存管理提供了将部分内存动态分配给进程,并在不再需要时释放内存以供重复使用。现代操作系统使用地址空间来隔离进程内存。
地址空间表示进程用于引用内存的一组虚拟地址。虚拟地址直接映射到实际地址。Fuchsia 使用 VMAR(虚拟内存地址区域)来表示地址空间。
VMAR、映射和 VMO
在 Fuchsia 中,每个进程都有一个根 VMAR,并且可以划分为 VMAR 和映射的层次结构。映射指向底层 VMO(虚拟内存对象):
VMAR 和虚拟机映射
VMAR 是特定进程地址空间中连续的虚拟地址范围:
- VMAR 可以具有不重叠子区域的子 VMAR 和/或虚拟机映射
- 它们将保护位应用于内存的某个部分(读写、可执行文件等)
- 它们具有子项的 WAVL 树,以实现高效搜索
虚拟机映射表示地址空间中的“映射的”范围,即由物理页面支持的虚拟地址:
- 虚拟机映射没有子级
- 它们从 VMO 映射一系列页面
- 成功的网页搜索到此结束
以下是可用的 VMAR 和虚拟机映射系统调用:
zx_vmar_allocate()
- 创建新的子级 VMARzx_vmar_map()
- 将 VMO 映射到进程zx_vmar_unmap()
- 从进程中取消映射内存区域zx_vmar_protect()
- 调整内存访问权限zx_vmar_destroy()
- 销毁 VMAR 及其所有子级
另请参阅虚拟内存地址区域参考。
虚拟机操作系统
VMO 是内存字节的容器。 它们存储可通过虚拟机映射映射到地址空间的物理页面。以下是 VMO 系统调用:
zx_vmo_create()
- 创建新的 VMOzx_vmo_create_child()
- 创建新的子级 VMOzx_vmo_create_physical()
- 创建新的物理 VMOzx_vmo_get_size()
- 获取 VMO 的大小zx_vmo_op_range()
- 对一系列 VMO 执行操作zx_vmo_read()
- 从 VMO 读取zx_vmo_replace_as_executable()
- 创建 VMO 的可执行版本zx_vmo_set_cache_policy()
- 为 VMO 所保留的页面设置缓存政策zx_vmo_set_size()
- 调整 VMO 的大小zx_vmo_write()
- 写入 VMO
另请参阅虚拟内存对象参考文档。
虚拟内存管理器 (VMM)
虚拟内存管理器 (VMM) 负责维护进程地址空间,包括:
- 为已映射的虚拟地址范围提供指向后备物理页面的指针。
- 确保地址范围已设置正确的访问保护位。
为此,它会管理 VMAR、虚拟机映射、VMO 和硬件页面表之间的关系。另请参阅 VMM 源代码。
当进程启动时,其整个地址空间表示为一个 VMAR。随着地址空间的不同部分被映射,VMAR 层次结构树会进行填充。随着 VMAR 和虚拟机映射的创建和销毁,树中的节点会被创建和销毁。
节点表示 VMAR 或虚拟机映射:
- VMAR 指向子级列表,这些子级是父级 VMAR 地址范围内的其他 VMAR 和虚拟机映射。
- 虚拟机映射指向的 VMO 内映射在该地址范围内的范围。
详细了解如何通过根 VMAR 直观呈现内存用量。
VMM 使用地址空间布局随机化来控制在地址空间内创建新 VMAR 的地址范围。aslr.entropy_bits
内核命令行选项可用于控制随机化中熵的位数。熵越高,使地址空间越稀疏,VMAR 和虚拟机映射就越分散。
物理内存管理器 (PMM)
物理内存管理器 (PMM) 将系统上的所有可用物理内存 (RAM) 划分为各个页面,并管理页面的情况。它负责在需要时为 VMO 提供免费物理页面。
VMO 是需求分页,即按需填充(已提交)其页面。页面会在写入时提交。在此之前,未提交的页面由系统上的单例物理零页面表示。这样可以避免为尚未访问过的页面或只被读取的页面分配内存,从而避免不必要的内存分配。
VMO 还可以由用户空间分页器提供支持,该分页器会按需填充 VMO 中的特定内容,例如,从磁盘上的文件中读取的内容。了解如何编写用户分页器。
示例:映射 VMO
如需映射 VMO,请执行以下操作:
使用
zx_vmar_root_self()
获取进程的根 VMAR 的句柄,例如:zx_handle_t vmar = zx_vmar_root_self();
使用
zx_vmo_create(...)
创建 VMO。这将返回 VMO 句柄,例如:const size_t map_size = zx_system_get_page_size(); zx_vmo_create(map_size, 0, &vmo);
使用
zx_vmar_allocate(...)
在父 VMAR 中创建子 VMAR。此操作会返回一个 VMAR 句柄及其起始地址,例如:const size_t region_size = zx_system_get_page_size() * 10; zx_vmar_allocate(vmar, ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE, 0, region_size, ®ion, ®ion_addr);
这是可选步骤;您还可以将 VMO 直接映射到父级。
使用
zx_vmar_map(...)
在 VMAR 内映射 VMO。这会返回现在映射到 VMAR 的 VMO 的起始地址,例如:zx_vmar_map(region, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0, map_size, &map_addr);
示例:访问 VMO 映射
在前面的示例中,假设 zx_vmar_map()
返回了 map_addr
0x4000。如需访问映射的地址 0x4000,代码可能如下所示:
zx_vmar_map(...&addr); // addr = 0x4000
auto arr = reinterpret_cast<int*>(addr);
arr[0] = 1; // page fault
指针解引用会导致页面错误。 为了解决页面错误,内核会执行以下操作:
- 在进程的 VMAR 树中搜索地址 0x4000,从
root_vmar
开始,直到找到包含该地址的虚拟机映射。 - 虚拟机映射将包含对 VMO 的引用,以及 VMO 页面列表中对应于映射的第一个地址的偏移量。查找与地址 0x4000 对应的 VMO 偏移量。
- 如果 VMO 在该偏移量处没有页面,请分配新页面。 假设此页面的实际地址为 0xf000。
- 通过
ArchVmAspace
将虚拟地址 0x4000 到物理地址 0xf000 的转换添加到硬件页表,该页表表示架构特定的页面表,用于跟踪 MMU 使用的虚拟地址到物理地址的映射。