Skip to content

点亮什么、为什么、设计图

这一章我们要点亮什么

一件核心的事:内核第一次能动态分配和释放物理内存。调一个 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 行)收起代码
text
   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 的正确性都建立在这个对应关系上。

035_multi_terminal-45-gf25de18 · f25de18 · 2026-08-04