近日,GNU 链接器(ld)的开发社区发布了一项重要更新:在链接脚本中新增了 ALLOCATE SECTION WITHOUT FILL 关键字,允许开发者对指定段(section)进行分配时跳过默认的零填充或模式填充行为。这一改进主要针对嵌入式系统和底层软件开发中的内存布局优化需求,预计将有效减少固件体积、提升启动速度,并赋予开发者更精细的内存控制能力。

背景:链接脚本中的段填充机制

在传统的 GNU ld 链接脚本中,当定义一个段时(例如通过 SECTION { .my_section : { *(.my_data) } }),链接器会为该段分配地址空间,并且在段内容之后、下一段开始之前,通常会用零或指定的填充模式(如 FILL(0xFF))填充剩余空间。这种填充行为对于确保内存对齐、初始化数据以及避免未定义状态是必要的,但在某些场景下却成为负担。

例如,在嵌入式系统中,某些段仅用于标记内存区域的边界(如堆栈指针的初始地址),或者用于存放运行时不需初始化的常量数据。此时,填充操作不仅浪费了宝贵的 ROM/RAM 空间,还增加了链接时间。更关键的是,当段跨越多个页面或扇区时,不必要的填充可能导致固件超出存储容量限制。

新特性:ALLOCATE WITHOUT FILL 的工作原理

新引入的语法允许开发者在段定义中明确指定“仅分配地址,但不进行填充”。其用法如下:

SECTIONS {
    .my_area : ALIGN(4) {
        _start = .;
        . = . + 0x100;  /* 保留 256 字节空间 */
        _end = .;
    } >RAM ALLOCATE WITHOUT FILL
}

在上述示例中,ALLOCATE WITHOUT FILL 告诉链接器:为 .my_area 段在 RAM 区域分配从 _start_end 的地址范围,但不对该空间写入任何数据(包括零)。链接后的二进制输出中,该段对应的区域将保持未定义状态,由加载器或运行时系统自行处理。

值得注意的是,该特性不会影响段的加载地址(LMA)和虚拟地址(VMA)之间的关系。它仅作用于链接器在输出文件中对段内容的填写行为。若段包含实际数据(如 *(.data)),则依然会正常拷贝;只有通过 BYTEFILL 或实现地址增量(. 符号)方式产生的“空洞”部分才会跳过填充。

社区反响与嵌入式开发者的期待

该补丁由资深嵌入式工具链开发者刘伟(音)提交,已在 GNU ld 的官方邮件列表中经过多轮测试和讨论。Linux 基金会嵌入式项目组成员表示,这一特性填补了链接脚本长期以来的一个空白:“过去,开发者为了实现无填充的段分配,不得不使用 NOLOAD 段或手动编写汇编文件来规避填充,但这些方法要么影响符号解析,要么增加维护成本。ALLOCATE WITHOUT FILL 以最简洁的方式解决了问题。”

知名操作系统开发者、RT-Thread 核心维护者张世俊评价:“我们的系统中有一个用于存储线程局部存储(TLS)的管理区域,之前不得不通过 NOCROSSREFS 和复杂对齐技巧来避免填充。新特性可以直接用链接脚本完成,代码更加直观,且节省了约 2% 的 RAM 预留空间。”

应用场景与未来展望

该特性特别适用于以下场景:

  1. 内存映射 I/O(MMIO)区域:外设寄存器地址映射段不需要填充任何数据。
  2. 堆栈或堆预留空间:仅在链接时分配地址范围,运行时由启动代码初始化。
  3. 调试符号表保留:用于 PROVIDE 地址标记的段,填充无意义。
  4. 多核共享内存:多个核心各自预留的通信缓冲区无需预先填充。

目前该补丁已进入 GNU binutils 的主线分支,预计将在下一个稳定版本(如 2.43 或 2.44)中正式发布。开发者可提前从 Git 仓库编译最新快照进行测试。随着嵌入式设备对内存效率和启动速度的要求日益提高,这一细微但关键的改进,有望帮助开发者从链接阶段就精打细算每一字节的存储空间。