- 操作系统
- 嵌入式
- 嵌入式OS
【免费下载链接】tock
A secure embedded operating system for microcontrollers
导读
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)在编译时需要完成几件固定的事:
- 把板卡自身的
layout.ld链接脚本(以及它INCLUDE的芯片级链接脚本)交给链接器; - 保证链接器能搜索到 Tock 内核提供的通用链接脚本
tock_kernel_layout.ld; - 当上述链接脚本内容变化时触发重新编译;
- 校验构建所用的 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 字节)**即可,脚本头部对此有明确说明。
板卡需要提供的符号契约
如果你要从零编写自己的链接脚本,则必须定义以下符号(脚本头部注释逐条给出了语义):
| 符号 | 含义 |
|---|---|
_etext | flash 中驻留数据(含需拷贝的初始值)的结束地址 |
_srelocate/_erelocate | SRAM 中可变程序数据的拷贝目标区间;Tock 启动时把_erelocate - _srelocate字节从_etext拷贝到_srelocate |
_szero/_ezero | BSS 区间,Tock 启动时清零 |
_sapps/_eapps | flash 中应用内存的起止 |
_sappmem/_eappmem | RAM 中应用内存的起止 |
关键 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 a
layout.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
相关推荐
RustTraining工程实践:build.rs构建脚本与交叉编译完全指南
RustTraining工程实践:build.rs构建脚本与交叉编译完全指南 RustTraining 是一套面向不同编程背景的 Rust 培训课程仓库,其中
文档教程Tauri 构建脚本 tauri-build 完全指南:从 build.rs 到跨平台产物生成的编译期魔法
Tauri 构建脚本 tauri build 完全指南:从 build.rs 到跨平台产物生成的编译期魔法 tauri build 是 Tauri 桌面与移动应
桌面应用跨平台移动开发conventional-changelog template 包深度解析:Markdown 渲染辅助函数、仓库链接构建器与默认模板实现
conventional changelog template 包深度解析:Markdown 渲染辅助函数、仓库链接构建器与默认模板实现 @convention
开发工具CLI文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考