- 操作系统
- 嵌入式
- 嵌入式OS
【免费下载链接】tock
A secure embedded operating system for microcontrollers
本文以 Tock 操作系统核心团队 2021 年 4 月 23 日的例会纪要(doc/wg/core/notes/core-notes-2021-04-23.md)为主线,解读 Tock 2.0 发布前夜团队围绕 HIL 错误码规范化、系统调用返回值语义、kernel crate 导出重组与「阻止 Upcall 交换」机制所做出的关键决策。结合当前仓库源码,读者可以清晰看到这些设计讨论最终如何在 kernel/src/errorcode.rs、kernel/src/syscall.rs、kernel/src/grant.rs 等核心文件中落地成型。
会议概况:2.0 发布前的收官讨论
本次例会由 Tock 核心团队 9 位成员参加(Hudson Ayers、Brad Campbell、Branden Ghena、Philip Levis、Gabriel Marcano、Pat Pannuto、Leon Schuermann、Vadim Sukhomlinov、Johnathan Van Why),议程集中在三个方向:
- 会前更新:Tockloader 修复、内核二进制代码体积研究、HIL TRD(Hardware Interface Layer 技术报告)错误码条款修订;
- Tock 2.0 状态更新:系统调用 TRD 引入
BADRVAL、ReturnCode向StatusCode迁移、kernel crate 导出重组、AppSlice 别名安全性问题; - 阻止 Upcall 交换:将全部非虚拟化 capsule 迁移到 Grant 机制,从根源上杜绝 upcall 被非法替换。
对照今日仓库,kernel/src/lib.rs中KERNEL_MAJOR_VERSION = 2(kernel/src/lib.rs),说明这次会议所规划的 2.0 目标最终如期达成,会议中讨论的多数机制至今仍在内核中发挥核心作用。
会前更新:三件与工程质量直接相关的事
Tockloader 修复与发布前测试
Brad 汇报了 Tockloader 的几项小修复:此前程序退出时可能抛出异常(疑似仅发生在 macOS 上),已在最新 Git HEAD 修复,但正式发布前仍需更多测试以防止新问题引入。Tockloader 是 Tock 生态中负责向开发板刷写内核与应用的配套工具,这类「退出时异常」问题虽不直接影响内核运行,却会干扰开发者正常的刷写与调试流程。
内核代码体积研究:inlining 与 debug 语句的开销
Phil 提到有学生在研究内核二进制体积,实验方法是通过调整函数内联(inlining)来分解各函数的体积成本;下一步计划量化「移除全部调试语句」对体积的影响。Leon 随即提出一个具有实际价值的诉求:希望研究结果能持续反馈到 Tock 2.0 发布前的二进制尺寸调优中,而非等到报告最终成型才一次性公开。
从当前仓库看,Tock 对二进制体积与内存占用的关注延续至今:tools/目录下保留了 print_tock_memory_usage.py、diff_memory_usage.py 等内存占用分析工具,并在 CI 中用于跟踪每次改动带来的尺寸变化——这正是该议题沉淀下来的工程实践。
HIL TRD 修订:回调错误码统一为 ErrorCode
Phil 提交了对 HIL TRD 的更新(对应 PR #2550)。修订的背景是文档内部存在矛盾:一方面声称回调必须返回ErrorCode,另一方面又允许 HIL 定义自己的错误类型(如 I2C 的例子)。修改后的文本将措辞统一为:
- 回调应当(SHOULD)返回
ErrorCode; - 无论错误是作为回调的一部分返回,还是同步返回,类型应当保持一致。
这一修订在今日源码中可以得到直接印证。标准错误码集中定义于 kernel/src/errorcode.rs,每个错误码对应固定的usize非零数值,0保留给「无错误/成功」:
FAIL = 1, BUSY = 2, ALREADY = 3, OFF = 4, RESERVE = 5, INVAL = 6, SIZE = 7, CANCEL = 8, NOMEM = 9, NOSUPPORT = 10, NODEVICE = 11, UNINSTALLED = 12, NOACK = 13而 I2C HIL 正是「自定义错误类型」的典型代表:kernel/src/hil/i2c.rs 定义了pub enum Error(AddressNak、DataNak、ArbitrationLost、Overrun、NotSupported、Busy),并通过impl From<Error> for ErrorCode(kernel/src/hil/i2c.rs)将自定义错误映射回标准错误码,例如地址/数据 NAK 映射为NOACK、仲裁丢失映射为RESERVE、接收溢出映射为SIZE。这套「HIL 内部用精确的自定义错误,对外统一收敛到 ErrorCode」的设计,正是 2021 年会议纪要所确立方向的最终实现。
议题一:libtock-rs 的 macOS CI 困境
Johnathan 汇报了libtock-rs(Tock 的 Rust 用户态库)的 CI 状况:其 CI 负责在 macOS 上测试 RISC-V 构建,但成功率仅约三分之一,经常超时(最长约 6 小时),而团队中几乎无人使用 Mac,难以高效维护。会议讨论了三个备选方案:
libtock-rs不再官方支持 macOS 构建,直到有 Mac 的成员修复 CI;- 由核心团队中使用 macOS 的成员担任 macOS CI 维护者;
- 改为在 macOS 上只测试 ARM 构建,绕开 RISC-V 工具链在 macOS 上的安装难题(Brad 提出,Johnathan 采纳并排在第一优先级)。
会议还厘清了一个关键事实:GitHub Actions 对 macOS 构建有组织级并发配额,且配额低于其他平台;libtock-rs长达 6 小时的超时任务可能占满配额,进而挤占 Tock 主仓库的 CI 资源。Pat 提出了折中方案——只在 Bors 合并暂存分支上运行 macOS CI,使其仅作为合并前的最终把关环节,因为「Linux 构建通过时 macOS 构建失败的概率本就较低」。最终 Johnathan 确定的执行顺序为:先尝试 ARM on macOS,其次由 Hudson 接手维护,最后才考虑放弃 macOS 测试。
议题二:Tock 2.0 状态更新
会议以 tracking issue #2429 为线索逐项过了一遍 2.0 的收尾工作,除更新 changelog 和阻止回调交换外,重点讨论了以下子议题。
BADRVAL:用户态对「返回变体不匹配」的兜底
Phil 指出,系统调用 TRD(即 doc/reference/trd104-syscalls.md 对应的 syscalls 规范)在用户态引入了BADRVAL,用于表示「内核(capsule)返回的系统调用返回变体与预期不匹配」这一异常情形,但当时libtock-c的大部分代码并未处理该情况。Brad 回应:作为ReturnCode更新与向StatusCode迁移工作的一部分,libtock-c现已实现这一检查,因此不应阻塞发布。
从内核侧看,「系统调用返回变体」由 kernel/src/syscall.rs 的SyscallReturn枚举完整承载,包含Failure(ErrorCode)、FailureU32、FailureU64、Success系列,以及AllowReadWriteFailure、SubscribeFailure等带载荷的专用变体。该枚举由调度器构造并下传给架构层编码为寄存器值;capsule 侧则通过CommandReturn等受限包装类型构造返回值,确保只能构造语义合法的变体。用户态据此可校验返回变体是否匹配预期——这正是BADRVAL存在的基础。
ReturnCode → StatusCode:错误码跨边界的统一编码
ReturnCode到StatusCode的迁移是 2.0 的重要清理工作。StatusCode并非实际的 Rust 类型,而是一种「伪类型」:它用单个usize表示成功或失败,跨越内核与用户态的 syscall 接口传递。其关键设计在于错误码数值映射与ErrorCode完全一致,同时用0表示成功——因为ErrorCode按约定不使用0,kernel/src/errorcode.rs 的into_statuscode函数正是这一约定的直接实现:
pub fn into_statuscode(r: Result<(), ErrorCode>) -> usize { match r { Ok(()) => 0, Err(e) => e as usize, } }kernel crate 导出重组:先列清单,再动手改
Brad 提出重组 kernel crate 的导出:由于内核 crate 长期「有机生长」,导致使用体验不一——有时需要导入很长的路径,有时又可以从 crate 根直接导入;有的导入命名清晰,有的则不然(对应 PR #2545)。Phil 建议一种更稳健的推进方式:先完整列出目标导出清单,再据此修改,确保最终结果一致、方向明确。Leon 赞同该方案优于大量迭代式修改。Brad 补充说可以先用一个不含代码改动的草稿 PR 来对齐清单,再落地实现。
从当前仓库回看,kernel crate 的可见性策略已在 kernel/src/lib.rs 的模块级文档中被系统化地阐述:核心内核类型仅暴露最小必要接口(如ProcessBuffer构造函数被限制为pub(crate));敏感但必须公开的接口(如启动主调度循环的入口)要求调用方持有Capability;服务于内核外部扩展的接口则追加_external命名后缀。这套规则的成型与本次会议确立的「清单先行」思路一脉相承。
AppSlice 别名不安全:延期至下周三重点讨论
Leon 提出一个内存安全问题:内核中不能存在两个指向同一内存区域的可变借用切片(AppSlice),否则属于未定义行为。Amit(当天缺席)此前给出过 volatile slice 的方向,但 Leon 调研后未找到既良好又非侵入式的实现方案,其余备选方案也不乐观。会议决议将该议题移至下周例会并赋予高优先级,Leon 邀请有时间的成员共同参与——Hudson 主动提出会前与 Leon 沟通并先行调研。
议题三:阻止 Upcall 交换——全部非虚拟化 capsule 迁移到 Grant
这是本次会议最核心的技术议题(对应 PR #2462)。所谓「Upcall 交换」,指进程通过subscribe注册到 capsule 的回调(upcall)可能被意外替换或错配的安全隐患。Hudson 总结:引入「阻止 capsule 交换 upcall」机制所需的工作,几乎只剩下把所有非虚拟化 capsule 改为使用 Grant。Leon、Brad 与 Hudson 已分工推进,PR 描述中维护着待迁移驱动的认领清单,Phil 也鼓励大家各认领一个驱动——迁移过程本身也是理解「非虚拟化 capsule 在新机制下如何工作」的最佳途径。Leon 还指出 PR #2521 提供了一个现成的迁移模板,可套用到其余 capsule 上。
单进程预留:command / allow / subscribe 的策略分歧
Hudson 提出了一个具体的策略问题:早期迁移的 capsule 只在command系统调用上强制「单进程预留(reservation)」——若某进程对已被另一进程预留的 capsule 发起command,会收到错误返回;但allow与subscribe却仍然成功。在他最新的 PR 中,这一限制被扩展到全部三类系统调用:应用若在command之前先通过allow或subscribe使用驱动,其 Grant 区域同样会被分配。代价是文件内容略有增加、代码体积小幅上升。
Brad 的观点是:非虚拟化驱动本就不特别有用(更多用于测试),团队更应关注内核的安全性与健全性(soundness),因此该策略差异不是问题——Grant 区域被分配但驱动不可用的情形可以接受。Leon 则补充了一个语义层面的考量:若进程已共享了某些资源,而另一进程「抢占」了 capsule 预留权(并考虑未来可能释放预留),前者将永远无法取回资源;从语义上讲,「capsule 持有进程状态但拒绝某项操作」是合理的。最终 Hudson 同意按此调整 PR。
从源码看 Grant 如何承载 upcall 防交换机制
会议所讨论的机制在今日内核中已完全成型。Grant 的类型签名直接体现了「每个进程独立存储」的设计:kernel/src/grant.rs 中Grant<T, Upcalls, AllowROs, AllowRWs>同时管理进程数据、upcall 槽位与只读/读写 allow 槽位,并记录driver_num以唯一标识 upcall 归属。upcall 的身份由 kernel/src/upcall.rs 的UpcallId唯一确定——driver_num+subscribe_num的组合使得一个 upcall 只能对应到其注册时所属的驱动与订阅号,capsule 无法跨进程或跨槽位交换 upcall。
capsule 调度回调时通过GrantKernelData::schedule_upcall(kernel/src/grant.rs)进行:先按subscribe_num在 grant 存储的 upcall 切片中查找(越界返回UpcallError::InvalidSubscribeNum),再构造Upcall对象入队。而Upcall内部保存的fn_ptr若为 null 则不实际调度(kernel/src/upcall.rs),对应文档中提到的「null upcall」语义。核心要点是:upcall 的存储与读取都发生在进程自己的 Grant 内存区域中,而非 capsule 的全局状态里——这就从数据布局层面杜绝了「胶囊交换 upcall」的可能性,与会议纪要中「阻止 capsule 交换 upcall 的最后一步是把所有非虚拟化 capsule 迁移到 Grant」的结论完全吻合。
会议决议的落地与后续影响
本次会议虽然没有产生新的代码提交,但确立了一系列影响深远的决策:
| 议题 | 会议决议 | 当前仓库中的落地形态 |
|---|---|---|
| HIL 错误码 | 回调 SHOULD 返回ErrorCode,类型须一致 | kernel/src/errorcode.rs 标准错误码 + kernel/src/hil/i2c.rs 自定义错误映射 |
| syscall 返回值 | BADRVAL不阻塞发布,libtock-c已支持 | kernel/src/syscall.rs 的SyscallReturn变体体系 |
| kernel 导出 | 先列导出清单再修改 | kernel/src/lib.rs 的可见性与 Capability 策略 |
| upcall 防交换 | 非虚拟化 capsule 全部迁移 Grant,预留覆盖 command/allow/subscribe | kernel/src/grant.rs 与 kernel/src/upcall.rs |
从会议纪要到今日源码,KERNEL_MAJOR_VERSION = 2(kernel/src/lib.rs)标志着 2.0 已正式落地。回顾这场发布前夜的讨论,可以看到 Tock 团队在系统设计上的鲜明方法论:用 TRD 规范约束接口语义(ErrorCode 的统一与 SHOULD 措辞)、用类型系统与数据布局消除安全隐患(Grant 承载 upcall)、用「清单先行」的方式推进大规模重构(kernel 导出重组)。对于希望深入 Tock 内核源码的读者,建议沿着 kernel/src/grant.rs、kernel/src/upcall.rs 与 kernel/src/errorcode.rs 三个文件继续阅读,它们正是本次会议三个核心议题的最终代码形态;完整的例会纪要系列则收录于 doc/wg/core/notes/ 目录。
- 操作系统
- 嵌入式
- 嵌入式OS
【免费下载链接】tock
A secure embedded operating system for microcontrollers
相关推荐
Tock 2.0 内核内存安全设计回顾:从 core-notes-2021-04-16 看 Grant 区域、AppSlice 与 Upcall 的演进
Tock 2.0 内核内存安全设计回顾:从 core notes 2021 04 16 看 Grant 区域、AppSlice 与 Upcall 的演进 Toc
操作系统嵌入式嵌入式OSTock 2021-07-02 核心会议解读:upcall 交换内核化设计、allocate_grant 机制与代码体积优化
Tock 2021 07 02 核心会议解读:upcall 交换内核化设计、allocate_grant 机制与代码体积优化 导读 本文深度解读 Tock 嵌入
操作系统嵌入式嵌入式OSTock 内核设计解读:ProcessBuffer 裸指针化改造、无 panic 访问与错误码体系演进(Core Notes 2022-04-22)
Tock 内核设计解读:ProcessBuffer 裸指针化改造、无 panic 访问与错误码体系演进(Core Notes 2022 04 22) 本篇技术指
操作系统嵌入式嵌入式OS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考