目录

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


X86 Virtual Memory Map

计算机硬件架构中存在一种硬件 MMU(Memory Management Unit), 通常称为内存管理单元,有时也称为分页内存管理单元 PMMU(Paged memory management unit), 负责处理 CPU 内存访问请求的计算机硬件。MMU 主要包含: 虚实地址翻译、访问权限控制。另外在计算机中存在实时模式和保护模式,当 CPU 处在实时模式下,CPU 看到的内存就是地址总线上的物理内存,那么 CPU 可以直接访问物理内存; 当计算机开启保护模式,MMU 分页功能便随之启动,此时 CPU 看到的内存是一块连续的线性空间,那么称这块空间为虚拟内存, CPU 访问虚拟内存的地址称为虚拟地址。虚拟内存的大小与计算机的位宽有关,例如 32 位系统虚拟内存的范围是 [0, 4G), 而 64 位系统虚拟内存的范围是 [0, 2^64). 虚拟内存的特点就是连续且范围巨大。

系统将虚拟内存划分成两个区域,用户进程使用的虚拟内存区域称为用户空间(Userspace), 内核使用的虚拟内存区域称为内核空间(Kernel Space), 由于 MMU 的存在,用户进程访问内核空间或者内核访问用户空间都会引起系统错误。在不同架构中两个区域的>大小划分有所不同:

在 IA32 架构中,32 位地址总线的寻址能力为 [0, 4G), 内核将虚拟内存从 PAGE_OFFSET 处分作两部分,[0, PAGE_OFFSET) 为用户进程所使用的虚拟内存,而 [PAGE_OFFSET, 4G) 为内核使用的虚拟内存。PAGE_OFFSET 可能是 2G 或者 3G,经典的分割是 [0,3G) 是用户空间 [3G, 4G) 为内核空间.

在当前 AMD64/X64 架构中 64 位虚拟地址仅实现 48 位,剩下的高 16 位仅仅是作为符号拓展,组成最终的 64 位虚拟地址。高 16 位称为 Sign Extension 域,而低 48 位称为线性地址域,这样的 64 位虚拟地址称为 canonical-address 地址形式。Sign Extension 域要么全为 1 或者 0,对于 Sign Extension 不全为 0 或 1 的地址称为 Noncanonical-address.

当前 AMD64/X64 架构中 Sign Extension 域为 16 位,那么虚拟内存会分作三部分,当 Sign Extension 全为 0 的区域称为 Canonical “Lower half”, 也就是用户空间,128TiB 用户空间 [0, 0x00007fff ffffffff) 占据了虚拟内存的底部. Sign Extension 全为 1 的区域称为 Canonical “High half”, 也就是内核空间,128TiB 内核空间 [0xffff8000 00000000, 0xffffffff ffffffff) 占据了虚拟内存顶部的位置。Sign Extension 域不全为 0 或 1 的范围 [0x00008000 0000000, 0xffff8000 00000000) 称为 “Noncanonical Address Space”, 落在这个区域的虚拟地址都是非法地址.

随着技术的不断发展,AMD64/X64 64 位虚拟地址可以实现 56 根或者 64 根都能寻址,那么虚拟内存的 Canonical 区域的 “Lower Half” 和 “Higher Half” 将像 IA32 架构一样链接在一起,那么系统的内核空间和用户空间的范围将大大增加.

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


I386 32bit Virtual Memory Map

