深入理解 Linux 内核内存管理:逻辑地址 → 线性地址 → 物理地址的完整转换过程。
地址类型总览
| 地址类型 | 说明 |
|---|---|
| 虚拟地址 | 由段选择符 + 段内偏移组成,不能直接访问物理内存 |
| 逻辑地址 | 程序产生的段内偏移地址 |
| 线性地址 | 虚拟地址经分段变换后的中间层,处理器可寻址 |
| 物理地址 | 最终的内存单元地址 |
转换流程:逻辑地址 → 分段(查段表)→ 线性地址 → 分页(查页表)→ 物理地址
分段机制
段描述符
每个段由一个 8 字节的段描述符表示,包含段的基址、限长、优先级等属性:
| 字段 | 描述 |
|---|---|
| Base | 段首字节线性地址 |
| G | 粒度标志(0=字节,1=4096 字节) |
| Limit | 段长度 |
| S | 描述符类型(0=系统段,1=数据/代码段) |
| DPL | 特权级(0=内核,3=用户) |
门描述符
存放指向过程入口点的指针:
- 任务门:任务切换
- 中断门:调用中断处理程序
- 陷阱门:调用异常处理程序
- 调用门:在不同特权级间实现受控的程序控制转移
段选择符
8 字节长,由 index、TI(表指示符)、RPL(请求特权级)字段组成,用于在描述符表中查找段描述符。
Linux 的四种段
Linux 中所有进程共享相同的段描述符,基址均为 0x00000000,因此逻辑地址 = 线性地址:
| 段 | Base | Limit | DPL |
|---|---|---|---|
用户代码段 __USER_CS |
0x00000000 | 0xfffff | 3 |
用户数据段 __USER_DS |
0x00000000 | 0xfffff | 3 |
内核代码段 __KERNEL_CS |
0x00000000 | 0xfffff | 0 |
内核数据段 __KERNEL_DS |
0x00000000 | 0xfffff | 0 |
GDT(全局描述符表)
- 单处理器系统只有一张 GDT,多核每个 CPU 各一张
- 实模式通过
段值×16+偏移寻址(仅 1MB);GDT 进入保护模式,提供更大寻址能力和内存保护 - 每个 GDT 包含 18 个段描述符 + 14 个保留项
分页机制
为什么需要分页
分段解决了地址空间隔离和程序运行地址不确定的问题,但没有解决内存使用效率问题。分页通过”搭积木”式的分配缓解了碎片问题。
把进程的虚拟地址空间按页分割,常用数据装在内存,暂时不用的保存在磁盘,需要时再加载。
页与页框
- 页(page):线性地址的固定长度分组
- 页框(page frame):RAM 中的存储块,大小与页相等
- 页可以存放在任意页框或磁盘中
cr0 寄存器的 PG 标志控制是否启用分页。
页表
页表将线性地址映射到物理地址。转换过程:
- 用页号 p 检索页表,得到物理块号
- 将页内地址 d 送入物理地址寄存器的块内地址字段
- 二者拼接得到最终物理地址
页表还记录:页面是否在内存、是否受保护、是否非法空间等。
Linux 四级分页
自 2.6.11 起,Linux 采用四级分页以适配 32 位和 64 位:
1 | 页全局目录 → 页上级目录 → 页中间目录 → 页表 → 页框 |
- 32 位系统:页上级目录和页中间目录位为 0(保留位置以兼容 64 位)
- PAE 启用:使用三级分页,对应 80x86 的 PDPT
硬件高速缓存
CPU 时钟频率是 DRAM 的数百倍,引入高速缓存减小速度差距。
1 | CPU 寄存器:1 个时钟周期 |
缓存结构
高速缓存由硬件高速缓存内存(存放行数据)和高速缓存控制器(存放表项数组)组成。每个表项包含:
- 标签(tag):辨别该行映射的内存单元
- 状态 flag
物理地址分为三组:高几位=标签,中间几位=控制器子集索引,低几位=行内偏移量。
物理内存布局
保留的 1MB
Linux 内核安装在物理地址 0x00100000(第 2MB)开始处,前 1MB 被保留:
- 页框 0:BIOS 使用(POST 期间存储硬件配置)
- 0x000a0000
0x000fffff:BIOS 例程 + ISA 显卡内存(著名的 640KB1MB “hole”)
典型物理地址映射
| 开始 | 结束 | 类型 |
|---|---|---|
| 0x00000000 | 0x0009ffff | Usable |
| 0x000f0000 | 0x000fffff | Reserved |
| 0x00100000 | 0x07feffff | Usable |
| 0x07ff0000 | 0x07ff2fff | ACPI data |
| 0xffff0000 | 0xffffffff | Reserved |
进程页表
地址空间划分
0x00000000 ~ 0xbfffffff:用户态和内核态均可访问0xc0000000 ~ 0xffffffff:仅内核态可访问
宏 PAGE_OFFSET = 0xc0000000 是内核生存空间的起始偏移量。
内核页表初始化
分为两个阶段:
- 临时内核页表:创建有限地址空间(内核代码/数据 + 128KB),映射 RAM 前 8MB,完成实模式→保护模式转换
- 完整页表:充分利用剩余 RAM,建立正式页表
内核映像装入内存后,CPU 仍运行于实模式,分页功能尚未启用。
临时页全局目录(swapper_pg_dir)通过填充 0、1、0x300(第 768 项)、0x301(第 769 项)四项来实现前 8MB 的映射。