Rust OS 入门:物理地址 ≠ RAM 地址——地址空间、MMIO 与 Memory Map
物理地址(Physical Address)是一张地址地图,RAM 只是地图上的一种东西。
本文是《Writing an OS in Rust》(https://os.phil-opp.com/)入门系列的第三篇,接上《Rust OS 入门:从按下电源键到内核接管》里”内存映射:Kernel 需要知道机器长什么样”那一节,把物理地址空间里到底有什么、Bootloader 为什么要把 Memory Map 传给 Kernel 彻底讲透。
📚 基本概念速读
| 名称 | 定义 | 省流 |
|---|---|---|
| 物理内存(Physical Memory) | 真正的 RAM 硬件 | 可读写的存储芯片 |
| 物理地址(Physical Address) | CPU 访问物理资源时产生的地址 | 不一定是 RAM 的地址 |
| 物理地址空间(Physical Address Space) | CPU 眼中全部地址的集合 | 一张地址地图,RAM 只是其中一种 |
| MMIO(Memory-Mapped I/O) | 把设备寄存器映射进地址空间 | 读写内存 = 操作硬件 |
| Memory Map | 记录地址区域用途的地图 | Kernel 靠它分辨能碰/不能碰 |
| Reserved | 地址空间里存在但 OS 不能用的区域 | 千万别拿来当 RAM |
| 虚拟地址(Virtual Address) | 程序看到的地址,经 MMU 翻译 | 程序眼里的地址是假的 |
🧩 物理地址 ≠ RAM 地址
先想象 CPU 眼里的世界。假设电脑有 16GB RAM,如果地址空间里只有 RAM,那确实很简单:
1 | 0x00000000 ───────┐ |
但真实电脑不是这样。CPU 执行 mov rax, [0x12345678]
时产生的地址叫物理地址(Physical
Address),而这个地址背后的东西不一定是
RAM:
1 | 物理地址空间 |
物理地址是 CPU 用来定位物理资源的地址;RAM 只是其中一种资源。
🗺️ 物理地址空间里有什么
对一台典型的 x86 PC,物理地址空间大致可以分成五类:
1 | 物理地址空间 |
逐个来看:
RAM:最好理解,CPU 访问它就是普通读写。RAM 本身不规定这里必须放什么,启动后由操作系统动态填充:
1 | RAM |
Firmware / ROM:例如物理地址 0x000F0000
附近对应的可能是主板上的 Flash ROM(BIOS)。CPU
访问它时路径是:
1 | CPU → Memory Controller / Chipset → 主板上的 Firmware Flash |
而不是 CPU → RAM。这就是 Memory-mapped
ROM。
MMIO:见下一节。
Reserved:存在于地址空间里,但操作系统不能随便用。
🔌 MMIO:内存映射 I/O
MMIO(Memory-Mapped I/O,内存映射 I/O) 的思想很简单:
把硬件设备的寄存器映射到物理地址空间里。
硬件工程师的思路是:CPU 本来就有读地址 / 写地址的机制,那干脆把设备寄存器也放进地址空间,CPU 就不需要额外的指令来访问设备了。
1 | CPU 看起来: |
假设显卡把某个寄存器规定在物理地址 0xF0000000:
1 | mov eax, [0xF0000000] ; 不是读 RAM,而是在读 GPU 寄存器的值 |
经典的例子是传统 PC 的显存区域:CPU 往物理地址 0xA0000
附近写数据,就会影响屏幕显示内容。现代 PCIe 设备也大量使用
MMIO,只是地址和机制更复杂。
⚠️ Reserved:为什么不能碰
某些物理地址区域存在于地址空间里,但操作系统不能随便使用。原因可能是:
- 被硬件使用
- 被固件使用
- 对应 MMIO
- 有特殊用途
- 芯片组保留区域
如果 OS 把它当普通 RAM 存数据,后果很严重:
1 | 写入数据 |
所以 OS 必须非常小心地区分:绿色 = 可以拿来用,红色 = 千万别碰,黄色 = 硬件。
📦 Memory Map:Bootloader 为什么要传给 Kernel
现在就能理解为什么 Bootloader 要把 Memory Map(内存映射) 传给 Kernel 了——Kernel 不能靠猜:
1 | 0x00000000 → RAM? |
它需要一张地图:
1 | Physical Memory Map |
整个启动链路串起来就是:
1 | 电脑启动 → BIOS → 检测 RAM / 硬件 / 物理地址布局 |
🧱 从 Memory Map 到 Frame Allocator
拿到 Memory Map 之后,Kernel 才能建立自己的内存管理系统。以 Rust OS 为例,下一步就是 Frame Allocator(物理页帧分配器):
假设 Memory Map 告诉 Kernel:0x100000 ~ 0x80000000
是可用 RAM,Kernel 就把它切成一个个 4 KiB page
frame:
1 | RAM |
之后所有内存需求都从这里申请:
1 | 需要创建页表? → 申请一个 Frame |
Memory Map → Frame Allocator → Page Table → Virtual Memory 就是接下来最自然的学习路线。
🕳️ 虚拟地址:程序看到的地址是假的
到这里还有一个很容易混淆的点。你写代码时看到的地址:
1 | int *p = malloc(100); // p ≈ 0x7FF123456000 |
通常并不是 RAM 的物理地址,而是虚拟地址(Virtual Address)。现代操作系统的地址翻译链路是:
1 | 程序 |
举例:程序执行 *p = 123,p 是虚拟地址
0x7FFF0000,实际过程是:
1 | CPU 写虚拟地址 0x7FFF0000 |
所以 0x7FFF0000 不是 RAM 第 0x7FFF0000
个字节,而是当前这个进程看到的虚拟地址。由此可以建立三层概念:
| 层级 | 内容 | 示例 |
|---|---|---|
| 第一层:虚拟地址空间 | 程序看到的地址 | 0x7FFF0000 |
| 第二层:物理地址空间 | MMU 翻译后的地址 | 0x12345000 |
| 第三层:真正的硬件 | 地址最终对应的东西 | RAM / MMIO / Firmware / Reserved |
🎨 一张图串起全部
1 | 用户程序 |
再回到开头的疑问:16GB RAM 里面有什么?要分两种情况回答:
- 问 RAM 这块硬件运行时存什么:Kernel、Kernel Stack/Heap、Page Tables、各进程的 Code/Data/Heap/Stack、文件缓存、网络缓冲区、设备缓冲区等。
- 问 CPU 的整个物理地址空间里有什么:可用 RAM、Reserved 区域、Firmware/ROM、MMIO、ACPI 硬件信息、APIC 控制区域等。
这两处提到的内存内容不是一回事。
✅ 总结
物理地址是 CPU 用来定位物理资源的地址,RAM 只是地址空间里的一种资源。 Kernel 靠 Bootloader 传的 Memory Map 分辨能碰 / 不能碰 / 是硬件,才能安全地建立 Frame Allocator,进而进入虚拟内存的世界。
下一步建议沿着 Memory Map → Frame Allocator → Page Table → Virtual Address 继续:搞清一个 4 KiB 的物理页帧到底是什么、Page Table 怎么把虚拟地址翻译成物理地址,前面的所有概念就会真正串起来。
Happy Hacking! 🎉