2. 虚拟地址空间布局:用户空间与内核空间划分

在 x86_64 架构下,虚拟地址空间被划分为用户空间与内核空间两大部分。这种划分是现代操作系统实现进程隔离与内核保护的基础。每个进程拥有独立的虚拟地址空间,而内核则映射到所有进程的高地址区域。

2.1 用户空间与内核空间的基本划分

虚拟地址空间的高位部分由内核使用,低位部分由用户进程使用。划分的边界由内核配置决定,通常以 PAGE_OFFSET 宏定义。在 x86_64 架构中,典型的划分比例为:用户空间占低 47 位(约 128TB),内核空间占高 47 位(约 128TB),中间保留巨大的非规范区域(non-canonical hole)。

关键概念: 用户空间与内核空间的划分不仅仅是地址范围的分配,更关键的是权限控制。用户态代码无法直接访问内核空间地址,任何尝试都会触发缺页异常(page fault),从而保护内核数据不被用户程序破坏。
架构差异: 在 32 位 x86 系统中,用户空间通常为 3GB(0x00000000 – 0xBFFFFFFF),内核空间为 1GB(0xC0000000 – 0xFFFFFFFF)。x86_64 将地址空间扩展到了 256TB,彻底解决了 32 位内核空间不足的问题。

2.2 x86_64 架构下的地址空间布局

x86_64 架构使用 48 位虚拟地址,但通过符号扩展(sign-extension)形成 64 位地址。有效地址范围分为两个区域:低 128TB(用户空间)和高 128TB(内核空间)。中间的非规范区域(0x0000800000000000 – 0xFFFF7FFFFFFFFFFF)在硬件层面被视为非法地址,任何访问都会引发异常。

下表列出了 x86_64 架构下虚拟地址空间的主要区域及其用途:

地址范围 大小 区域名称 用途
0x0000000000000000 – 0x00007FFFFFFFFFFF 128 TB 用户空间 用户进程代码、堆、栈、数据段、mmap 映射
0x0000800000000000 – 0xFFFF7FFFFFFFFFFF ~16M TB 非规范区域 硬件禁止访问,用于捕获指针错误
0xFFFF800000000000 – 0xFFFF87FFFFFFFFFF 8 TB 内核空间(低端) 直接映射物理内存(线性映射)
0xFFFF880000000000 – 0xFFFFC7FFFFFFFFFF 64 TB vmalloc 区域 非连续内存分配、模块加载
0xFFFFC80000000000 – 0xFFFFE8FFFFFFFFFF 32 TB 内核高内存映射 固定映射、持久内核映射、临时映射
0xFFFFE90000000000 – 0xFFFFFEFFFFFFFFFF 56 TB 内核栈、模块、fixmap 每个 CPU 栈、内核模块、固定映射页表
0xFFFFFF0000000000 – 0xFFFFFFFFFFFFFFFF 4 TB 内核页表与保留 页表自映射、内核代码段、数据段
注意: 非规范区域(non-canonical hole)是 x86_64 架构特有的设计。如果指针错误地指向该区域,CPU 会直接抛出异常,而无需经过页表查询。这为内核提供了额外的安全屏障,防止用户空间指针误入内核区域。

下面通过一张 SVG 图展示 x86_64 虚拟地址空间的整体布局:

x86_64 虚拟地址空间布局 x86_64 虚拟地址空间布局 用户空间 (128 TB) 0x0000000000000000 – 0x00007FFFFFFFFFFF 进程代码、堆、栈、数据段、mmap 非规范区域 (Non-canonical Hole) 0x0000800000000000 – 0xFFFF7FFFFFFFFFFF 硬件禁止访问,捕获指针错误 内核空间 (128 TB) 0xFFFF800000000000 – 0xFFFFFFFFFFFFFFFF 直接映射物理内存 (8 TB) – 线性映射 vmalloc 区域 (64 TB) – 非连续内存分配 内核高内存映射 (32 TB) – 固定映射、持久映射 内核栈、模块、fixmap、页表自映射 (60 TB) 用户空间与内核空间通过非规范区域隔离,硬件级保护防止越界访问

2.3 内核高内存映射

在 x86_64 架构中,由于物理内存可能远大于内核直接映射区域(8TB),内核需要额外的机制来访问超出直接映射范围的物理内存。这就是内核高内存映射(High Memory Mapping)的由来。虽然 x86_64 的 8TB 直接映射已经覆盖了绝大多数服务器物理内存,但在某些极端场景(如配备大量内存的 NUMA 系统)下,高内存映射仍然必要。

内核高内存映射主要包含以下三种映射方式:

  • 固定映射(Fixed Mapping, fixmap): 在编译时确定虚拟地址,用于访问特定物理地址(如 I/O 寄存器、APIC)。这些映射在系统启动时建立,运行期间不会改变。
  • 持久内核映射(Persistent Kernel Mapping, kmap): 用于将高内存物理页框长期映射到内核虚拟地址空间。通过 kmap() 函数建立,通过 kunmap() 解除。适用于需要频繁访问的高内存页。
  • 临时内核映射(Temporary Kernel Mapping, kmap_atomic): 用于短时间、原子操作的高内存访问。映射建立后不能被阻塞或睡眠,适用于中断上下文或临界区。通过 kmap_atomic() 建立,kunmap_atomic() 解除。