i386 虚拟地址空间布局(右键新标签页查看大图): 当 i386 架构支持 32-bit 页表,其支持 32 位线性地址空间和 32 位物理地址空间,因此 i386 架构虚拟地址空间长度为 4G. 其中用户空间长度 3Gig,内核空间占据末端 1Gig,并且支持 4MiB 和 4KiB 两种尺寸的物理页. 虚拟地址空间各区域的范围:

  • 用户空间(Userspace): 每个进程 [0, 3G) 的虚拟内存区域,其中 STACK_TOP 是用户空间堆栈的栈底. 每个进程都只能看到自己的 3G 线性地址空间,无法看到其他进程的 3G 用户空间.
  • 内核空间(Kernel Space): 内核线程使用的虚拟地址空间,其占据末端 1G 空间,起始虚拟地址为 0xC0000000, 内核空间又被划分成不同功能区域.
  • 线性映射区(Direct Mapping Area): 线性映射区是内核将 0xC0000000 开始的 896M 虚拟内存映射到物理起始地址 0 的 896M 物理内存上,形成了虚拟连续且物理也连续的空间。
  • 内核镜像区(Kernel Text Mapping): 该区域用于映射内核镜像,其范围是 [PAGE_OFFSET, PAGE_OFFSET + KERNEL_IMAGE_SIZE).
  • 高端内存(high_memroy): 高端内存是没有被线性区映射的物理内存,内核访问高端内存需要先建立页表再访问,其范围 [high_memory, 4G).
  • VMALLOC 区域: VMALLOC 分配器管理的区域,区域特点是虚拟地址连续但物理地址不连续的区域,其位于高端内存起始地址 VMALLOC_OFFSET 之后的地方,那么其范围: [VMALLOC_START, VMALLOC_END)
  • MODULE 区域: 内核模块自身使用虚拟内存区域,该区域与 VMALLOC 区域重叠,因此内核也是为 MODULE 动态创建页表并映射到随机物理内存上.
  • PKMAP: 该区域预先分配一些物理页,以备在内核运行期间特殊情况使用,如异常处理、中断处理和其他紧急内核任务, 其范围是 [PKMAP_BASE, PKMAP_BASE + PAGE_SIZE).
  • LDT Remap for PTI Area: 该区域是用于重新映射 LDT 的一个特殊区域,并用于实现页表隔离机制(Page Table Isolation), 用于减轻特定 CPU 漏洞(Spectre 和 Meltdown)的攻击.
  • CPU Entry Area: 该区域为每个 CPU 提供了一个独立的、预分配的内存段,其中包含了 CPU 启动和初始化所需的代码,以确保每个 CPU 可以正确进入操作系统的执行环境,其范围是:
  • FIXADDR Area: 固定映射区域,区域的虚拟内存用作特殊的目的,例如 Permanent mapping Area 用于永久(持久)映射使用, 又如 KMAP 区域用于临时映射,满足特定需求,其范围是 [FIXADDR_START, FIXADDR_TOP)
  • Tail Hole: 最后 4K 区域预留作为 Hole.

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


X86-64 4-level PageTable Virtual Memory Map

