分页时,Loader 的线性地址为什么要与物理地址相同?
在 分页机制(Paging)启用之前,Loader(引导程序或内核启动代码) 需要使用 物理地址进行访问,而在分页启用后,它的 线性地址需要与物理地址一致,这样可以 确保分页机制生效后,代码仍然可以正常运行。
1. 什么是分页?
- 分页(Paging) 是 CPU 通过页表将线性地址(Linear Address)转换为物理地址(Physical Address) 的过程。
- 开启分页前,CPU 直接使用 物理地址 访问内存。
- 开启分页后,CPU 访问的 虚拟地址(线性地址) 需要通过 页表转换 成 物理地址。
但在启用分页的瞬间,CPU 仍然要继续执行代码,因此必须确保代码地址在启用分页前后保持一致,否则会出现访问错误。
2. 为什么 Loader 线性地址 = 物理地址?
在 分页启用(开启 CR0.PG
)的瞬间:
- Loader(内核代码)仍然在运行。
- 指令地址不能改变,否则 CPU 会找不到指令,导致崩溃!
因此,必须保证 Loader 在开启分页前后的地址是一样的:
[
\text{线性地址} = \text{物理地址}
]
这样 分页前后的代码地址不变,保证 CPU 能继续正常执行代码。
3. 具体实现方式
(1)开启分页前
- 使用物理地址访问(未开启分页)
- 代码仍然在 物理地址
0x100000
运行(假设 Loader 在 1MB 处)。
(2)设置页表
建立一个 “恒等映射(Identity Mapping)”:
- 页表设置
线性地址 = 物理地址
- 例如:
这样:页目录:0x00000000 → 指向 0x00000000(恒等映射) 页目录:0x00100000 → 指向 0x00100000(恒等映射)
线性地址 0x100000 → 物理地址 0x100000
(3)开启分页
write_cr3(页目录地址);
// 设置CR3
write_cr0(read_cr0() | 0x80000000);
// 开启CR0.PG
- 此时,CPU 进入分页模式,但地址转换仍然映射到相同的物理地址。
(4)跳转到新的虚拟地址
- 如果 Loader 需要被映射到更高地址(例如
0xC0000000
),则可以:- 建立新的页表映射:
线性地址 0xC0000000 → 物理地址 0x100000
- 跳转到
0xC0000000
,然后释放恒等映射。
- 建立新的页表映射:
4. 为什么不直接使用虚拟地址?
如果 分页前 代码运行在 虚拟地址 0xC0000000
:
- 在启用分页前,
0xC0000000
不是一个有效的物理地址,导致 CPU 找不到代码,程序崩溃! - 必须先建立页表,然后再跳转到高地址。
5. 结论
✅ 开启分页前,Loader 只能访问物理地址。
✅ 开启分页时,必须确保 线性地址 = 物理地址
,防止 CPU 找不到代码。
✅ 分页启用后,Loader 可以跳转到更高的虚拟地址(如 0xC0000000
)。
🚀 “恒等映射” 是分页启用的关键,确保 CPU 在启用分页后仍然能正确执行指令! 🎯