性能对比: 临时内核映射(kmap_atomic)比持久内核映射(kmap)快得多,因为它不需要获取锁或操作全局映射表。但它的使用限制也更严格:不能睡眠、不能持有锁、必须在同一 CPU 上解除映射。

下表总结了三种高内存映射方式的关键差异:

映射类型 建立函数 解除函数 可睡眠 适用场景 性能
固定映射 (fixmap) 编译时 / 启动时 无(永久存在) — I/O 寄存器、APIC、早期页表 极高(无运行时开销)
持久内核映射 (kmap) kmap() kunmap() 是 文件系统、块设备、长时间访问 中等(需锁保护)
临时内核映射 (kmap_atomic) kmap_atomic() kunmap_atomic() 否 中断处理、临界区、短时访问 高(无锁、无阻塞)

下面通过一张 SVG 图展示内核高内存映射的架构与数据流:

内核高内存映射架构 内核高内存映射架构 物理内存 低内存区域 (直接映射) 物理地址 0x0 – 0x0007FFFFFFFFFF 高内存区域 (超出直接映射) 物理地址 > 8TB 范围 内核虚拟地址空间 直接映射区域 (线性映射) 虚拟地址 = 物理地址 + PAGE_OFFSET 高内存映射区域 fixmap / kmap / kmap_atomic 直接映射 高内存映射 三种高内存映射方式 固定映射 (fixmap) 编译时确定,永久存在 持久映射 (kmap) 可睡眠,需锁保护 临时映射 (kmap_atomic) 原子操作,不可睡眠 高内存映射使得内核可以访问超出直接映射范围的物理内存,适用于大内存 NUMA 系统 kmap_atomic 性能最优,但使用限制最严格;kmap 灵活性最高,但性能较低

2.4 用户空间布局细节

用户空间的布局由内核的 mm_struct 管理,每个进程拥有独立的布局。典型的用户空间地址从低到高依次为:

  1. 代码段(Text Segment): 从 0x400000 开始(可配置),包含可执行指令,只读。
  2. 数据段(Data Segment): 包含已初始化全局变量和静态变量,可读写。
  3. BSS 段: 未初始化全局变量,在加载时清零。
  4. 堆(Heap): 通过 brk() / sbrk() 系统调用扩展,向上增长。
  5. 内存映射区域(mmap): 通过 mmap() 系统调用映射文件或匿名内存,通常从高地址向下增长。
  6. 栈(Stack): 从高地址向下增长,每个线程拥有独立栈。
  7. 环境变量与参数: 位于栈的顶部。
ASLR(地址空间布局随机化): 现代 Linux 内核默认启用 ASLR,随机化堆、栈、mmap 基址和代码段加载地址,增加攻击者预测内存布局的难度。可以通过 /proc/sys/kernel/randomize_va_space 控制 ASLR 级别(0=关闭,1=部分随机化,2=完全随机化)。

下面通过一张 SVG 图展示用户空间的典型布局:

用户空间典型布局 用户空间典型布局 高地址 低地址 环境变量与参数 栈 (Stack) 向下增长,每个线程独立 向下增长 内存映射区域 (mmap) 文件映射、匿名映射、共享库 向下增长 堆 (Heap) 通过 brk() / sbrk() 扩展 向上增长 BSS 段(未初始化数据) 数据段(已初始化数据) 代码段 (Text Segment) ASLR 随机化堆、栈、mmap 基址,增强安全性

2.5 内核空间布局细节

内核空间的布局在 x86_64 架构下由 arch/x86/include/asm/page_64_types.h 和 arch/x86/mm/init_64.c 定义。主要区域包括:

  • 直接物理映射区域(Linear Mapping): 从 PAGE_OFFSET 开始,将物理内存直接映射到内核虚拟地址空间。映射公式为:虚拟地址 = 物理地址 + PAGE_OFFSET。该区域用于内核访问大部分物理内存。
  • vmalloc 区域: 用于分配非连续物理内存的虚拟地址空间。内核模块、某些驱动程序使用此区域。
  • 持久内核映射区域(PKMAP): 用于 kmap() 函数映射高内存页框。
  • 固定映射区域(FIXMAP): 用于编译时确定的特殊映射,如 I/O 端口、APIC、早期页表。
  • 内核代码段与数据段: 包含内核本身的代码、全局变量、静态变量。
  • 模块区域: 用于加载内核模块(动态可加载内核模块)。
关键数据结构: 内核使用 struct vm_struct 管理 vmalloc 区域,使用 struct fixmap 管理固定映射,使用 struct page 的 mapping 字段跟踪高内存页框的映射状态。这些数据结构共同构成了内核虚拟内存管理的骨架。

下面通过一张 SVG 图展示内核空间的详细布局:

内核空间详细布局 (x86_64) 高地址 低地址 内核页表自映射 & 保留区域 (4 TB) 内核栈、模块、fixmap (56 TB) 每个 CPU 栈、动态模块加载、固定映射页表 内核高内存映射 (32 TB) PKMAP / FIXMAP / kmap_atomic 区域 vmalloc 区域 (64 TB) 非连续内存分配、内核模块、驱动程序 直接映射物理内存 (8 TB) 虚拟地址 = 物理地址 + PAGE_OFFSET 覆盖物理内存 0x0 – 0x0007FFFFFFFFFF 内核代码段 & 数据段 ← 上一章 📖 返回目录 下一章 →