2. 虚拟内存系统:虚拟地址空间布局、用户空间与内核空间划分、32位与64位系统差异
2.1 虚拟地址空间概述
虚拟内存是现代操作系统最核心的抽象之一。它为每个进程提供独立的、连续的、私有的地址空间,使进程误以为自己独占整个内存。Linux 内核通过 内存管理单元(MMU) 和 页表 将虚拟地址转换为物理地址,从而实现隔离、保护和高效利用。
虚拟地址空间的大小由 CPU 的地址总线宽度决定:
- 32位系统:地址总线宽度为 32 位,虚拟地址空间大小为 2³² = 4 GB。
- 64位系统:地址总线宽度为 64 位,理论上虚拟地址空间大小为 2⁶⁴ = 16 EB(Exabyte)。但当前硬件和操作系统通常只使用 48 位或 57 位地址线,实际可寻址空间远小于理论值。
2.2 用户空间与内核空间划分
Linux 将虚拟地址空间划分为两个主要区域:用户空间 和 内核空间。这种划分基于 CPU 的 特权级别(Ring):
- 用户空间(Ring 3):运行用户态进程,权限受限,不能直接访问硬件或内核数据结构。
- 内核空间(Ring 0):运行内核代码、驱动程序和核心子系统,拥有最高权限,可以访问所有内存和硬件。
划分比例因系统架构而异:
| 系统架构 | 用户空间 | 内核空间 | 划分方式 |
|---|---|---|---|
| 32位(默认) | 3 GB(0x00000000 – 0xBFFFFFFF) | 1 GB(0xC0000000 – 0xFFFFFFFF) | 3:1 划分 |
| 32位(内核选项) | 2 GB / 1 GB | 2 GB / 3 GB | 可配置(如 CONFIG_PAGE_OFFSET) |
| 64位(x86_64) | 低地址区域(通常 0x0000000000000000 – 0x00007FFFFFFFFFFF) | 高地址区域(通常 0xFFFF800000000000 – 0xFFFFFFFFFFFFFFFF) | 非对称划分,内核空间占据高位 |
2.3 32位系统虚拟地址空间布局
在 32 位 Linux 系统中,虚拟地址空间布局如下:
2.4 64位系统虚拟地址空间布局
64 位系统(以 x86_64 为例)的虚拟地址空间远大于 32 位系统。当前 Linux 内核使用 48 位地址线,虚拟地址空间被分为两个巨大的区域:
- 用户空间:0x0000000000000000 – 0x00007FFFFFFFFFFF(128 TB)
- 内核空间:0xFFFF800000000000 – 0xFFFFFFFFFFFFFFFF(128 TB)
中间区域(0x0000800000000000 – 0xFFFF7FFFFFFFFFFF)是 非规范地址(Non-canonical),CPU 无法访问,用于捕获错误指针。
2.5 32位与64位系统差异对比
| 对比维度 | 32位系统 | 64位系统 |
|---|---|---|
| 虚拟地址空间大小 | 4 GB(2³²) | 128 TB(2⁴⁷,当前实现) |
| 用户空间大小 | 3 GB(默认) | 128 TB |
| 内核空间大小 | 1 GB(默认) | 128 TB |
| 页表级数 | 2 级(PGD + PT) | 4 级(PGD + PUD + PMD + PT)或 5 级 |
| 直接映射区域 | ZONE_NORMAL(896 MB)+ ZONE_HIGHMEM | 线性映射整个物理内存(无 HIGHMEM) |
| 进程最大内存 | 约 3 GB | 约 128 TB |
| 内核模块地址 | 固定区域(0xC0000000 以上) | 独立模块区域(通常靠近内核代码) |
| vmalloc 区域 | 有限(约 128 MB) | 巨大(数 TB) |
| 物理内存限制 | 4 GB(无 PAE)/ 64 GB(PAE) | 理论上 4 PB(实际受硬件限制) |
2.6 内核空间关键区域详解
2.6.1 直接映射区域(Linear Mapping / Direct Mapping)
内核空间中最核心的区域是 直接映射,也称为 线性映射。它将物理内存直接映射到内核虚拟地址空间,映射关系为:
虚拟地址 = 物理地址 + PAGE_OFFSET
在 32 位系统中,PAGE_OFFSET 通常为 0xC0000000,直接映射区域覆盖物理内存的前 896 MB(ZONE_NORMAL)。超过 896 MB 的物理内存属于 ZONE_HIGHMEM,需要通过 kmap() 临时映射。
在 64 位系统中,PAGE_OFFSET 通常为 0xFFFF880000000000(或类似值),直接映射区域可以覆盖整个物理内存,无需 HIGHMEM 机制。
2.6.2 vmalloc 区域
vmalloc 区域用于分配 虚拟地址连续但物理地址不连续 的内存。它位于直接映射区域之后,适用于需要大块连续虚拟地址但物理内存碎片化的场景(如内核模块、驱动缓冲区)。
- 32位:vmalloc 区域通常从
VMALLOC_START到VMALLOC_END,大小约 128 MB。 - 64位:vmalloc 区域巨大(数 TB),从
VMALLOC_START到VMALLOC_END,几乎不受限制。
2.6.3 固定映射区域(Fixmap)
固定映射区域用于 永久映射 特定的物理地址到内核虚拟地址空间,通常用于访问硬件寄存器、ACPI 表、APIC 等。它位于内核空间的高位,地址固定不变。
2.6.4 模块区域
内核模块(.ko 文件)被加载到 模块区域。在 32 位系统中,模块区域紧邻内核代码段;在 64 位系统中,模块区域通常位于内核代码段附近,大小约 2 GB。
2.7 用户空间布局细节
每个用户进程的虚拟地址空间布局如下:
| 区域 | 起始地址(典型) | 说明 |
|---|---|---|
| 代码段(.text) | 0x08048000(32位) | 可执行指令,只读 |
| 数据段(.data / .bss) | 代码段之后 | 已初始化/未初始化全局变量 |
| 堆(Heap) | 数据段之后 | 通过 brk() / sbrk() 扩展,向上增长 |
| mmap 区域 | 堆之上(随机化) | 共享库、文件映射、匿名映射,向下增长 |
| 栈(Stack) | 用户空间顶部(随机化) | 局部变量、函数调用帧,向下增长 |
| vDSO / vsyscall | 栈之下 | 内核提供的用户空间加速接口 |
/proc/sys/kernel/randomize_va_space 控制 ASLR 级别(0=关闭,1=随机化,2=完全随机化)。
2.8 进程切换与地址空间
当 CPU 从一个进程切换到另一个进程时,内核必须切换 地址空间。这通过修改 页表基地址寄存器(CR3 在 x86 中) 实现。每个进程拥有独立的页表,切换进程即切换页表。
关键数据结构:
struct mm_struct:描述进程的虚拟地址空间,包含页表基地址、代码段/数据段/堆/栈的边界、mmap 链表等。struct vm_area_struct:描述地址空间中的一个连续区域(VMA),包含起始地址、结束地址、权限标志、文件映射信息等。
// 进程地址空间核心结构(简化)
struct mm_struct {
unsigned long start_code, end_code; // 代码段范围
unsigned long start_data, end_data; // 数据段范围
unsigned long start_brk, brk; // 堆范围
unsigned long start_stack; // 栈起始地址
unsigned long arg_start, arg_end; // 命令行参数
unsigned long env_start, env_end; // 环境变量
pgd_t *pgd; // 页全局目录(页表基地址)
struct vm_area_struct *mmap; // VMA 链表
struct rb_root mm_rb; // VMA 红黑树
unsigned long total_vm; // 总虚拟页面数
unsigned long hiwater_rss; // 最大驻留集大小
...
};
2.9 内核空间与用户空间的数据交换
内核与用户空间之间的数据交换必须通过 系统调用 或 驱动程序接口 进行。由于用户空间不能直接访问内核空间,内核提供以下机制:
- copy_from_user() / copy_to_user():安全地在用户空间和内核空间之间复制数据,会检查用户空间地址的有效性。
- get_user() / put_user():用于复制单个整数或指针。
- mmap:允许用户空间直接映射内核内存(如帧缓冲、DMA 缓冲区),但需要内核显式支持。
- procfs / sysfs / debugfs:通过文件系统接口暴露内核数据。
copy_from_user() 等安全函数,否则可能导致内核崩溃或安全漏洞(如 Spectre、Meltdown 相关攻击)。
2.10 本章小结
- 虚拟地址空间是 Linux 内存管理的基石,通过 MMU 和页表实现虚拟地址到物理地址的转换。
- 用户空间与内核空间的划分基于 CPU 特权级别,32 位系统通常采用 3:1 划分,64 位系统提供 128 TB 用户空间和 128 TB 内核空间。
- 32 位系统受限于 4 GB 地址空间,存在 HIGHMEM 问题;64 位系统地址空间巨大,无需 HIGHMEM,页表级数更多。
- 内核空间包含直接映射、vmalloc、固定映射、模块区域等关键区域,每个区域有特定的用途和管理方式。
- 用户空间布局包括代码段、数据段、堆、mmap 区域、栈和 vDSO,ASLR 提供安全保护。
- 进程切换涉及地址空间切换,内核通过
struct mm_struct和struct vm_area_struct管理进程地址空间。