Linux 内核内存管理:从寻址到分页

2k 词

深入理解 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 标志控制是否启用分页。

页表

页表将线性地址映射到物理地址。转换过程:

  1. 用页号 p 检索页表,得到物理块号
  2. 将页内地址 d 送入物理地址寄存器的块内地址字段
  3. 二者拼接得到最终物理地址

页表还记录:页面是否在内存、是否受保护、是否非法空间等。

Linux 四级分页

自 2.6.11 起,Linux 采用四级分页以适配 32 位和 64 位:

1
页全局目录 → 页上级目录 → 页中间目录 → 页表 → 页框
  • 32 位系统:页上级目录和页中间目录位为 0(保留位置以兼容 64 位)
  • PAE 启用:使用三级分页,对应 80x86 的 PDPT

硬件高速缓存

CPU 时钟频率是 DRAM 的数百倍,引入高速缓存减小速度差距。

1
2
3
4
CPU 寄存器:1 个时钟周期
L1/L2 缓存:~几十个时钟周期
主存 DRAM:~上百个时钟周期
磁盘:~几千万个时钟周期

缓存结构

高速缓存由硬件高速缓存内存(存放行数据)和高速缓存控制器(存放表项数组)组成。每个表项包含:

  • 标签(tag):辨别该行映射的内存单元
  • 状态 flag

物理地址分为三组:高几位=标签,中间几位=控制器子集索引,低几位=行内偏移量。

物理内存布局

保留的 1MB

Linux 内核安装在物理地址 0x00100000(第 2MB)开始处,前 1MB 被保留:

  • 页框 0:BIOS 使用(POST 期间存储硬件配置)
  • 0x000a00000x000fffff: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 是内核生存空间的起始偏移量。

内核页表初始化

分为两个阶段:

  1. 临时内核页表:创建有限地址空间(内核代码/数据 + 128KB),映射 RAM 前 8MB,完成实模式→保护模式转换
  2. 完整页表:充分利用剩余 RAM,建立正式页表

内核映像装入内存后,CPU 仍运行于实模式,分页功能尚未启用。

临时页全局目录(swapper_pg_dir)通过填充 0、1、0x300(第 768 项)、0x301(第 769 项)四项来实现前 8MB 的映射。