拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Tock 板级构建辅助 crate(tock_build_scripts)完全指南:从 build.rs 到默认链接脚本

Tock 板级构建辅助 crate(tock_build_scripts)完全指南:从 build.rs 到默认链接脚本
  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载

导读

tock_build_scripts是 Tock 内核仓库中为板级(board)构建提供支撑的辅助 crate:它把"调用默认链接脚本、校验编译参数、跟踪链接脚本变更"等样板逻辑封装成一行函数调用,并随 crate 打包了默认的tock_kernel_layout.ld链接脚本。无论是仓库内的 Tock 板卡(如 hail、qemu_rv32_virt),还是基于 Tock 的out-of-tree(仓库外)内核配置,都可以通过三步接入获得一致的构建行为。读完本文,你将掌握在自定义板卡中接入该 crate 的完整流程、其底层 build.rs 函数族的工作原理,以及默认链接脚本定义的各内存区域与关键符号语义。

这个 crate 解决什么问题

Tock 的板卡 crate(如boards/hail)在编译时需要完成几件固定的事:

  1. 把板卡自身的layout.ld链接脚本(以及它INCLUDE的芯片级链接脚本)交给链接器;
  2. 保证链接器能搜索到 Tock 内核提供的通用链接脚本tock_kernel_layout.ld;
  3. 当上述链接脚本内容变化时触发重新编译;
  4. 校验构建所用的 RUSTFLAGS 没有被意外覆盖,避免产出无效固件。

tock_build_scripts正是把这些逻辑收敛为一个 crate。其官方定位在 boards/build_scripts/README.md 中写得很清楚:"该 crate 为构建 Tock 板卡提供辅助;out-of-tree 的 Tock 内核配置可以可选地把该 crate 作为依赖引入,以使用内核提供的 build.rs 功能;该 crate 同时打包了默认的tock_kernel_layout.ld链接脚本。"

从源码看,该 crate 结构极简:boards/build_scripts/src/lib.rs 仅声明default模块并对外重导出default_linker_script;真正的实现都在 boards/build_scripts/src/default.rs 中。整条调用链的入口是一个不返回值的函数default_linker_script()。

三步接入:在自定义板卡中使用该 crate

README 给出了三个通用步骤,下面逐一展开并结合仓库实际用法说明。

第 1 步:在 Cargo.toml 的 build-dependencies 中引入

在板卡的Cargo.toml中把该 crate 加入构建依赖:

# Cargo.toml # ...Existing Cargo.toml contents... [build-dependencies] tock_build_scripts = { git = "https://github.com/tock/tock"}

这样会确保构建脚本在板卡编译时可用。对于仓库内的板卡,则使用相对路径引用,例如 boards/hail/Cargo.toml:

[build-dependencies] tock_build_scripts = { path = "../build_scripts" }

仓库内绝大多数板卡(apollo3 系列、arty_e21、clue_nrf52840、microbit_v2、各种 nrf52840dk 测试配置等)都采用这种path = "../build_scripts"或path = "../../build_scripts"的形式。

第 2 步:在 build.rs 中调用辅助函数

在板卡 crate 根目录的build.rs里,常见用法就是直接调用提供的函数:

// build.rs fn main() { tock_build_scripts::default_linker_script(); }

仓库中真实的 boards/build.rs 正是这样写的,且其文档注释明确建议:"out-of-tree 板卡推荐把这个文件复制到自己的板卡 crate 中"(同时把Cargo.toml的[package] build字段指过去,见 boards/hail/Cargo.toml 中的build = "../build.rs")。

第 3 步:提供板卡自身的 layout.ld

default_linker_script()约定板卡必须提供一个名为layout.ld的链接脚本(该约定常量定义于 boards/build_scripts/src/default.rs)。它的末尾应包含一行INCLUDE tock_kernel_layout.ld,把通用内核布局引入:

INCLUDE tock_kernel_layout.ld

例如 boards/hail/layout.ld 就是芯片布局 + 内核布局两级 INCLUDE 的典型写法:

INCLUDE ./chip_layout.ld INCLUDE tock_kernel_layout.ld

其中chip_layout.ld(如 boards/hail/chip_layout.ld)负责定义MEMORY区域(如 sam4l 的rom/prog/ram三块区域的 ORIGIN 与 LENGTH);而tock_kernel_layout.ld负责定义所有 section 的排布。如果板卡未提供layout.ld,default_linker_script()会直接 panic 并提示 "Boards must provide alayout.ldlink script file"。

源码级拆解:default_linker_script() 到底做了什么

default_linker_script()的实现位于 boards/build_scripts/src/default.rs,依次执行四件事:

