点亮什么、为什么、设计图
这一章我们要点亮什么
一件核心的事:内核第一次能动态分配和释放物理内存。调一个 alloc_page(),拿回一个 4KB 物理页的物理地址;用完了 free_page(addr) 还回去。alloc_pages(count) 还能一次要连续的若干页。
这件事的意义不只是「多了一个函数」。它意味着内核从「一切内存写死」变成了「内存可管理」。在此之前,我们能用的内存就是镜像和栈那固定的一块;从这一章起,内核有了一个可以按需取用的物理页池子。后面几乎所有子系统都要踩在它上面:虚拟内存管理(VMM)要把物理页映射成虚拟页,堆分配器要在页上切块,进程要把页挂进自己的地址空间。PMM 是它们共同的底下那一层。
这本「账本」的具体形态是一个 bitmap(位图):物理内存按 4KB 一页划分,每一页对应 bitmap 里的一位,1 表示占用、0 表示空闲。分配就是「在 bitmap 里找一个 0,置 1」;释放就是「把那一位置 0」。听起来朴素,但bitmap 是物理页分配器最直接、也最容易实现正确的形态——我们这一章就用它。
为什么现在需要它
先说为什么是现在。014 之后,内核的外设已经相当齐全了:能显示、能收键盘、中断能跑。可你仔细想会发现,这些全是在「固定内存」上搭起来的——frame buffer 是 bootloader 映射好的显存,栈是链接脚本指定位置的,kprintf 的 sink 表是个静态全局变量。没有任何一处是「运行时按需要来的内存」。这条路走到进程就撞墙了:你没法给一个新进程分配页表、没法给它分独立的栈,因为你根本拿不到空闲物理页。所以在动进程之前,必须先把物理内存管起来。
再往远看一步,PMM 是后面整条内存线的地基。下一章(016)就是虚拟内存管理,而 VMM 干的事——把物理页映射到虚拟地址——天然需要先有一个「物理页从哪来」的来源,那正是 PMM。再往后的堆、进程地址空间,无一不是「向 PMM 要页、再在虚拟层面组织」。所以这一章看似只是写了个分配器,实际上它在给后面四五章铺最底下那层路。早做、做对,后面省一大堆事。
还有一个工程上的理由:PMM 是个几乎纯逻辑的组件(就是 bitmap 那一套位运算),几乎不碰硬件细节,极其适合用 host 单测把它焊死。这种「容易测对」的基础组件放在链路最底层,稳了,上层才好排查问题。
设计图
PMM 的工作分三个阶段:先搞清楚物理内存长什么样,再为它建 bitmap,最后用 bitmap 做分配。
展开代码 (共 24 行)收起代码
BIOS E820 内存图 (bootloader 采集, 塞进 BootInfo.mmap[])
┌──────────────────────────────────────────┐
│ base=0x00000000 len=0x9FC00 type=1 usable │ ← 640KB 常规内存
│ base=0x0009FC00 len=... type=2 reserved│ ← BIOS/保留
│ base=0x00100000 len=0x3F... type=1 usable │ ← 1MB 以上的可用 RAM
│ base=0x... type=2/3/4 ... │ ← ACPI、MMIO 等
└───────────────────┬──────────────────────┘
▼ parse_memory_map
过滤 type=1 + 丢弃 <1MB + 4KB 对齐
┌──────────────────────────────────────────┐
│ usable regions[]: [1MB, ...], [...] │
└───────────────────┬──────────────────────┘
▼ PMM::init
① 按最高物理地址算 bitmap 大小: 1 bit / 4KB 页
② bitmap 放在 __kernel_stack_top 之后 (已映射区域, 页对齐)
③ 先全置 1 (全占用), 再把 usable region 的位清 0 (标 free)
④ 把 kernel image + stack + bitmap 自身的位重新置 1 (保护)
┌──────────────────────────────────────────┐
│ bitmap: bit=1 占用, bit=0 空闲 │
│ 第 N 位 ↔ 物理地址 N*4KB │
└───────────────────┬──────────────────────┘
▼ alloc_page / free_page
alloc: find_first_free (64 位 ctzll 扫描) → 置 1 → 返回地址
free: 对应位置 0 (0/越界/已空闲 → no-op)bitmap 的核心不变量很简洁:第 N 位,对应物理地址 N × 4KB。整个 PMM 的正确性都建立在这个对应关系上。