1. Linux 内核内存管理概述
内存管理子系统是 Linux 内核最核心、最复杂的组件之一。它负责在有限的物理内存与无限的虚拟地址空间之间建立高效、安全的映射,并为每个进程提供独立的、私有的地址空间。理解内存管理是深入内核调试、性能优化和驱动开发的基石。
1.1 内存管理子系统的作用
内存管理子系统承担以下关键职责:
- 地址空间隔离:确保进程 A 无法访问进程 B 的内存数据,提供硬件级保护。
- 虚拟化抽象:让每个进程看到连续的、从 0 开始的虚拟地址空间,简化应用开发。
- 物理内存分配与回收:管理物理页框的分配、释放、回收和交换。
- 内存映射:支持文件映射、匿名映射、共享内存等机制。
- 缺页异常处理:按需加载数据,实现惰性分配和写时复制。
- 缓存与缓冲区管理:协调页缓存、slab 分配器和 DMA 缓冲区。
核心观点:内存管理子系统是进程与硬件之间的“契约管理者”,它决定了系统的稳定性、安全性和性能上限。
1.2 虚拟内存与物理内存
现代操作系统普遍采用虚拟内存技术。虚拟内存是进程看到的逻辑地址空间,物理内存是硬件 RAM 的真实存储单元。两者通过页表进行动态转换。
| 特性 | 虚拟内存 | 物理内存 |
|---|---|---|
| 地址空间大小 | 取决于 CPU 架构(如 x86_64 为 128 TB) | 取决于硬件安装容量(如 8 GB、64 GB) |
| 连续性 | 对进程呈现连续线性空间 | 物理页框可能不连续 |
| 访问权限 | 通过页表项控制读/写/执行 | 由 MMU 和页表联合控制 |
| 生命周期 | 随进程创建和销毁 | 系统启动后持续存在 |
| 交换能力 | 可被换出到磁盘交换分区 | 物理页框不可换出 |
虚拟内存带来的核心优势包括:
- 进程隔离:每个进程拥有独立的页表,天然防止越界访问。
- 简化链接与加载:所有程序可以假设从固定地址开始运行。
- 按需分页:只有真正访问的页面才占用物理内存。
- 共享内存:多个进程的虚拟页可以映射到同一物理页框。
关键概念:虚拟地址到物理地址的转换由 MMU(内存管理单元) 硬件完成,内核只负责维护页表结构。转换失败会触发缺页异常,由内核缺页异常处理程序接管。
1.3 内存管理核心组件
Linux 内核内存管理子系统由多个协同工作的组件构成。下图展示了核心组件及其关系:
各核心组件的职责简述如下:
| 组件 | 核心数据结构 | 主要职责 |
|---|---|---|
| 内存描述符 | struct mm_struct |
管理进程的整个地址空间,包含页表基址、VMA 链表、代码段/数据段/堆/栈边界等。 |
| VMA 管理 | struct vm_area_struct |
描述地址空间中的连续区域,记录起始地址、长度、权限、映射文件等。 |
| 页表管理 | PGD/P4D/PUD/PMD/PTE | 实现虚拟地址到物理地址的分级转换,处理缺页异常和权限检查。 |
| 物理内存分配 | 伙伴系统 + slab | 管理物理页框的分配与回收,提供通用分配器和专用缓存。 |
| 页框回收 | LRU 链表 + kswapd | 在内存压力下回收页面,支持换出和脏页回写。 |
| 内存映射 | struct file + address_space |
将文件内容映射到进程地址空间,实现文件 I/O 与内存访问的统一。 |
1.4 内存描述符的核心地位
在 Linux 内核中,struct mm_struct 是进程地址空间的“总指挥”。每个进程(或线程组)拥有一个 mm_struct 实例,它串联起所有内存管理相关的数据结构。
关键字段示例:
struct mm_struct {
struct vm_area_struct *mmap; /* VMA 链表头 */
struct rb_root mm_rb; /* VMA 红黑树根 */
unsigned long start_code, end_code; /* 代码段范围 */
unsigned long start_data, end_data; /* 数据段范围 */
unsigned long start_brk, brk; /* 堆起始与当前边界 */
unsigned long start_stack; /* 栈起始地址 */
pgd_t *pgd; /* 页全局目录指针 */
atomic_t mm_users; /* 共享该地址空间的用户数 */
atomic_t mm_count; /* 内存描述符引用计数 */
int map_count; /* VMA 数量 */
spinlock_t page_table_lock; /* 页表锁 */
struct rw_semaphore mmap_sem; /* VMA 读写信号量 */
};
注意:内核线程没有独立的
mm_struct,它们直接借用上一个用户态进程的地址空间(active_mm)。这是内核线程与用户进程在内存管理上的关键区别。