内存模型与地址空间——物理内存、虚拟内存和 BOCHS 的映射关系
物理内存模型
bochsrc 里的 memory: guest=64 给 Bochs 模拟一块 64MB 的物理内存——就是一个从 0x00000000 到 0x03ffffff 的线性数组。这是 Bochs 模拟的 PC 硬件能看到的所有物理 RAM。
这块内存里,部分地址被硬件保留(映射到 BIOS ROM、VGA VRAM、PCI 设备的 MMIO 区域),真正可用的 RAM 是:
| 物理地址范围 | 用途 |
|---|---|
| 0x00000000 ~ 0x0009ffff | 常规 RAM(低 640KB,DOS 传统区) |
| 0x000a0000 ~ 0x000bffff | VGA VRAM(64KB,0xa0000-0xbffff) |
| 0x000c0000 ~ 0x000dffff | ISA ROM(128KB) |
| 0x000e0000 ~ 0x000fffff | BIOS shadow RAM |
| 0x00100000 ~ guest 上限 | 扩展 RAM(1MB 以上) |
| 0xfffc0000 ~ 0xffffffff | BIOS ROM(128KB) |
| PCI 设备 BAR 区 | PCI MMIO(由 PCI 枚举决定具体范围) |
OS 内核在做内存管理时必须了解这些保留区域——不能把 BIOS 区域或 VGA VRAM 当作普通 RAM 分配给用户进程。
虚拟内存:CR3 和页表
内核打开分页后,CR3 寄存器指向当前进程的页表根(32 位下是 PDG,x86-64 下是 PML4)。每次 CPU 访问虚拟地址时:
- 查询 CR3 找到页表根的物理地址
- 多级遍历页表(根据模式不同:2 级、3 级、4 级、5 级)
- 最后一级页表项给出物理页框号 + 权限标志
- 把虚拟地址低 12 位偏移加到物理页框号上,得到最终物理地址
Bochs 在每次内存访问时都做这个遍历——精度极高,但也是 Bochs 慢的原因之一。QEMU 会直接把虚拟地址翻译成宿主机地址跳过去,VirtualBox/VMware 让硬件做。
调试器里的内存查看
Bochs 调试器提供两种前缀查看内存:
| 命令 | 前缀 | 含义 |
|---|---|---|
xp <addr> | xp | 查看物理地址(Physical) |
x <addr> | x | 查看虚拟地址(由当前 CR3 页表翻译) |
wx <addr> <value> | wx | 写入物理地址 |
w <addr> <value> | w | 写入虚拟地址 |
示例:
bochs:1> xp 0x7c00 # 看物理地址 0x7c00(Bootloader 入口)
bochs:2> x 0x100000 # 看虚拟地址 0x100000(经过 CR3 翻译)
bochs:3> xp /32bx 0x0 # 看中断向量表(物理)
bochs:4> w /4bx 0x7c00 0x00 # 往 Bootloader 开头写入 4 个 0
注意 x 命令依赖当前 CR3 页表有效——如果内核还没初始化 CR3(实模式或保护模式刚开始),虚拟地址翻译会出错。此时用 xp 看物理地址更可靠。
bochsrc 里的 memory 字段详解
memory: guest=64, host=64, slots=2
| 字段 | 说明 |
|---|---|
guest | 客户机能看到的物理内存大小(MB) |
host | 宿主机分配给 Bochs 的内存(MB) |
slots | 内存槽数(可选,影响 BIOS 报告的物理 DIMM 布局) |
通常 guest == host。如果你要精确模拟"客户机 64MB RAM 但宿主机只给 Bochs 32MB 工作内存"的极端情况,也可以让 host < guest——但通常没必要,反而会影响性能(Bochs 会更频繁地 swap)。
内存与 BIOS 的关系
BIOS 自检时会枚举物理内存布局,并把结果写到 E820 内存映射表(BIOS 调用 int 0x15 AX=0xE820 返回)。OS 内核启动时读这个表来决定哪些地址是可用 RAM、哪些被保留。
Bochs 正确实现了 E820——它会根据 bochsrc 里的 memory: 大小自动生成一份合理的内存映射表,和真实硬件一致。OS 开发者不需要手动构造 E820,Bochs 会给你一份"看起来像真的"。
64 位模式的内存映射
x86-64 模式下物理地址空间可以到 1TB 甚至更大(取决于 bochsrc 里 CPU model 和 guest 内存)。但内核在初始化时会把物理内存拆成不同区域:
- Direct Mapping:内核把所有物理 RAM 映射到一个连续的虚拟地址区间(如 0xffff800000000000 + phys)
- Kernel Code + Data:内核自身的代码段和数据段
- Module Area:可加载内核模块
- Fixmap:固定映射的硬件寄存器区
调试 64 位内核时,x 命令看直接映射区特别有用——可以直接用"物理地址 + 固定偏移"的方式访问任何物理内存。