<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Rust on TiJet</title><link>https://www.tainuohc.com/categories/rust/</link><description>Recent content in Rust on TiJet</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Sun, 19 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.tainuohc.com/categories/rust/index.xml" rel="self" type="application/rss+xml"/><item><title>裸机 Rust 实战：重写 Bootloader，零内存错误的背后</title><link>https://www.tainuohc.com/blog/2026-07-19-rust-embedded/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-19-rust-embedded/</guid><description>&lt;h2 id="1-bootloader-的困局">1. Bootloader 的困局&lt;/h2>
&lt;p>泰诺的工业控制板卡上搭载一颗 MCU，负责电源时序管理、看门狗和 OTA 固件升级。Bootloader 约 8000 行 C 代码，已&amp;quot;稳定&amp;quot;运行多年——但在嵌入式语境下，&amp;ldquo;稳定&amp;quot;往往意味着&amp;quot;已知的坑都补上了，未知的等待下一次现场翻车&amp;rdquo;。&lt;/p>
&lt;p>每隔 6 到 9 个月，新的 OTA 边界场景就会浮现：固件镜像校验时的缓冲区溢出、Flash 擦除例程的 off-by-one、CAN 总线命令分发器中的 use-after-free。每一次修复都需要全量 OTA 推送，而针对已部署的工控设备群，一次全量 OTA 是以周为单位的痛苦过程。&lt;/p>
&lt;h2 id="2-为什么要在裸机上用-rust">2. 为什么要在裸机上用 Rust&lt;/h2>
&lt;p>2026 年初，团队决定用 Rust 重写 Bootloader，目标平台为 &lt;code>thumbv7em-none-eabihf&lt;/code>（ARM Cortex-M7）。这个决定不是出于技术信仰，而是工程经济学的考量：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>编译期内存安全&lt;/strong>：借用检查器在编译阶段就消灭了 use-after-free、double-free 和缓冲区溢出——这几类问题占据了我们 Bootloader CVE 的约 70%。&lt;/li>
&lt;li>&lt;strong>零成本抽象&lt;/strong>：Rust 的 &lt;code>no_std&lt;/code> 生态提供了 RAII、类型化状态机和穷尽模式匹配，却不引入任何运行时和分配器。&lt;/li>
&lt;li>&lt;strong>生态成熟度&lt;/strong>：&lt;code>embedded-hal&lt;/code>、&lt;code>cortex-m&lt;/code>、&lt;code>cortex-m-rt&lt;/code>、&lt;code>probe-rs&lt;/code> 已达到生产级水准，有活跃维护和商业支持。&lt;/li>
&lt;/ul>
&lt;h2 id="3-架构设计">3. 架构设计&lt;/h2>
&lt;p>我们的 Bootloader 被建模为一个类型级强制的有限状态机：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-rust" data-lang="rust">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">enum&lt;/span> &lt;span style="color:#a6e22e">BootState&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PowerOn(Peripherals),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> VerifyFirmware(Peripherals, FlashHandle),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ReadyToBoot(VerifiedImage),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Fallback(Error),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">impl&lt;/span> BootState {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fn&lt;/span> &lt;span style="color:#a6e22e">transition&lt;/span>(self) -&amp;gt; &lt;span style="color:#a6e22e">Self&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">match&lt;/span> self {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Self::PowerOn(p) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> self.init_peripherals(p),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Self::VerifyFirmware(p, f) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> self.verify_image(p, f),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 编译器保证每个状态都被处理
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>核心设计原则：&lt;strong>非法状态不可表达&lt;/strong>。你不可能在镜像校验尚未完成时意外调用 &lt;code>jump_to_firmware()&lt;/code>，因为该函数只接受 &lt;code>VerifiedImage&lt;/code> 类型——而构造该类型的唯一路径就是通过校验流程。&lt;/p></description></item></channel></rss>