X86 虚拟地址空间布局(右键新标签页查看大图): 当 X86-64 架构支持 4 级页表,其支持 48 位线性地址空间和 52 位物理地址空间,用户空间长度达 128TiB,内核空间占据末端 128TiB,并且支持 1Gig、2MiB 和 4KiB 三种尺寸的物理页. 下图虚拟地址空间各区域的范围:

  • 用户空间(Userspace): 每个进程 [0, 128TB) 的虚拟内存区域,其中 STACK_TOP 是用户空间堆栈的栈底. 每个进程都只能看到自己的 128TB 线性地址空间,无法看到其他进程的 128TB 用户空间.
  • 内核空间(Kernel Space): 内核线程使用的虚拟地址空间,其占据末端 128T 空间,起始虚拟地址为 0xffff800000000000, 内核空间又被划分成不同功能区域.
  • Guard Hole: 内核空间第一个区域,与 Non-Canonical 区域相连,大小为 8TiB。其作用是为内核空间创建一个保护边界,以便检测内存越界访问和栈溢出等错误。当程序访问 Guard Hole 区域时就会触发硬件异常,其范围是: [GUARD_HOLE_BASE_ADDR, LDT_BASE_ADDR)
  • LDT Remap for PTI Area: 该区域是用于重新映射 LDT 的一个特殊区域,并用于实现页表隔离机制(Page Table Isolation), 用于减轻特定 CPU 漏洞(Spectre 和 Meltdown)的攻击. 其范围是: [LDT_BASE_ADDR, LDT_END_ADDR)
  • 线性映射区(Direct Mapping Area): 线性映射区是内核将 PAGE_OFFSET 起始的虚拟内存连续建立页表,映射到连续的物理内存上,形成了虚拟连续且物理也连续的空间。其大小为 64TiB,对应 [PAGE_OFFSET, PAGE_OFFSET + 64TB).
  • VMALLOC 区域: VMALLOC 分配器管理的区域,区域特点是虚拟地址连续但物理地址不连续的区域,长度为 32TiB 且范围: [VMALLOC_START, VMALLOC_END)
  • SPARSE VMEMMAP Area: 该区域是 SPARSE 内存映射区域,用于存储物理页的 struct page 数据结构. 其范围是: [VMEMMAP_START, VMEMMAP_START + 1TB)
  • KSAN Shadow Memory: 该区域是在内核运行时跟踪每个分配的内存块,并为每个字节分配一个影子字节,该字节的值表示分配的内存是否被访问过以及如何访问的信息,以此 KSAN 可以在内核中检查到访问越界或使用未初始化内存的问题. 其范围是: [KASAN_SHADOW_START, KASAN_SHADOW_END)
  • CPU Entry Area: 该区域为每个 CPU 提供了一个独立的、预分配的内存段,其中包含了 CPU 启动和初始化所需的代码,以确保每个 CPU 可以正确进入操作系统的执行环境,其范围是: [CPU_ENTRY_AREA_BASE, CPU_ENTRY_AREA_BASE + 0.5T)
  • Fixup Stacks: ESP fixup 堆栈固定区域,其范围是: [0xffffff0000000000, 0xffffff0000000000 + 0.5T)
  • EFI Region 映射区域: 区域的作用就是在内核中映射这些 EFI 内存映射寄存器,以便内核能够访问和操作这些信息. EFI 内存映射寄存器是一些用于访问系统内存映射信息的寄存器。它们包含了有关物理内存、I/O 端口、PCI 设备等映射的信息,操作系统和引导加载程序可以通过这些寄存器获得硬件映射信息. 区域的范围是: [EFI_VA_END, EFI_VA_START)
  • 内核镜像区(Kernel Text Mapping): 该区域用于映射内核镜像,其范围是 [__START_KERNEL_map, __START_KERNEL_map + KERNEL_IMAGE_SIZE).
  • MODULE 区域: 内核模块自身使用虚拟内存区域,该区域的范围是: [MODULES_VADDR, MODULES_END)
  • FIXADDR Area: 固定映射区域,区域的虚拟内存用作特殊的目的,例如 Permanent mapping Area 用于永久(持久)映射使用, 又如 KMAP 区域用于临时映射,满足特定需求,其范围是 [FIXADDR_START, VSYSCALL_ADDR)
  • Leacy SYSCALL: 特殊的系统调用区域,已提供给所有进程共同使用,其范围是: [VSYSCALL_ADDR, VSYSCALL_ADDR + PAGE_SIZE)
  • Tail Hole: 最后 2M 区域预留作为 Hole.

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


X86-64 5-level PageTable Virtual Memory Map

