本文是对嵌入式 Rust 先行者 Julius Gustavsson 的采访。他是沃尔沃汽车公司的技术专家和系统架构师。
JF = Johannes Foufas(采访者),JG = Julius Gustavsson(受访者) 。
JF:你如何发现了 Rust?
JG:我想,那要追溯到 2014 年,那时,我从事 C 和 C++ 开发已经 15 年了。我当时是瑞典一家大型科技公司里一个新团队的一员。他们的代码风格很先进,他们以自己的代码库为荣。看起来很不错。你知道,我对开始在那里工作感到兴奋,但结果,我还是要调试和之前一样的内存模糊问题。
我突然有个想法,这是最好的结果了吗?
我的职业生涯就这样了吗?
我下半辈子都要做这个吗?
有趣的是,大概是在那一周或之后的一周,我在 Reddit 上看到了 Rust 1.0 最终版本即将发布的帖子。显然,它履行了承诺,和我在 2009 年前后第一次发现 Rust 时一样。
左边是 Julius,右边是 Johannes
从那时起,我就开始在私下里关注 Rust 项目。几年后,当我加入沃尔沃汽车公司时,我已经被它所吸引,我认为它对沃尔沃汽车公司来说很有用,因为它的设计理念与你在开发安全关键软件时所秉持的原则是一致的。你肯定愿意将质量保证提前。
JF:那些模糊不清、难以发现的错误,最让人纠结。那不会经常发生,但一旦发生,就会让人痛苦不堪。
JG:是的,的确是。
JF:你那时会在业余时间编写 Rust 代码吗,私下里?
JG:没有什么重要的事。但当我开始在沃尔沃的工作时,我的第一个项目是在 Core Computer 的原型上对 Signal Broker 做 Android 集成。那是一个面向代理的硬件抽象层(HAL),本身是用 Elixir 编写的。我是用 Rust 和 async Futures 实现的。这也在很大程度上证实了这确实是一种有用的东西。所有东西从一开始就是有效的。我的意思是,只要构建完成,运行时通常都可以运行。当然,它不能解决现实存在的任何逻辑错误。但是,如果你的逻辑没问题,它就会很神奇,编译完了就可以工作。当然,有时候你可能会遇到编译难题,特别是当编译器认为你试图做的事情是错误的。
JF:你是从那以后变得更当真了?
JG:后来,我就向经理们推销说,“如果我们想在公司内部认真开展 Rust 项目,那么我愿意参与其中,并成为公司的一名员工。”我这样说是因为我那时还是一名顾问。
我就是这样成了沃尔沃的员工,也就是在那时,我遇到了 Niko(Nikolaos Korkakakis),我们的志向相同。因此,我们开始合作,研究核心计算机的低功耗节点。从根本上说,我们能拥有这个节点纯属侥幸,因为每个人都在忙着处理其他的节点,没有人真正注意到我们这个。而且,它碰巧运行在当时 Rust 在嵌入式裸金属领域支持最好的架构上。此外,它也不是安全关键部件,所以我们不必担心安全认证。
JF:是啊,那一般都很麻烦。
JG:我们不需要担心安全问题。
但与此同时,它必须非常可靠,因为如果它不能工作,汽车将无法启动。
此外,由于功能范围有限,我们在开展第一个项目时可以只是一个小团队。
JF:对你们来说,还缺什么东西吗?你们是否需要开源社区提供的什么东西,或者需要完全靠自己开发一个?
JG:是的,我们并不是什么都有。有些部分是缺失的。我们运行在一块 ATMEL/Microchip ATSAME 芯片上,有一个开源项目是专门针对它的。
Rust 提供了这样一个硬件抽象层,类似于 Autosar 中的 MCAL 。那个项目最近才开始,还只有少数几个外围设备,许多我们需要的外围设备还没到位。这和我们在汽车行业中经常使用的驱动程序(如 CAN)情况类似。为此,我们一直在积极参与那个项目。
JF:你们那时还与其他人合作了吗?
JG:是的,我们在 Rust 大会上了接触到了 Grepit。Grepit 成立于 2014 年,是 Luleå科技大学的下属公司。他们创建了 cortex-m-rtic,那是一个用于构建实时系统的并发框架。
有了它,你就可以在系统中实现实时行为,但也仅此而已。它不提供任何其他 RTOS 通常会提供的高级抽象或服务之类的东西。不过,我们可以从其他许多可用的开源组件中获得这些功能。所以,是的,我们和 Grepit 开展了合作,为的是让我们可以运转起来。开发驱动程序并将其推送给项目的上游。也有一些开源项目没有提供的实现,我们不得不从头开始开发,但这并没有对我们造成多大阻碍。可以使用的工具很多很多。
JF:你有 C++ 世界的经验。相比之下,使用 Rust 带来了什么直接的好处吗?我是说现在,当你们已经有一些用 Rust 编写的东西在运行的时候。
JG:我想说的是,我从一开始就看到的好处是,不必再考虑竞态条件和内存崩溃,以及一般的内存安全。你知道的,从一开始就编写正确而健壮的代码。我的第一印象基本就是这样,但现在,我也开始意识到,还有很多其他的方面。第一个方面的副作用也给我们带来了同样大的好处。首先,让我们回顾并简要描述一下 Rust 是如何实现内存安全的,非常独特。它对程序中数据的生命周期做静态分析,确保对该数据的任何引用都不会超过数据本身的生命周期。在任何给定的时间,永远都不允许有多个可变实例,或者,可以有多个读取数据的实例,但永远不能混合使用这两种实例。通过编译时的静态分析,你不必付出任何代价就实现了内存安全,因为编译器知道数据的生命周期何时结束,它会在相应的位置上注入清理代码。
JF:除非你使用 unsafe 关键字?
JG:恩,不安全的部分会提供额外的灵活性,让你可以搬起石头砸自己的脚。但那并不是说所有的一切都不可预料了。即使是不安全的情况也支持很多不变量。例如,有关生命周期的东西就仍然支持。区别在于,你可以使用原始指针了。从根本上说,它们擦除了所有的生命周期信息,或者你可以绕过编译器的限制。其中也包括编译器自己无法推断而必须留给人来做的东西,这类东西应该总是在一个明确标记为不安全的块中完成,以便可以专门审计。
但这并不像许多人想象的那样,仅仅因为不安全就回到了 C 语言。事实并非如此。你有很多安全措施可以采用,虽然与安全子集相比,它们要宽松许多。但是,由编译器强制实施这个内存模型、生命周期和所有权模型的好处是,所有人都在同一起跑线上,这使你更容易直接导入和使用第三方组件。因为 Rust 自带一个内置的工具链,负责构建、获取和解析依赖项,所以添加新的依赖项也变得更容易、更安全。
你不再需要检查:
构建了吗?
失败是构建系统导致的吗?
你不必再为了构建和链接而修改构建系统。如果我不用再考虑下面这些问题,你在使用第三方组件时会感觉轻松很多:
第三方库对于内存和所有权所做的假设和我一样吗?
谁将负责释放这块内存?
如果我必须创建一个缓冲区,谁来删除它,诸如此类。
JF:这是有规则的吗?
JG:是的。
JF:程序包呢?你是把什么都拿过来,还是有足够的理由时才拿过来?
JG:我们尽量将依赖降到最低,因为我们正在创造的产品将会存在很长时间。因此,我们引入的所有依赖项都是我们自认为可以维护的东西,以防我们需要修复 Bug 或其他什么东西。有一个 Cargo 的内置插件,可以对代码进行审计,然后对照数据库进行检查,并报告在产品生命周期中可能出现的任何漏洞或其他问题。
还有一个我一开始没有意识到的好处是,让新员工加入进来更容易,因为新员工可以自由地使用代码库,尝试改进、更改或重构它,除非所有的不变量都被再次维护,否则编译器都不会编译。也就是说,你可以毫无顾虑地进行重构,新人可以开始编写代码,而且不需要进行详细的审核,因为你知道,有很多不成文的不变量,只有少数人知道。我很有信心,随着时间的推移,这也会减少维保问题,因为你将质量保证提前了。
JF:是的,你不需要在产品上运行那么多工具来确认它是否安全,而在 C 和 C++ 代码上,我们要运行一整套工具来设法找出那些难以发现的错误。
JG:是的,不需要到那样的程度。有一些运行时特征之类的东西,你可能需要检查一下。此外,这还取决于你希望代码得到多大程度的正式验证。你希望以静态的方式确保这些事情永远不会发生吗?
对于部分运行时行为,你总是需要借助一些其他的工具。
JF:有像这样的工具吗?
JG:有,而且越来越多。我不确定是否有工具可以完成所有我们希望它完成的工作,但我们正在试用几个这样的工具。
例如 Miri,从根本上讲,它是编译期间在虚拟机中运行代码,从而找出代码库中任何不健全之处。
JF:大学里不是也有人做了一个这样的工具吗?
JG:是的,有,比如 KLEE,这是一个基于 LLVM 编译基础设施构建的动态符号执行引擎,已经针对 Rust 做了调整。这个工具可以帮你确定,某个东西在某种情况下是否会成为慌乱之源。我们还没有开始使用它,但它是我们想要探索的东西。Kani 验证器看起来也不错。
JF:分析器呢,是语言自带的吗?
很遗憾,在嵌入式目标上没有,至少现在还没有。在 Rust 的最新版本中,你可以给编译器提供一个代码检测覆盖率标识,这样就能得到额外的说明信息。然后,它将为你显示实际执行的代码路径。我们需要做一些工作,把它应用到嵌入式目标上。我知道,它在台式机上有效,因为它可以动态生成那些文件。当然,嵌入式目标上没有文件系统或文件,所以你需要自己把它写入内部缓冲区。对于性能分析,你可以使用标准的桌面工具,但我不确定效果怎么样。在进行测试时,我们会设法将所有硬件无关的逻辑隔离到它们自己的 crate 中。因为这样,我们就可以运行它们并使用该语言提供的内置测试支持。我们可以在那些硬件上运行常规的测试基础设施,但是,当在实际的目标硬件上进行集成测试时,我们就会使用集成测试工具。
JF:所以,你们可以大量依赖于 X86?
JG:是的,我们为主机构建代码并在那里进行测试。这也是 Rust 的强大之处。它提供了完美的跨平台支持。它只是工具链的一部分,与 C 或 C++ 的体验完全不同。
JF:如何测试它们?我的意思是,Rust 是否提供了什么测试框架?
JG:有一个内置的单元测试框架。代码中的任何函数都可以作为一个测试来运行,只需用特定的标记对其进行注释。你可以在常规代码中穿插测试,当进行测试构建时,它们就会运行。单元测试几乎没有任何门槛。它是内置的,非常奇妙。编写基准测试来检查函数的改进情况也很容易。同样的测试框架也可以用于编写集成测试,在桌面上运行很容易,但当你在目标上执行时,就不是那么天衣无缝了。但是,有一个新的 Rust 项目 Probe-rs,让我们可以用一种相当流畅的方式与目标硬件进行通信。它提供了 GDB 类型的接口库,因此,你可以针对目标环境编写测试应用。在主机上时,它只是将 probe-rs 作为库加载,然后你可以进行交互,并像命令一样发送 GDB,或者作为测试的一部分,通过网络进行低级硬件操作。
JF:对于未来,你有什么看法?
JG:我们有雄心勃勃的计划。
我们希望在沃尔沃汽车公司将 Rust 扩展到更多的节点上,为此,对于某些硬件目标,我们需要获得编译器支持,对于其他目标,我们需要获得操作系统支持。替换已经开发完成并经过测试的代码没有意义,但是,只要可行,从头开发的代码肯定应该用 Rust 开发。这并不是说 Rust 是万能的。Rust 仍然有一些不完善的地方,你需要进行权衡,有时候,这可能并不是最好的做法。但总的来说,我认为 Rust 潜力巨大,可以让我们以更低的成本开发出更高质量的代码,降低我们的维护成本,最终实现双赢。
JF:但是,Rust 能和基于 C 的代码库共存吗?
JG:它几乎可以在任何粒度上与之共存,可以在模块级上共存,也可以在函数级上共存,这取决于你在做什么。例如,你可以重写需要网络安全的部分,那些容易遭受攻击的部分。C 和 Rust 之间的调用没有开销,C 可以调用 Rust,反之亦然。即使是 C++,也有一些方法,虽然还是得通过 C 接口,但有一些不错的 crate 可以帮助你生成额外的样板代码,使得 C++ 和 Rust 可以无缝通信。
安全关键行业中还有许多可以吸收的东西,我们正设法提供尽可能多的支持。
例如,有一个 Ferrocene 项目,旨在对编译器做 ASIL D 认证。Ferrocene 将是现有开源编译器 rustc 的 ISO26262 限定版本。
AUTOSAR 和 SAE 也成立了一个 Rust 工作组。
原文链接:
相关阅读:
活动推荐:
2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。
评论