pub fn default_linker_script() { if !Path::new(LINKER_SCRIPT).exists() { panic!("Boards must provide a `layout.ld` link script file"); } rustflags_check(); include_tock_kernel_layout(); add_board_dir_to_linker_search_path(); set_and_track_linker_script(LINKER_SCRIPT); }

1. rustflags_check():防止 RUSTFLAGS 被覆盖

Tock 通过cargo配置文件(仓库内见 boards/cargo/tock_flags.toml 等)设置整套 RUSTFLAGS,但这些环境变量很容易被命令行参数无意覆盖——构建仍会"成功",产物却是无效的。为此 boards/build_scripts/src/default.rs 采用哨兵标记方案:配置文件里设置cfg_tock_buildflagssentinel,构建脚本检查CARGO_ENCODED_RUSTFLAGS中是否包含该标记,若缺失则 panic 提示 "Incorrect build configuration. Verify you are using unstable cargo and have not unintentionally set the RUSTFLAGS environment variable."。

两个细节值得注意:

  • 该检查仅在交叉编译(TARGET ≠ HOST)时执行,从而避免cargo clippy等主机端工具误报;
  • 如果你有意不使用标准 Tock 配置文件,可以在自己的 cargo 配置中设置cfg-tock-buildflagssentinel来显式规避此错误。

2. include_tock_kernel_layout():把默认链接脚本加入搜索路径

见 boards/build_scripts/src/default.rs。它向链接器输出-L<本 crate 的 CARGO_MANIFEST_DIR>,使链接器在搜索INCLUDE tock_kernel_layout.ld时能找到随 crate 打包的脚本;同时输出cargo:rerun-if-changed=<tock_kernel_layout.ld 路径>,保证内核默认链接脚本一旦变更,所有依赖它的板卡都会自动重编。

3. add_board_dir_to_linker_search_path():把板卡目录加入链接器搜索路径

见 boards/build_scripts/src/default.rs。它输出cargo:rustc-link-arg=-L<板卡 Cargo.toml 所在目录>。源码注释特别点明:这里必须用运行时读取的CARGO_MANIFEST_DIR,而不能用编译期展开的std::env!("CARGO_MANIFEST_DIR")——因为前者指向板卡目录,后者指向 build_scripts crate 自身目录,二者是不同的路径。

4. set_and_track_linker_script():传递链接脚本并递归跟踪 INCLUDE

见 boards/build_scripts/src/default.rs:输出cargo:rustc-link-arg=-Tlayout.ld把板卡链接脚本交给链接器,随后调用track_linker_script()。

跟踪逻辑(boards/build_scripts/src/default.rs)值得一提:

  • 跳过tock_kernel_layout.ld本身(它已在第 2 步单独登记了 rerun 指令);
  • 校验路径确实存在(assert!(path.is_file(), ...));
  • 输出cargo:rerun-if-changed=<path>;
  • 解析链接脚本中所有INCLUDE <相对路径>行并递归跟踪,因此layout.ld → chip_layout.ld → tock_kernel_layout.ld任意一环变更都会触发增量重建。

这解释了为什么"修改芯片内存布局后不需要手动 clean 重编"——cargo 会在下一次cargo build时自动感知。

默认链接脚本 tock_kernel_layout.ld 详解

tock_build_scripts打包的 boards/build_scripts/tock_kernel_layout.ld 是 Tock 内核的通用链接脚本。对大多数开发者而言,**只需在板卡/芯片链接脚本里定义 6 个变量(ROM/PROG/RAM三块区域的ORIGIN与LENGTH)和可选的PAGE_SIZE(默认 512 字节)**即可,脚本头部对此有明确说明。

板卡需要提供的符号契约

如果你要从零编写自己的链接脚本,则必须定义以下符号(脚本头部注释逐条给出了语义):

符号含义
_etextflash 中驻留数据(含需拷贝的初始值)的结束地址
_srelocate/_erelocateSRAM 中可变程序数据的拷贝目标区间;Tock 启动时把_erelocate - _srelocate字节从_etext拷贝到_srelocate
_szero/_ezeroBSS 区间,Tock 启动时清零
_sapps/_eappsflash 中应用内存的起止
_sappmem/_eappmemRAM 中应用内存的起止

关键 section 排布

