AUTOSAR自适应平台中的Rust应用程序
目录
1. 介绍
本文档介绍了在Rust编程语言中编写AUTOSAR自适应应用程序的程序员接口的当前提案。这个API是在AUTOSAR Rust工作组内创建的。
本文档的目标读者是希望用Rust编程语言编写自适应应用程序的应用程序开发人员。假定读者对自适应AUTOSAR概念及其C++应用程序编程接口有一定的熟悉度。
1.1 动机
C++在汽车领域使用中的挑战,如内存管理复杂性、多线程复杂性、默认复制语义、缺少越界检测等,促使我们引入AUTOSAR自适应Rust绑定。引入Rust绑定的目的是利用Rust的优势来使用AUTOSAR自适应平台,并可能进行未来的结构性变更。
Rust是一种相对较新的编程语言,具有现代化的工具链。它方便、快速发展,且对C++开发人员来说易于理解。它在许多传统上在C++中难以管理的领域带来了改进。Rust还具有高级的编译时检查,可以防止代码中常见但非凡的错误类别。
1.2 Rust绑定优势
引入Rust旨在解决C++的以下挑战:
用例 | C++方法 | Rust方法 |
---|---|---|
内存管理 | 速度优化的手动内存管理 | 编译时检查的形式化基于所有权的内存管理 |
多线程应用程序的数据保护 | 通过文档关联关键部分和数据,通过运行时检测 | Send和Sync限制线程本地和并发访问,由编译器检查 |
数组类数据类型的越界检测 | 默认不进行边界检查。检查边界是更冗长的变体 | 始终在编译时或运行时检查,未检查的访问很冗长且需要仔细审查 |
对象语义 | 对象默认被复制,指针之间可能存在别名 | 对象默认被移动,跨模块防止读写干扰 |
语言设计重点 | 速度和最大灵活性 | 正确性与足够好的速度 |
对象初始化 | 由静态分析器检查初始化 | 编译器要求对象初始化 |
模块 | 在C++20中引入 | 模块是核心语言功能 |
宏 | 宏绕过语法检查并具有非本地效应 | 声明性和程序性类型的宏。支持内省和可变数量的参数 |
代码检查和审查 | 各种代码检查器、依赖管理和构建系统、代码风格 | 单一广泛接受的解决方案:Clippy、cargo、rustfmt |
类型转换 | 隐式转换由MISRA检查防止 | 仅显式类型转换 |
无栈协程 | 从C++20开始的co_await | 从2018年开始的async |
注1:错误传播将自动转换错误类型。
1.3 绑定方法论
尽管有几种Rust绑定生成器可用,我们引入了手动实现的AUTOSAR自适应API的Rust绑定,以提供社区同意并审查的绑定,并避免语言之间自动数据类型转换的不一致性。随着Rust绑定生成器变得更加功能丰富和生产就绪,这种方法可能在未来发生变化。在我们的Rust绑定方法论中,我们希望利用自上而下(包设计驱动实现)和自下而上(接口实现导致可用的包结构)两种世界的最佳部分,以引入易于理解、快速适应新项目并最小化错误使用情况的Rust绑定。
1.3.1 分层架构
从一个简单的应用程序开始,暂时省略ARXML生成的代码,我们可以看到Rust应用程序支持需要多个层。Rust API适配器不能直接与AUTOSAR C++ API通信,因为Rust和C++对象之间没有共同的二进制互操作性。需要一个语言无关的二进制接口ABI。
定义这个接口的一种选择是使用C兼容的数据类型和调用约定,因为C语言的标准化二进制接口可以从C++和Rust两者访问。当前实现在Rust端定义了这个接口,并使用cbindgen生成供C++适配器使用的C(++)头文件。
请注意,这里并不存在用于此目的的C代码,它仅仅使用Rust和C++的C兼容子集进行互操作。
放大AUTOSAR特定部分,它由应用程序无关代码和ARXML生成的代理和骨架代码组成。中间列包含每个骨架或代理的三个单独文件,这些文件是从ARXML生成的:
- ara::com
- ara::com适配层
- ara::com Rust包装器
2. Rust异步编程架构
在AUTOSAR自适应平台中使用Rust编程时,异步编程是一个核心概念,它允许有效地处理I/O操作、通信和并发。以下是Rust异步编程架构的详细解析:
2.1 异步操作和执行器
Rust的异步编程模型基于非阻塞操作,允许程序在等待I/O或其他操作完成时继续执行其他任务。这种模型的核心是异步函数和执行器:
-
异步函数:使用
async
关键字标记的函数,它会创建一个表示将来某个时刻完成的计算的Future对象。- 异步函数的返回类型通常是
impl Future<Output = T>
- 异步函数可以使用
.await
语法等待其他Future完成
- 异步函数的返回类型通常是
-
执行器:负责调度和执行异步任务的组件,它管理任务队列并确保任务在就绪时得到执行。
- 执行器维护任务队列并轮询任务以检查它们是否可以取得进展
- 常见的执行器包括tokio、async-std和futures库中的执行器
-
事件循环:执行器的核心组件,负责监控I/O事件并唤醒相应的任务。
- 事件循环使用操作系统提供的非阻塞I/O原语(如epoll、kqueue或IOCP)
- 当事件发生时,事件循环会唤醒等待该事件的任务
2.2 Future和任务
Rust的异步编程基于Future特性,它表示将来某个时刻可用的值:
-
Future特性:表示异步计算的核心抽象,具有
poll
方法,该方法检查计算是否完成。pub trait Future { type Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>; }
-
Pin:一种特殊的类型,确保Future不会在内存中移动,这对于包含自引用数据的Future是必需的。
- Pin保证对象在内存中的位置不会改变
- 这允许安全地创建自引用结构,这在异步代码中很常见
-
任务(Task):包装Future的执行单元,包含状态和唤醒器。
- 任务包含Future对象、当前状态和唤醒器(Waker)
- 唤醒器用于通知执行器任务已准备好进行进一步处理
2.3 I/O操作和同步原语
Rust异步编程提供了处理I/O和同步的原语:
-
异步I/O:非阻塞的读写操作,允许程序在等待I/O完成时执行其他任务。
- 异步I/O操作返回Future而不是直接阻塞
- 例如:
async fn read(&mut self, buf: &mut [u8]) -> Result<usize>
-
流(Stream):表示一系列异步值,类似于同步迭代器。
pub trait Stream { type Item; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>; }
-
同步原语:异步版本的互斥锁、读写锁等,允许多个任务安全地共享数据。
Mutex<T>
:异步互斥锁,保证对数据的独占访问RwLock<T>
:异步读写锁,允许多个读取者或单个写入者- 这些原语确保在等待锁时不会阻塞执行器
在AUTOSAR自适应平台中,这种异步架构使Rust应用程序能够高效地处理通信、定时器和I/O操作,同时保持响应性和资源效率。
3. ara::com绑定生成架构
AUTOSAR自适应平台中的通信是通过ara::com接口实现的,为了在Rust中使用这些接口,需要一套完整的绑定生成架构:
3.1 服务定义
ara::com绑定生成的起点是ARXML服务定义:
-
ARXML文件:包含服务接口的完整定义,包括:
- 服务接口定义:描述服务的名称、版本和命名空间
- 类型定义:定义服务使用的数据类型,如结构体、枚举和类型别名
- 方法定义:描述服务提供的方法,包括输入参数和返回类型
- 事件定义:描述服务可以触发的事件
-
服务设计考虑:ARXML文件通常由系统设计人员创建,基于功能需求和系统架构。它们定义了服务的契约,所有通信必须遵循这个契约。
3.2 代码生成过程
从ARXML服务定义开始,代码生成过程涉及多个步骤:
-
C++代码生成器:从ARXML生成C++代码,包括:
- C++代理代码:客户端用于调用服务的代码
- C++骨架代码:服务实现用于处理请求的代码
- C++数据类型:用于在服务之间传输数据的类型
-
Rust绑定生成器:从ARXML生成Rust代码,包括:
- Rust代理代码:提供类型安全的异步API
- Rust骨架代码:实现处理服务请求的异步处理程序
- Rust数据类型:在Rust中表示服务数据类型的结构
-
cbindgen:用于从Rust代码生成C/C++头文件的工具,这些头文件定义了Rust和C++代码之间的接口。
3.3 C++与Rust侧代码交互
生成的代码在C++和Rust之间通过以下组件进行交互:
-
C++侧代码:
- 代理代码(C++):提供服务客户端API,处理序列化和异步调用
- 骨架代码(C++):实现服务提供者逻辑,接收请求并调度处理程序
- C++适配器:连接Rust和C++代码,处理内存管理和类型转换
-
Rust侧代码:
- 代理代码(Rust):提供Rust异步API,返回Future对象
- 骨架代码(Rust):实现服务处理逻辑,支持异步处理
- Rust适配器:处理FFI边界,安全封装unsafe代码,管理所有权和生命周期
-
应用代码:
- C++应用:可以使用C++代理调用服务,或实现C++服务骨架
- Rust应用:使用Rust代理异步调用服务,或实现异步服务
这种架构允许C++和Rust代码无缝互操作,同时利用两种语言的优势:C++的广泛使用和与现有AUTOSAR代码的兼容性,以及Rust的安全性、并发性和现代语言特性。
4. AUTOSAR-Rust通信场景
在AUTOSAR自适应平台中,服务之间的通信可以发生在不同的语言环境中。下图展示了四种可能的通信场景:
4.1 纯C++通信
这是最传统的场景,其中C++代理与C++骨架之间进行通信:
- 发送方:C++代理组件(如激光雷达应用代理)
- 接收方:C++骨架组件(如激光雷达应用骨架)
- 特点:
- 使用原生C++ ara::com API
- 不需要跨语言桥接
- 遵循标准AUTOSAR通信流程
4.2 C++到Rust通信
在这种场景中,C++客户端调用Rust服务:
- 发送方:C++代理组件(如摄像头应用代理)
- 接收方:Rust骨架组件(如摄像头应用骨架)
- 特点:
- C++客户端使用标准C++ ara::com API
- Rust服务实现使用Rust ara::com骨架
- 通过语言无关的二进制接口(ABI)进行通信
- Rust服务能够利用Rust的安全特性和并发模型
4.3 Rust到C++通信
在这种场景中,Rust客户端调用C++服务:
- 发送方:Rust代理组件(如超声波应用代理)
- 接收方:C++骨架组件(如超声波应用骨架)
- 特点:
- Rust客户端使用Rust ara::com代理API
- C++服务使用标准C++ ara::com骨架
- Rust调用被转换为C++接口调用
- Rust客户端能够使用异步编程模型
4.4 纯Rust通信
这是最现代化的场景,完全在Rust环境中进行通信:
- 发送方:Rust代理组件(如雷达应用代理)
- 接收方:Rust骨架组件(如雷达应用骨架)
- 特点:
- 完全使用Rust ara::com API
- 充分利用Rust的所有优势:安全性、并发性、异步编程
- 仍然与AUTOSAR通信机制兼容
- 提供现代化的错误处理和类型安全
这四种通信场景展示了AUTOSAR自适应平台与Rust的灵活集成,允许渐进式采用Rust,同时保持与现有C++代码的互操作性。这种灵活性使团队能够在保持系统完整性的同时逐步引入Rust的优势。