X86 虚拟地址空间布局(右键新标签页查看大图): 当 X86-64 架构支持 5 级页表,其支持 57 位线性地址空间和 52 位物理地址空间,用户空间长达 64PB,内核空间占据末端 64PB,并且支持 1Gig、2MiB 和 4KiB 三种尺寸的物理页. 下图虚拟地址空间各区域的范围:

  • 用户空间(Userspace): 每个进程 [0, 64PB) 的虚拟内存区域,其中 STACK_TOP 是用户空间堆栈的栈底. 每个进程都只能看到自己的 64PB 线性地址空间,无法看到其他进程的 64PB 用户空间.
  • 内核空间(Kernel Space): 内核线程使用的虚拟地址空间,其占据末端 64PB 空间,起始虚拟地址为 0xff00000000000000, 内核空间又被划分成不同功能区域.
  • Guard Hole: 内核空间第一个区域,与 Non-Canonical 区域相连,大小为 4PB。其作用是为内核空间创建一个保护边界,以便检测内存越界访问和栈溢出等错误。当程序访问 Guard Hole 区域时就会触发硬件异常,其范围是: [GUARD_HOLE_BASE_ADDR, LDT_BASE_ADDR)
  • LDT Remap for PTI Area: 该区域是用于重新映射 LDT 的一个特殊区域,并用于实现页表隔离机制(Page Table Isolation), 用于减轻特定 CPU 漏洞(Spectre 和 Meltdown)的攻击. 其范围是: [LDT_BASE_ADDR, LDT_END_ADDR)
  • 线性映射区(Direct Mapping Area): 线性映射区是内核将 PAGE_OFFSET 起始的虚拟内存连续建立页表,映射到连续的物理内存上,形成了虚拟连续且物理也连续的空间。其大小为 32PB,对应 [PAGE_OFFSET, PAGE_OFFSET + 32PB).
  • VMALLOC 区域: VMALLOC 分配器管理的区域,区域特点是虚拟地址连续但物理地址不连续的区域,长度为 12.5B 且范围: [VMALLOC_START, VMALLOC_END)
  • SPARSE VMEMMAP Area: 该区域是 SPARSE 内存映射区域,用于存储物理页的 struct page 数据结构. 其范围是: [VMEMMAP_START, VMEMMAP_START + 0.5PB)
  • KSAN Shadow Memory: 该区域是在内核运行时跟踪每个分配的内存块,并为每个字节分配一个影子字节,该字节的值表示分配的内存是否被访问过以及如何访问的信息,以此 KSAN 可以在内核中检查到访问越界或使用未初始化内存的问题. 其范围是: [KASAN_SHADOW_START, KASAN_SHADOW_END)
  • CPU Entry Area: 该区域为每个 CPU 提供了一个独立的、预分配的内存段,其中包含了 CPU 启动和初始化所需的代码,以确保每个 CPU 可以正确进入操作系统的执行环境,其范围是: [CPU_ENTRY_AREA_BASE, CPU_ENTRY_AREA_BASE + 0.5T)
  • Fixup Stacks: ESP fixup 堆栈固定区域,其范围是: [0xffffff0000000000, 0xffffff0000000000 + 0.5T)
  • EFI Region 映射区域: 区域的作用就是在内核中映射这些 EFI 内存映射寄存器,以便内核能够访问和操作这些信息. EFI 内存映射寄存器是一些用于访问系统内存映射信息的寄存器。它们包含了有关物理内存、I/O 端口、PCI 设备等映射的信息,操作系统和引导加载程序可以通过这些寄存器获得硬件映射信息. 区域的范围是: [EFI_VA_END, EFI_VA_START)
  • 内核镜像区(Kernel Text Mapping): 该区域用于映射内核镜像,其范围是 [__START_KERNEL_map, __START_KERNEL_map + KERNEL_IMAGE_SIZE).
  • MODULE 区域: 内核模块自身使用虚拟内存区域,该区域的范围是: [MODULES_VADDR, MODULES_END)
  • FIXADDR Area: 固定映射区域,区域的虚拟内存用作特殊的目的,例如 Permanent mapping Area 用于永久(持久)映射使用, 又如 KMAP 区域用于临时映射,满足特定需求,其范围是 [FIXADDR_START, VSYSCALL_ADDR)
  • Leacy SYSCALL: 特殊的系统调用区域,已提供给所有进程共同使用,其范围是: [VSYSCALL_ADDR, VSYSCALL_ADDR + PAGE_SIZE)
  • Tail Hole: 最后 2M 区域预留作为 Hole.

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


