
early_alloc_pgt_buf()是 Linux 内核 x86 架构启动早期用于初始化页表缓冲区的关键函数。它从brk区域申请一块内存并将其划分为页表页供后续的alloc_low_page()分配使用。代码逐行解析void __init early_alloc_pgt_buf(void) { unsigned long tables INIT_PGT_BUF_SIZE; // 1. 计算需要的内存大小 phys_addr_t base; base __pa(extend_brk(tables, PAGE_SIZE)); // 2. 从 brk 区域扩展并转为物理地址 pgt_buf_start base PAGE_SHIFT; // 3. 记录缓冲区起始页帧号 pgt_buf_end pgt_buf_start; // 4. 分配游标初始化为起始位置 pgt_buf_top pgt_buf_start (tables PAGE_SHIFT); // 5. 记录缓冲区结束页帧号 }关键变量与操作INIT_PGT_BUF_SIZE这是需要预留的总字节数由INIT_PGD_PAGE_COUNT * PAGE_SIZE计算得出。INIT_PGD_PAGE_COUNT会根据是否启用 KASLR 和 5 级页表动态调整通常在 6 到 16 个页面之间。extend_brk(tables, PAGE_SIZE)从内核镜像的brk区域“扩展”出tables字节的空间。brk区域是内核镜像的一部分在内核启动早期必然被映射因此可以作为页表分配的“启动资本”。PAGE_SIZE参数确保扩展后的地址按页对齐。__pa()将brk区域的虚拟地址转换为物理地址。页表项中需要填写的是物理地址因此这一步是必需的。三个关键全局变量pgt_buf_start缓冲区的起始页帧号PFN。pgt_buf_end当前分配位置初始时等于pgt_buf_start。pgt_buf_top缓冲区的结束页帧号。在启动流程中的角色这个函数在setup_arch()中被调用执行时机非常早。它解决了“先有鸡还是先有蛋”的问题要建立物理内存的直接映射需要页表。但要分配存放页表的内存又需要先有直接映射。pgt_buf就是在这个矛盾中提供的一个安全内存池。在直接映射完全建立之前alloc_low_page()会优先从这个缓冲区中取用内存来分配页表页。当pgt_buf_end追上pgt_buf_top即缓冲区耗尽时内核才会转向memblock去查找更多已映射的内存。设计意图这个函数体现了内核启动早期对确定性和安全性的追求使用brk区域而不是动态内存分配器是因为此时伙伴系统尚未就绪而brk区域是内核镜像的一部分其映射关系是确定且可靠的。