拓冰建站拓冰建站
首页 / 资讯中心 / 正文

early_alloc_pgt_buf

early_alloc_pgt_buf()是 Linux 内核启动早期在setup_arch()中被调用的一个关键函数。它的核心作用非常直接为内核初始化阶段建立直接内存映射direct mapping所需的早期页表分配一个临时的内存池buffer。页表内存池的来源BRK 区域这个页表内存池的物理内存来自内核的BRKBreak区域。你可以把BRK区域理解为内核启动早期的一段动态内存空间。预留空间代码通过RESERVE_BRK(early_pgt_alloc, INIT_PGT_BUF_SIZE)宏在链接脚本定义的 BRK 区域中为页表池预留了一块空间。这个预留的大小由INIT_PGT_BUF_SIZE定义在不同版本的内核中其值有所调整例如曾经从5 * PAGE_SIZE增加到12 * PAGE_SIZE以满足更复杂的映射需求。分配与初始化early_alloc_pgt_buf()函数通过调用extend_brk()从刚才预留的 BRK 区域中“切出”所需的内存并将其地址转换为物理地址用来初始化几个关键的全局变量pgt_buf_start页表池的起始物理页号PFN。pgt_buf_end当前已使用的页表池尾部的物理页号初始时与pgt_buf_start相同表示尚未使用。pgt_buf_top页表池的结束物理页号即内存池的边界。角色与运作一个临时的“内存批发商”early_alloc_pgt_buf()初始化完成后内核就拥有了一个暂时的“页表批发商”其运作方式如下优先使用 BRK 池在初始化init_mem_mapping()等需要建立内核页表的过程中分配页表例如通过alloc_low_pages()函数会优先从这个 BRK 内存池中获取空间。一个典型的注释提到“step_size need to be small so pgt_buf from BRK could cover it”步进值需要足够小以便 BRK 中的 pgt_buf 能够覆盖它说明初期映射的粒度会受限于这个池的大小。耗尽可能后启用后备方案这个初始内存池的大小是有限的。当pgt_buf_end触及pgt_buf_top即池子被用完后内核后续若还需要分配新页表就会转而使用更通用的memblock分配器来获取内存。这确保了页表映射工作能够持续进行。特殊的“免重叠”保护代码中有一个名为can_use_brk_pgt的标志其设计初衷是为了避免用 BRK 池中的页表去映射它自身所在的内存区域以防止可能出现的 MMU 问题。不过也有讨论认为这种担心在当前实现下可能已不再必要。early_alloc_pgt_buf()函数的代码实现非常简洁其核心逻辑是在 Linux 内核启动早期从内核的 BRK 区域分配一块连续内存作为构建初始页表的内存池。这个分配动作是一次性的并有相应的防重复机制。代码位置与数据结构该函数定义在arch/x86/mm/init.c文件中其实现依赖于三个关键的全局变量static unsigned long pgt_buf_start, pgt_buf_end, pgt_buf_top;pgt_buf_start记录页表内存池的起始页帧号 (PFN)。pgt_buf_end记录当前已使用的内存池末尾页帧号 (PFN)初始时与pgt_buf_start相等。pgt_buf_top记录内存池的结束页帧号 (PFN)即内存池的边界。代码实现演变该函数的定义在不同内核版本中有所变化主要区别在于池的大小和防重复逻辑。初始版本在最初引入该功能时代码相对简单RESERVE_BRK(early_pgt_alloc, 16384); void __init early_alloc_pgt_buf(void) { unsigned long tables 16384; phys_addr_t base; base __pa(extend_brk(tables, PAGE_SIZE)); pgt_buf_start base PAGE_SHIFT; pgt_buf_end pgt_buf_start; pgt_buf_top pgt_buf_start (tables PAGE_SHIFT); }内存池大小固定此处通过RESERVE_BRK在 BRK 区域预占了 16KB即 4 个页面的空间。通过extend_brk分配extend_brk(tables, PAGE_SIZE)从预留的 BRK 区域中“切出”所需内存并返回其虚拟地址。转换为物理地址__pa()负责将此虚拟地址转换为物理地址base。计算页帧号将物理地址base除以页大小PAGE_SHIFT得到起始页帧号并相应计算出结束页帧号pgt_buf_top。演进版本随后的补丁对该函数进行了完善主要是增加了缓冲区大小和防重复调用的保护。/* need 4 4k for initial PMD_SIZE, 4k for 0-ISA_END_ADDRESS, 4 4k for kernel */ #define INIT_PGT_BUF_SIZE (9 * PAGE_SIZE) RESERVE_BRK(early_pgt_alloc, INIT_PGT_BUF_SIZE); void __init early_alloc_pgt_buf(void) { unsigned long tables INIT_PGT_BUF_SIZE; phys_addr_t base; /* already set ? */ if (pgt_buf_end) return; base __pa(extend_brk(tables, PAGE_SIZE)); pgt_buf_start base PAGE_SHIFT; pgt_buf_end pgt_buf_start; pgt_buf_top pgt_buf_start (tables PAGE_SHIFT); }INIT_PGT_BUF_SIZE宏为了满足更复杂的早期内存映射需求如内核本身的映射池的大小从最初的 16KB 增加到了9 个页面。历史补丁记录显示这个值是根据需求动态调整的例如在特定配置下从 5 个页面增加到 6 个页面。防重复调用检查添加了if (pgt_buf_end) return;判断。如果pgt_buf_end非零说明缓冲池已被分配函数会直接返回避免了重复分配导致的内存浪费或指针异常。函数的作用与背景early_alloc_pgt_buf()创建的内存池为紧接着执行的init_mem_mapping()函数提供了构建初始页表所需的内存。init_mem_mapping()会调用alloc_low_pages()来分配页表页而该分配器会优先从这个 BRK 内存池中获取空间。一个相关的注释提到“step_size need to be small so pgt_buf from BRK could cover it”说明了早期映射的粒度step_size会受到这个池子大小的制约以确保初期映射能在这个有限的缓冲区中完成。它的代码实现十分精巧通过RESERVE_BRK预先在链接脚本层面“圈地”再通过extend_brk在运行时“挂牌”最终为内核页表搭建了一个早期、临时的“工作台”。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门