Userspace Memory Map

用户空间(Userspace) 是用户进程运行时的虚拟内存,当进程运行时会将虚拟内存划分成不同的区域,每个区域用于存储不同的数据或执行特定的任务。每个区域的具体含义如下:

  • [0x0000000, __executable_start) 区域为预留区域
  • [__executable_start, 0x00007FFFFFFFFFFF) 用户进程可以使用的虚拟内存
  • .text 区域存储了进程的代码段
  • .data 区域存储了进程的数据段
  • .bss 区域为进程的 .bss 段, 初始化为 0 的数据加载到该段
  • Heap 进程的堆,向高地址(向上)生长,可以通过 malloc() 和 brk() 为进程小粒度的虚拟内存
  • MMAP 区域通过 mmap() 和 malloc() 向进程提供大块连续的虚拟内存,其向低地址(向下)生长,分配的虚拟内存可以用来映射共享库或者文件等.
  • Stack 区域为进程的堆栈,其向下生长,栈底是栈区域的起始地址,栈顶是栈最新扩展的区域,栈利用先进后出的模式存储进程运行时的临时数据
  • argv/environ 区域用于存储进程运行时的参数和环境变量相关的信息.
  • Kernel Space 为进程看到的内核空间,不同架构存在位置差异,在 IA32 架构内核空间和用户空间相邻,但在有的 X86 架构两者并不相邻,但 PAGE_OFFSET 之后的虚拟内存就是内核空间.

系统初始化正常运行时,系统存在多个用户进程同时运行的情况,由于进程隔离性的存在,每个进程都有各自的地址空间,并且每个进程只看得到自己的虚拟内存和内核空间,因此进程运行时会认为系统只有自己和内核线程在运行。由于该特性的存在,所有用户进程看到内核空间都是一致的,因此在进程切换的时候内核空间不需要切换,另外对于不同的进程就算用户空间虚拟地址相同,但两个虚拟内存对于的内容完全不是一个东西.

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


FIXMAP Mapping Map

FIXMAP 区域 可以理解为系统预留虚拟内存区域,内核将 [FIXADDR_START, FIXADDR_TOP) 的虚拟内存区域进行预留给特定的分配器或任务使用,内核的其他子系统就不能使用这部分虚拟内存。FIXMAP 区域包括了固定映射分配器维护的区域,其内部又包括了Permanent Mapping Allocator(永久映射分配器)KMAP Mapping Allocator(临时映射分配器), 以及 Early I/O and Reserved Memory Allocator(早期 IO/预留内存分配器)。因此可将该区域的虚拟内存已经预留给特定功能使用,这些功能各自维护各自的虚拟内存,然后各自获得物理内存并独立建立页表,以此使用各自的内存,最后还可以根据各自的特点进行内存释放回收.

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


X86-64 Physical Address Space Map

将 CPU 地址总线可以寻址的范围称为物理地址空间(Physical Address Space), 且寻址的地址称为物理地址(Physical Address), 那么物理地址空间将会是一块连续的线性区域. 在 Intel X86 架构中,将物理地址空间也称为存储域,其主要由三部分组成:

  • 第一部分是 DDR 控制器映射的 DDR 域
  • 第二部分是 PCI 总线域映射到存储域的 PCI 总线地址
  • 第三部分是设备内部存储空间映射到存储域的区域

将第二部分和第三部设备 IO 映射到存储域的,统一称为 MMIO(Memory Mapping IO)。Intel X86 架构也通过 Host PCI 主桥维护一颗或多颗 PCI 总线,每条 PCI 总线构成独立的空间称为PCI 总线域.

