brk 区域 📅 2026/8/24 13:07:11 BRK 区域是 Linux 内核映像中位于 BSS 段之后的一块特殊内存区域。它专为内核启动的“非常非常早期”阶段设计提供了一个类似于brk()的动态内存分配器在系统最核心的内存管理机制如memblock正式工作前使用。它与应用程序的brk堆段无关而是内核自身在启动流程中使用的内部数据区。它的设计和运作可以概括为以下几点诞生背景解决“先有鸡还是先有蛋”的问题在内核启动的极早期一些子系统例如在 x86 上建立早期页表的代码需要动态分配内存但此时通用的内存分配器memblock和伙伴系统Buddy System都还没有初始化。BRK 区域正是为解决这个矛盾而设计的。它通过在链接阶段就将一块内存预留给内核使得早期代码可以在不依赖任何外部内存管理服务的情况下实现简单的动态内存分配。这避免了使用静态数组造成的浪费因为很多子系统的内存需求在不同配置或硬件上可能完全不同。核心特性与定位内存位置与属性BRK 区域位于 BSS 段.bss之后由符号__brk_base和__brk_limit界定。在 ELF 文件中它和 BSS 段一样属于NOBITS类型这意味着它在镜像文件中不占用实际空间只在加载时才分配内存。它的初始大小通常设定为 1MB。生命周期它的生命周期是动态的预留与清零启动时整个 BRK 区域会像 BSS 段一样被清零保证初始状态干净。不同内核版本中清零动作可能位于不同的位置例如在clear_bss()中一并清除或在链接脚本中直接指定为NOLOAD属性来保证加载器预留此段空间。使用期代码可以通过extend_brk()函数像一个指针一样从区域底部向上“生长”地分配内存。回收与移交当memblock分配器可用后会通过reserve_brk()函数将已使用的BRK 区域标记为保留而所有未使用的空间则会释放给系统的通用内存池实现资源的充分利用。如何使用内核开发者通过RESERVE_BRK(name, size)宏来预留空间然后使用extend_brk(size, align)函数来真正获取内存。early_alloc_pgt_buf()函数正是用这个机制来为早期页表分配内存池的。RESERVE_BRK是一个用于管理内核启动早期临时内存池的宏它通过在链接脚本层面上“圈地”为extend_brk()函数预留内存空间解决了在memblock等内存分配器正式工作前的内存分配需求。这个宏在不同版本中经历了从复杂的内联汇编到更简洁的 C 语言定义的演进其核心设计目标始终是在 vmlinux 镜像文件中不占用实际空间类似于 BSS 段同时为早期启动代码提供动态内存池。实现这一目标的关键技术是使用nobits或.bss前缀的 section让链接器将它标记为“分配但不占用文件空间”的类型。核心定义与演进RESERVE_BRK的定义和使用方式在不同内核版本中差异明显其演进体现了为了解决工具链兼容性和简化代码所做的努力。早期版本复杂的内联汇编实现早期的内核版本中RESERVE_BRK被设计为一个极其复杂的宏。它使用内联汇编inline asm并包裹在一个被丢弃的.discard函数中通过nobits属性来确保被预留的空间不会在vmlinux二进制文件中实际占用字节。这种复杂的设计是为了绕开当时 GCC 编译器的限制因为直接使用__section属性定义变量会被标记为progbits从而在镜像中占用实际空间。近期版本使用.bss前缀的 C 语言实现这种设计虽然巧妙但也引发了与较老版本 binutils 的构建失败问题。为了解决此问题并简化宏的实现开发者引入了新的定义方式在 C 语言中声明一个以.bss..brk为 section 的静态字符数组。// 简化自较新内核版本的定义 #define RESERVE_BRK(name, size) \ __section(.bss..brk) __aligned(1) __used \ static char __brk_##name[size]这个新实现的巧妙之处在于GCC 和 Clang 编译器会自动为任何放在以.bss前缀命名的 section 中的变量设置STT_NOBITS标志从而完美地在 C 语言层面实现了“预留空间但不占用文件空间”的效果无需再借助复杂的内联汇编和.discard函数技巧。作为后备方案链接脚本中也为.brk段添加了NOLOAD属性以确保兼容性。工作原理与生命周期RESERVE_BRK宏的工作原理与其在启动流程中的生命周期紧密相连。预留空间当代码中使用RESERVE_BRK(name, size)时它实际上是在链接脚本定义的内存区域即.brk段由__brk_base和__brk_limit界定中预占了size字节的空间。清零BRK区域和.bss段具有相同的“仅分配”属性因此它也会在启动早期通常在clear_bss()函数中被一同清零确保其初始状态是干净的以避免 Xen 等虚拟化环境在验证页表时出错。使用与回收内核启动代码可以通过extend_brk()函数从该预留区域中分配内存。一旦系统更通用的memblock内存分配器初始化完毕所有已使用的BRK区域会被转换成memblock的一部分而所有未被使用的空闲空间则会被释放回系统实现资源的充分利用。RESERVE_BRK正是通过这种在链接阶段预留、启动阶段清零、随后由特定函数分配并在最后移交的方式为内核最早期、最核心的初始化阶段例如early_alloc_pgt_buf()提供了一个必要的内存“启动盘”。