tock_kernel_layout.ld的SECTIONS主体约 380 行,核心布局如下:

  • .text(> rom):刻意放在文件最前。注释解释了原因——QEMU 等系统用 multiboot 协议只扫描 ELF 前 8192 字节,.text必须先定义才能保证.multiboot落在前 8K。section 内部依次放置.multiboot、ARM 向量表(.vectors/.irqs)、RISC-V 启动代码(.riscv.start,且_start_trap需 256 字节对齐以配合 lowRISC CPU)、x86 启动代码(.x86.start)、常规.text/.rodata、C/C++ 构造函数与析构函数数组等。
  • .storage(> rom):按PAGE_SIZE对齐的片上内核非易失存储区,配合utils.rs中的storage_volume!宏分配卷。
  • .ccfg:客户配置区,通常位于 rom 末尾,板卡未定义ccfg区域时不会被写入。
  • .stack(NOLOAD,> ram):内核栈放在 SRAM最低地址,这样栈向下溢出时进入不可映射/不可访问内存,由硬件触发内存故障而非静默破坏数据。
  • .relocate(AT _etext,> ram):需搬移的已初始化数据,物理上放在 flash(_etext处),由 Tock 拷贝进 SRAM;其中还定义了 RISC-V 全局指针__global_pointer$ = . + 0x800,使编译器能用 12 位立即数访问前 4KB 数据内存。
  • .apps(NOLOAD,> prog):应用二进制占位区,默认不烧写;需要时可用arm-none-eabi-objcopy --set-section-flags .apps=LOAD,ALLOC <elf>使其随内核加载。内含 4 字节0xFF占位,避免空 section 被链接器忽略导致 openocd 写失败。
  • .sram(NOLOAD,> ram):内核 BSS(.sbss/.bss/COMMON)与应用内存(.app_memory)。_eappmem = ORIGIN(ram) + LENGTH(ram)表明应用使用 SRAM 剩余全部空间。
  • .attributes(AT rom 末尾):以倒序 TLV格式存放内核元数据(TOCK 哨兵 + 版本 + Kernel Flash 区域 + App RAM 区域),供主机侧工具读取已安装内核信息,具体字节布局在脚本内有 ASCII 图示。

脚本末尾还有两道ASSERT防线:其一确保 attributes 落在 ROM 末尾(_eattributes == _erom);其二在链接收敛的最终 pass 校验"text + 搬移数据 + attributes 未超出 ROM 容量",溢出时给出明确错误信息。ENTRY(_stext)则把入口点设为文本段起始(Cortex-M 为向量表,RISC-V 为_start)。

板卡侧的实际示例

  • qemu_rv32_virt(boards/qemu_rv32_virt/layout.ld):在MEMORY中定义三段 QEMU 模拟 DRAM 区域(rom 0x80000000、prog 0x80100000、ram 0x80200000),额外导出_sflash/_eflash/_ssram/_esram符号供 ePMP 配置使用,然后INCLUDE tock_kernel_layout.ld。
  • hail(boards/hail/layout.ld):两级 INCLUDE——先INCLUDE ./chip_layout.ld(sam4l 芯片的内存区域定义),再INCLUDE tock_kernel_layout.ld。

常见问题与排查要点

  • "Boards must provide alayout.ldlink script file":板卡 crate 根目录缺少layout.ld,检查文件是否存在于 Cargo.toml 所在目录。
  • "Incorrect build configuration...":RUSTFLAGS 被命令行覆盖导致哨兵标记丢失;确认使用的是 unstable cargo 且未手动设置 RUSTFLAGS 环境变量,或按上文所述在配置中设置cfg-tock-buildflagssentinel显式放行。
  • "Kernel attributes are not at the end of ROM" / "Text plus relocations plus attributes exceeds...":ROM 区域(LENGTH(rom))容量不足或rom/prog/ram的 ORIGIN/LENGTH 配置与芯片实际布局不符,检查chip_layout.ld中的MEMORY定义。
  • 改动链接脚本后未生效:正常情况下cargo:rerun-if-changed会自动处理layout.ld及其全部INCLUDE的追踪;若手动修改了链接器搜索路径或移除了 build 依赖,则需确认[build-dependencies]与[package] build配置完好。

小结

tock_build_scripts的价值在于把 Tock 板卡构建中"必须做对、但容易出错"的链接脚本接入与参数校验逻辑收敛为一处:一个default_linker_script()调用即完成 RUSTFLAGS 哨兵校验、默认链接脚本搜索路径注入、板卡目录加入搜索路径、链接脚本及其所有 INCLUDE 的变更追踪四件事。配合随 crate 发布的tock_kernel_layout.ld,自定义板卡只需专注定义好rom/prog/ram三个内存区域与PAGE_SIZE,即可获得与 Tock 官方板卡一致的链接布局、启动语义与 ROM 容量检查。若要在仓库外复用这套能力,参照 boards/build.rs 的写法把 build.rs 复制到自己的板卡 crate,并在 Cargo.toml 中引入该 crate 即可。

  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载

相关推荐

上一篇:Apache Spark 访问 OpenStack Swift 对象存储完整指南:swift:// 协议、Keystone 认证与 hadoop-cloud 集成
下一篇:D3PM 图像生成实战指南:离散状态空间扩散模型在 CIFAR-10 上的 Flax/JAX 实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表