2. Linux 内核内存布局基础:虚拟地址空间划分
在深入内核内存 Dump 分析之前,必须透彻理解 Linux 的虚拟地址空间布局。无论是使用 Crash 工具解析内存映像,还是分析 Kdump 捕获的 vmcore,所有地址解析的起点都基于对虚拟地址空间划分的认知。本章将系统梳理 32 位与 64 位系统下的地址空间划分、直接映射区、vmalloc 区及模块区的核心特征。
2.1 虚拟地址空间总体划分:用户态 vs 内核态
Linux 操作系统将 CPU 的虚拟地址空间划分为两个主要部分:用户空间 (User Space) 和 内核空间 (Kernel Space)。这种划分由进程的页表机制和 CPU 的特权级(Ring 0 / Ring 3)共同决定。
- 用户态 (Ring 3):每个进程拥有独立的虚拟地址空间,进程间相互隔离。用户态代码不能直接访问内核态地址,必须通过系统调用陷入内核。
- 内核态 (Ring 0):所有进程共享同一个内核地址空间。内核态代码可以访问所有物理内存和所有进程的用户空间(通过特定机制)。
在 32 位系统上,典型的划分比例为 3:1(用户空间 3GB,内核空间 1GB)。在 64 位系统(如 x86_64)上,地址空间巨大,通常采用 47:17 或类似的比例划分,用户空间和内核空间各占约 128TB。
System.map 或 vmlinux 符号表,将虚拟地址映射回具体的函数或数据结构。
2.2 内核空间内部布局:核心区域详解
内核空间内部并非铁板一块,而是根据用途划分为多个逻辑区域。理解这些区域是解读 Crash 工具中 kmem -v 或 mach 命令输出的基础。
| 区域名称 | 典型地址范围 (x86_64) | 主要用途 | 关键特征 |
|---|---|---|---|
| 直接映射区 (Direct Mapping) | 0xffff888000000000 - 0xffffc7ffffffffff | 线性映射所有物理内存 | 虚拟地址 = 物理地址 + 固定偏移 (PAGE_OFFSET) |
| vmalloc 区 | 0xffffc90000000000 - 0xffffe8ffffffffff | 非连续内存分配 | 虚拟地址连续,物理地址不连续;通过页表映射 |
| 模块区 (Module Region) | 0xffffffffa0000000 - 0xfffffffffeffffff | 加载内核模块 (.ko) | 地址空间有限,模块过多可能导致加载失败 |
| 固定映射区 (Fixmap) | 0xffffffffff600000 - 0xffffffffffdfffff | 特殊用途的固定虚拟地址 | 编译时确定,用于 ACPI、APIC 等 |
kmem -v 可以查看当前内核的虚拟地址空间布局。使用 vtop <虚拟地址> 可以将虚拟地址转换为物理地址,这对于验证直接映射关系非常有用。
2.3 直接映射区 (ZONE_NORMAL / ZONE_HIGHMEM)
直接映射区(也称为线性映射区或低端内存映射区)是内核空间中最基础的部分。内核启动时,会将所有物理内存直接映射到内核虚拟地址空间的一个连续区域。
- ZONE_NORMAL (普通区):在 32 位系统上,指物理内存中能被内核直接映射且无需特殊处理的区域(通常低于 896MB)。在 64 位系统上,由于地址空间充足,所有物理内存都属于 ZONE_NORMAL。
- ZONE_HIGHMEM (高端内存区):仅在 32 位系统上存在。当物理内存超过 896MB 时,超出部分无法被直接映射到 1GB 的内核空间,因此需要动态映射到 vmalloc 区或固定映射区。64 位系统已废弃此概念。
直接映射区的核心公式:
// x86_64 架构下,PAGE_OFFSET 通常为 0xffff888000000000
// 物理地址 = 虚拟地址 - PAGE_OFFSET
// 虚拟地址 = 物理地址 + PAGE_OFFSET
// 示例:物理地址 0x1000 对应的直接映射虚拟地址
unsigned long phys_addr = 0x1000;
unsigned long virt_addr = phys_addr + 0xffff888000000000;
// virt_addr = 0xffff888000001000
vmalloc() 分配的内存,其虚拟地址在 vmalloc 区,物理地址可能分散在多个页框中。在分析 Dump 时,如果直接使用物理地址偏移去访问 vmalloc 地址,会导致访问错误。
2.4 vmalloc 区:非连续内存分配
vmalloc 区 用于分配虚拟地址连续但物理地址不连续的内存。这对于需要大块连续虚拟地址空间但物理内存碎片化严重的场景非常有用(如动态加载的驱动、文件系统缓存等)。
- 分配函数:
vmalloc()、vzalloc()、__vmalloc()。 - 映射机制:每次分配时,内核在 vmalloc 区找到一块空闲的虚拟地址范围,然后通过页表将分散的物理页框映射到该虚拟地址范围。
- 性能特征:分配速度较慢(需要修改页表),访问速度略慢于直接映射区(可能触发 TLB 未命中)。
0xffffc9000000xxxx),不能直接通过减去 PAGE_OFFSET 来得到物理地址。必须遍历该虚拟地址对应的页表,才能找到其物理页框。Crash 的 vtop 命令会自动完成此操作。
2.5 模块区:内核模块的栖身之所
模块区 (Module Region) 是内核为动态加载的模块(.ko 文件)预留的虚拟地址空间。当使用 insmod 或 modprobe 加载模块时,模块的代码段、数据段等会被分配到此区域。
| 属性 | 说明 |
|---|---|
| 地址范围 (x86_64) | 0xffffffffa0000000 - 0xfffffffffeffffff (约 1.5GB) |
| 分配粒度 | 通常为 2MB 或 4KB 对齐,由 MODULES_VADDR 和 MODULES_END 宏定义 |
| 空间限制 | 地址空间有限,如果加载大量或体积巨大的模块,可能导致模块区耗尽 |
| 调试命令 | Crash 中 mod -S 加载模块符号,lsmod 查看已加载模块列表 |
mod -s <模块名> 或 sym <地址> 可以快速定位到具体的模块和函数。
2.6 本章小结:地址空间与 Dump 分析的关联
掌握内核内存布局是进行 Dump 分析的第一步。以下是需要牢记的核心要点:
- 地址分类:拿到一个内核虚拟地址,首先判断它属于用户态还是内核态,然后进一步判断属于内核空间的哪个区域。
- 直接映射区:最常见的区域,物理地址与虚拟地址有固定偏移关系,Crash 工具可以高效转换。
- vmalloc 区:需要页表遍历才能找到物理地址,Crash 的
vtop命令是分析此区域的关键。 - 模块区:崩溃地址落在此区域时,优先定位所属模块,通常能快速找到问题驱动。
- 工具验证:在 Crash 环境中,使用
kmem -v、vtop、mod等命令验证上述理论知识,做到理论与实践结合。