在 X86 架构中,存储域的空间长度和 PCI 总线域的长度是相同的,并且 DDR 域的地址可以映射到存储域,也可以映射到 PCI 总线域,同理 PCI 域的地址也可以映射到存储域,且该地址在存储域上称为 PCI 总线地址

上图是一个典型的 X86 架构物理地址空间分布图,可以看到物理地址空间并不等效于 DDR 域空间,物理空间被划分成了几个大区域,因此并不是物理地址空间都是内存条的内存。


DDR Physical Mapping

DDR 内存控制器可以看到完整的 DDR 域空间,并且是一块连续的地址空间, 那么内存条内存是如何映射到物理地址空间呢? 首先了解一下 DDR 域空间, DDR 域空间一般分作 3 类:

  • 第一类是直接映射到物理地址空间的区域
  • 第二类是重映射到物理地址空间的区域
  • 第三类是系统进入 SMM 模式才可以访问的区域

DDR 域的 [0x00000000, Reclaim Base] 区域直接映射到了物理地址空间 0x00000000 开始之后的区域,DDR 域 [0x100000000, MESEG Base] 区域直接映射到物理地址空间的 [0x100000000, Recliam Base] 区域。DDR 域接着采用 Reclaim 技术将 DDR 域的 [Reclaim Base, 0x100000000] 映射到物理地址空间 Reclaim Base 之后的区域.


DOS Mapping Area

最低地址区域称为 DOS RAM,其长度为 1MiB,用于 Legacy BIOS 使用的物理区,

  • [0x00000, 0xA0000): 最前面的 640KiB 常规内存也是 DDR 内存的一部分,最前 1KiB 用于存储 BIOS 的中断向量表,随后的 1KiB 被用作 BIOS 数据区;
  • [0xA0000, 0xC0000): 区域映射是显卡的显示 RAM, 也属于 MMIO 不是 DRAM 内存
  • [0xC0000, 0xD0000): 区域映射显卡的 ROM 还有硬盘、网卡的 ROM,也属于 MMIO 不是 DRAM 内存
  • [0xD0000, 0xE0000): 区域可以映射设备的 ROM,如果没有映射就是 DRAM 的内存
  • [0xE0000, 0xF0000): 区域为扩展的 BIOS 区域,输入 DRAM 内存
  • [0xF0000, 0x100000): 区域为常规 BIOS 区域,用于映射 BIOS 芯片上,CPU 的第一句指令 0xFFFF0 就是跳转到该区域.

Main Memory Mapping

物理地址空间中存在两段物理内存区域,分别是 [0x00100000, TOLM) 区域和 [0x100000000, TOUUD) 区域。两段区域都是可用物理内存区域:

  • [0x00F00000, 0x01000000) 区域为传统的 Windows ISA 黑洞(ISA Hole)
  • Extended SMRAM Space(TSEG) 扩展 SMM 内存.
  • Internal Graphics Memory
  • TOLUD 为 DDR 映射到物理地址空间的低端区域最大值,TOUUD 为 DDR 映射到高端区域最大值, 其中也包括 Reclaim 的内存.

MMIO Space Map

物理地址空间中存在两段 MMIO 区域,分别是 [TOLUD, 4Gig) 和 [TOUUD, 512Gig) 两片区域,这些区域可以用来映射 PCI/PCIe 外设的 BAR,也可以用来映射 GPU 的 Rangs,具体如下:

  • [0xFFE00000, 0x100000000): BIOS 内容映射的地址,它的大小可调.
  • [0xFEC00000, 0xFED00000): APIC 配置空间,其映射了 Per-CPU 的 Local APIC 和 IOAPIC 内部寄存器
  • [0xFEE00000, 0xFEF00000): PCIe/DMI 设备发送 MSI/MSIX 中断的 Address.
  • [0xE0000000, 0xF0000000): 映射 PCIe/PCI 配置空间
  • DMI Interface: 为 PCI/PCIe/DMI 设备内部寄存器或 BAR 映射的区域

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