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())提供了一个必要的内存“启动盘”。