2020-01-18 00:59:30 +08:00
|
|
|
|
[#]: collector: (lujun9972)
|
2020-01-19 15:51:25 +08:00
|
|
|
|
[#]: translator: (wxy)
|
2020-01-18 00:59:30 +08:00
|
|
|
|
[#]: reviewer: ( )
|
|
|
|
|
[#]: publisher: ( )
|
|
|
|
|
[#]: url: ( )
|
|
|
|
|
[#]: subject: (C vs. Rust: Which to choose for programming hardware abstractions)
|
|
|
|
|
[#]: via: (https://opensource.com/article/20/1/c-vs-rust-abstractions)
|
|
|
|
|
[#]: author: (Dan Pittman https://opensource.com/users/dan-pittman)
|
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
C 还是 Rust:选择哪个用于编程硬件抽象
|
2020-01-18 00:59:30 +08:00
|
|
|
|
======
|
2020-01-19 17:16:09 +08:00
|
|
|
|
|
|
|
|
|
> 在 Rust 中使用类型级编程可以使硬件抽象更加安全。
|
|
|
|
|
|
2020-01-18 00:59:30 +08:00
|
|
|
|
![Tools illustration][1]
|
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
Rust 是一种日益流行的编程语言,被视为硬件接口的最佳选择。通常会将其与 C 的抽象级别进行比较。本文介绍了 Rust 如何以多种方式处理按位运算,并提供了既安全又易于使用的解决方案。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
语言 | 源自 | 官方说明 | 总览
|
2020-01-18 00:59:30 +08:00
|
|
|
|
---|---|---|---
|
2020-01-19 17:16:09 +08:00
|
|
|
|
C | 1972 年 | C 是一种通用编程语言,具有表达式简约、现代的控制流和数据结构,以及丰富的运算符集等特点。(来源:[CS 基础知识] [2])| C 是(一种)命令式语言,旨在以相对简单的方式进行编译,从而提供对内存的低级访问。(来源:[W3schools.in] [3])
|
|
|
|
|
Rust | 2010 年 | 一种使所有人都能构建可靠、高效的软件的语言(来源:[Rust 网站] [4])| Rust 是一种专注于安全性(尤其是安全并发性)的多范式系统编程语言。(来源:[维基百科] [5])
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
### 在 C 语言中对寄存器值进行按位运算
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
在系统编程领域,你可能经常需要编写硬件驱动程序或直接与内存映射的设备进行交互,而这些交互几乎总是通过硬件提供的内存映射的寄存器来完成的。通常,你通过对某些固定宽度的数字类型进行按位运算来与这些寄存器进行交互。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
例如,假设一个具有三个字段的 8 位寄存器:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
+----------+------+-----------+---------+
|
|
|
|
|
| (unused) | Kind | Interrupt | Enabled |
|
|
|
|
|
+----------+------+-----------+---------+
|
|
|
|
|
5-7 2-4 1 0
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
字段名称下方的数字规定了该字段在寄存器中使用的位。要启用该寄存器,你将写入值 `1`(以二进制表示为`0000_0001`)来设置 `Enabled` 字段的位。但是,通常情况下,你也不想干扰寄存器中的现有配置。假设你要在设备上启用中断功能,但也要确保设备保持启用状态。为此,必须将 `Interrupt` 字段的值与 `Enabled` 字段的值结合起来。你可以通过按位操作来做到这一点:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-19 17:16:09 +08:00
|
|
|
|
1 | (1 << 1)
|
2020-01-18 00:59:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
通过将 1 和 2(左移 `1` 一位得到)进行“或”运算得到二进制值 `0000_0011` 。你可以将其写入寄存器,使其保持启用状态,但也允许中断。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
有很多事情要记住,特别是当你要为一个完整的系统处理可能有数百个之多的寄存器时。实际上,你可以使用助记符来执行此操作,助记符可跟踪字段在寄存器中的位置以及字段的宽度(即它的上边界是什么?)
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
这是这些助记符之一的示例。它们是 C 语言的宏,用右侧的代码替换它们的出现的地方。这是上面列出的寄存器的简写。`&` 的左侧是该字段的位置,而右侧则限制该字段的位:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-19 17:16:09 +08:00
|
|
|
|
#define REG_ENABLED_FIELD(x) (x << 0) & 1
|
|
|
|
|
#define REG_INTERRUPT_FIELD(x) (x << 1) & 2
|
|
|
|
|
#define REG_KIND_FIELD(x) (x << 2) & (7 << 2)
|
2020-01-18 00:59:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
然后,你将使用这些通过类似以下方式来抽象化寄存器值的操作:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
void set_reg_val(reg* u8, val u8);
|
|
|
|
|
|
|
|
|
|
fn enable_reg_with_interrupt(reg* u8) {
|
|
|
|
|
set_reg_val(reg, REG_ENABLED_FIELD(1) | REG_INTERRUPT_FIELD(1));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
这就是现在的做法。实际上,这就是大多数驱动程序出现在 Linux 内核中的方式。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:16:09 +08:00
|
|
|
|
有没有更好的办法?如果能够基于对现代编程语言研究得出新的类型系统,就可能能够获得安全性和可表达性的好处。也就是说,如何使用更丰富、更具表现力的类型系统来使此过程更安全、更持久?
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
### 在 Rust 语言中对寄存器值进行按位运算
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
继续用上面的寄存器作为例子:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
+----------+------+-----------+---------+
|
|
|
|
|
| (unused) | Kind | Interrupt | Enabled |
|
|
|
|
|
+----------+------+-----------+---------+
|
|
|
|
|
5-7 2-4 1 0
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
你可能想如何用 Rust 类型来表示它?
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
你将以类似的方式开始,为每个字段的*偏移*定义常量(即,距最低有效位有多远)及其掩码。*掩码*是一个值,其二进制表示形式可用于更新或读取寄存器内部的字段:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
const ENABLED_MASK: u8 = 1;
|
|
|
|
|
const ENABLED_OFFSET: u8 = 0;
|
|
|
|
|
|
|
|
|
|
const INTERRUPT_MASK: u8 = 2;
|
|
|
|
|
const INTERRUPT_OFFSET: u8 = 1;
|
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
const KIND_MASK: u8 = 7 << 2;
|
2020-01-18 00:59:30 +08:00
|
|
|
|
const KIND_OFFSET: u8 = 2;
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
接下来,你将声明一个 `Field` 类型,并进行操作以将给定值转换为与其位置相关的值以供在寄存器内使用:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
struct Field {
|
2020-01-19 17:28:59 +08:00
|
|
|
|
value: u8,
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Field {
|
2020-01-19 17:28:59 +08:00
|
|
|
|
fn new(mask: u8, offset: u8, val: u8) -> Self {
|
|
|
|
|
Field {
|
|
|
|
|
value: (val << offset) & mask,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
最后,你将使用一个 `Register` 类型,该类型会封装一个与你的寄存器宽度匹配的数字类型。 `Register` 具有 `update` 函数,可使用给定字段来更新寄存器:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
struct Register(u8);
|
|
|
|
|
|
|
|
|
|
impl Register {
|
2020-01-19 17:28:59 +08:00
|
|
|
|
fn update(&mut self, val: Field) {
|
|
|
|
|
self.0 = self.0 | field.value;
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
fn enable_register(&mut reg) {
|
|
|
|
|
reg.update(Field::new(ENABLED_MASK, ENABLED_OFFSET, 1));
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 17:28:59 +08:00
|
|
|
|
使用 Rust,你可以使用数据结构来表示字段,将它们附加到特定的寄存器,并在与硬件交互时提供简洁明了的人机工程学。这个例子使用了 Rust 提供的最基本的功能。无论如何,添加的结构都会减轻上述 C 示例中的某些密度。现在,字段是个已命名的事物,而不是从模糊的按位运算符派生而来的数字,并且寄存器是具有状态的类型 —— 这在硬件上多了一层抽象。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 22:44:12 +08:00
|
|
|
|
### 一个易用的 Rust 实现
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 22:44:12 +08:00
|
|
|
|
用 Rust 重写的第一个版本很好,但是并不理想。你必须记住要带上掩码和偏移量,并且要手工进行临时计算,这容易出错。人类不擅长精确且重复的任务 —— 我们往往会感到疲劳或失去专注力,这会导致错误。一次一个寄存器地手动记录掩码和偏移量几乎可以肯定会很糟糕。这是最好留给机器的任务。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 22:44:12 +08:00
|
|
|
|
其次,从结构上进行思考:如果有一种方法可以让字段的类型携带掩码和偏移信息呢?如果你要在访问硬件寄存器并与之交互的实现过程中就能发现错误,而不是在运行时才发现,该怎么办?也许你可以依靠一种通常用于在编译时解决问题的策略,例如类型。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-19 22:44:12 +08:00
|
|
|
|
你可以使用 [typenum][6] 来修改前面的示例,该库在类型级别提供数字和算术。在这里,你将使用掩码和偏移量对 `Field` 类型进行参数化,使其可用于任何 `Field` 实例,而不必在调用站点中将其包括在内:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
#[macro_use]
|
|
|
|
|
extern crate typenum;
|
|
|
|
|
|
|
|
|
|
use core::marker::PhantomData;
|
|
|
|
|
|
|
|
|
|
use typenum::*;
|
|
|
|
|
|
|
|
|
|
// Now we'll add Mask and Offset to Field's type
|
2020-01-19 22:44:12 +08:00
|
|
|
|
struct Field<Mask: Unsigned, Offset: Unsigned> {
|
|
|
|
|
value: u8,
|
|
|
|
|
_mask: PhantomData<Mask>,
|
|
|
|
|
_offset: PhantomData<Offset>,
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We can use type aliases to give meaningful names to
|
|
|
|
|
// our fields (and not have to remember their offsets and masks).
|
2020-01-19 22:44:12 +08:00
|
|
|
|
type RegEnabled = Field<U1, U0>;
|
|
|
|
|
type RegInterrupt = Field<U2, U1>;
|
|
|
|
|
type RegKind = Field<op!(U7 << U2), U2>;
|
2020-01-18 00:59:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 22:44:12 +08:00
|
|
|
|
现在,当重新访问 `Field` 的构造函数时,你可以忽略掩码和偏移量参数,因为类型中包含该信息:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-19 22:44:12 +08:00
|
|
|
|
impl<Mask: Unsigned, Offset: Unsigned> Field<Mask, Offset> {
|
|
|
|
|
fn new(val: u8) -> Self {
|
|
|
|
|
Field {
|
|
|
|
|
value: (val << Offset::U8) & Mask::U8,
|
|
|
|
|
_mask: PhantomData,
|
|
|
|
|
_offset: PhantomData,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And to enable our register...
|
2020-01-19 22:44:12 +08:00
|
|
|
|
fn enable_register(&mut reg) {
|
|
|
|
|
reg.update(RegEnabled::new(1));
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 22:44:12 +08:00
|
|
|
|
看起来不错,但是……如果你对给定的值是否*适合*某个字段犯了错误,会发生什么?考虑一个简单的输入错误,你在其中放置了 `10` 而不是 `1`:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-19 22:44:12 +08:00
|
|
|
|
fn enable_register(&mut reg) {
|
2020-01-18 00:59:30 +08:00
|
|
|
|
reg.update(RegEnabled::new(10));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-19 22:44:12 +08:00
|
|
|
|
在上面的代码中,预期结果是什么?好吧,代码会将启用位设置为 0,因为 `10&1 = 0`。那真不幸;最好在尝试写入之前知道你要写入字段的值是否适合该字段。事实上,我会考虑放弃错误字段值的高位*未定义行为*(喘气)。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
### 出于安全考虑使用 Rust
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
如何以一般方式检查字段的值是否适合其规定的位置?需要更多类型级别的数字!
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
你可以在 `Field` 中添加 `Width` 参数,并使用它来验证给定的值是否适合该字段:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-21 22:48:02 +08:00
|
|
|
|
struct Field<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> {
|
|
|
|
|
value: u8,
|
|
|
|
|
_mask: PhantomData<Mask>,
|
|
|
|
|
_offset: PhantomData<Offset>,
|
|
|
|
|
_width: PhantomData<Width>,
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
type RegEnabled = Field<U1,U1, U0>;
|
|
|
|
|
type RegInterrupt = Field<U1, U2, U1>;
|
|
|
|
|
type RegKind = Field<U3, op!(U7 << U2), U2>;
|
|
|
|
|
|
|
|
|
|
impl<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> Field<Width, Mask, Offset> {
|
|
|
|
|
fn new(val: u8) -> Option<Self> {
|
|
|
|
|
if val <= (1 << Width::U8) - 1 {
|
|
|
|
|
Some(Field {
|
|
|
|
|
value: (val << Offset::U8) & Mask::U8,
|
|
|
|
|
_mask: PhantomData,
|
|
|
|
|
_offset: PhantomData,
|
|
|
|
|
_width: PhantomData,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
现在,只有给定值适合时,您才能构造一个 `Field`!否则,你将得到 `None` 信号,该信号指示发生了错误,而不是放弃该值的高位并静默写入意外的值。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
但是请注意,这将在运行时引发错误。但是,我们知道我们想事先写入的值,还记得吗?鉴于此,我们可以教编译器完全拒绝具有无效字段值的程序 —— 我们不必等到运行它!
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
这次,您将向 `new` 的新实现 `new_checked` 中添加一个特征绑定(`where` 子句),该函数要求输入值小于或等于给定字段用 ``Width` 所控制的最大可能值:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-21 22:48:02 +08:00
|
|
|
|
struct Field<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> {
|
|
|
|
|
value: u8,
|
|
|
|
|
_mask: PhantomData<Mask>,
|
|
|
|
|
_offset: PhantomData<Offset>,
|
|
|
|
|
_width: PhantomData<Width>,
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
type RegEnabled = Field<U1, U1, U0>;
|
|
|
|
|
type RegInterrupt = Field<U1, U2, U1>;
|
|
|
|
|
type RegKind = Field<U3, op!(U7 << U2), U2>;
|
|
|
|
|
|
|
|
|
|
impl<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> Field<Width, Mask, Offset> {
|
|
|
|
|
const fn new_checked<V: Unsigned>() -> Self
|
|
|
|
|
where
|
|
|
|
|
V: IsLessOrEqual<op!((U1 << Width) - U1), Output = True>,
|
|
|
|
|
{
|
|
|
|
|
Field {
|
|
|
|
|
value: (V::U8 << Offset::U8) & Mask::U8,
|
|
|
|
|
_mask: PhantomData,
|
|
|
|
|
_offset: PhantomData,
|
|
|
|
|
_width: PhantomData,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
只有拥有此属性的数字才具有此特征的实现,因此,如果使用不适合的数字,它将无法编译。让我们看一看!
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-21 22:48:02 +08:00
|
|
|
|
fn enable_register(&mut reg) {
|
|
|
|
|
reg.update(RegEnabled::new_checked::<U10>());
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
2020-01-21 22:48:02 +08:00
|
|
|
|
12 | reg.update(RegEnabled::new_checked::<U10>());
|
|
|
|
|
| ^^^^^^^^^^^^^^^^ expected struct `typenum::B0`, found struct `typenum::B1`
|
|
|
|
|
|
|
|
|
|
|
= note: expected type `typenum::B0`
|
|
|
|
|
found type `typenum::B1`
|
2020-01-18 00:59:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
`new_checked` 将无法生成一个程序,因为该字段的值错误地过高。你的输入错误不会在运行时爆炸,因为你永远无法获得一个可以运行的工件。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 22:48:02 +08:00
|
|
|
|
就使内存映射的硬件进行交互的安全性而言,你已经接近 Peak Rust。但是,你在 C 的第一个示例中所写的内容比最终得到的一锅粥类型参数更简洁。当你谈论潜在的数百甚至数千个寄存器时,这样做是否容易处理?
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
### Rust 恰到好处:既安全又方便使用
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
早些时候,我认为手工计算掩码有问题,但我只是做了同样有问题的事情 —— 尽管是在类型级别。虽然使用这种方法很不错,但要达到编写任何代码的地步,则需要大量样板和手动转录(我在这里谈论的是类型同义词)。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
我们的团队想要像 [TockOS mmio 寄存器][7]之类的东西,但是他们会以最少的手动转录生成类型安全的实现。我们得出的结果是一个宏,该宏生成必要的样板以获得类似 Tock 的 API 以及基于类型的边界检查。 要使用它,请写下一些有关寄存器的信息,其字段、宽度和偏移量以及可选的[枚举][8]类的值(你应该为字段可能具有的值赋予“含义”):
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
register! {
|
2020-01-21 23:43:30 +08:00
|
|
|
|
// The register's name
|
|
|
|
|
Status,
|
|
|
|
|
// The type which represents the whole register.
|
|
|
|
|
u8,
|
|
|
|
|
// The register's mode, ReadOnly, ReadWrite, or WriteOnly.
|
|
|
|
|
RW,
|
|
|
|
|
// And the fields in this register.
|
|
|
|
|
Fields [
|
|
|
|
|
On WIDTH(U1) OFFSET(U0),
|
|
|
|
|
Dead WIDTH(U1) OFFSET(U1),
|
|
|
|
|
Color WIDTH(U3) OFFSET(U2) [
|
|
|
|
|
Red = U1,
|
|
|
|
|
Blue = U2,
|
|
|
|
|
Green = U3,
|
|
|
|
|
Yellow = U4
|
|
|
|
|
]
|
|
|
|
|
]
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
由此,你可以生成寄存器和字段类型,如上例所示,其中索引:`Width`、`Mask` 和 `Offset` 是从一个字段定义的 `WIDTH` 和 `OFFSET` 部分的输入的值派生的。另外,请注意,所有这些数字都是 “类型数字”;他们将直接进入你的 `Field` 定义!
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
生成的代码通过为寄存器及字段指定名称来为寄存器及其相关字段提供名称空间。这很绕口,看起来是这样的:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
mod Status {
|
2020-01-21 23:43:30 +08:00
|
|
|
|
struct Register(u8);
|
|
|
|
|
mod On {
|
|
|
|
|
struct Field; // There is of course more to this definition
|
|
|
|
|
}
|
|
|
|
|
mod Dead {
|
|
|
|
|
struct Field;
|
|
|
|
|
}
|
|
|
|
|
mod Color {
|
|
|
|
|
struct Field;
|
|
|
|
|
pub const Red: Field = Field::<U1>new();
|
|
|
|
|
// &c.
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
生成的 API 包含名义上期望的读取和写入的原语,以获取原始寄存器的值,但它也有办法获取单个字段的值、执行集体操作以及确定是否有任何(或全部)位集合的方法。你可以阅读[完整生成的 API][9]上的文档。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
### 粗略检查
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
将这些定义用于实际设备会是什么样?代码中是否会充斥着类型参数,从而掩盖了视图中的实际逻辑?
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
不会!通过使用类型同义词和类型推断,你实际上根本不必考虑程序的类型级别部分。你可以直接与硬件交互,并自动获得与边界相关的保证。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
这是一个 [UART] [10] 寄存器块的示例。我将跳过寄存器本身的声明,因为包括在这里就太多了。而是从寄存器“块”开始,然后帮助编译器知道如何从指向该块开头的指针中查找寄存器。我们通过实现 `Deref` 和 `DerefMut` 来做到这一点:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
#[repr(C)]
|
|
|
|
|
pub struct UartBlock {
|
2020-01-21 23:43:30 +08:00
|
|
|
|
rx: UartRX::Register,
|
|
|
|
|
_padding1: [u32; 15],
|
|
|
|
|
tx: UartTX::Register,
|
|
|
|
|
_padding2: [u32; 15],
|
|
|
|
|
control1: UartControl1::Register,
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Regs {
|
2020-01-21 23:43:30 +08:00
|
|
|
|
addr: usize,
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Deref for Regs {
|
2020-01-21 23:43:30 +08:00
|
|
|
|
type Target = UartBlock;
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
fn deref(&self) -> &UartBlock {
|
|
|
|
|
unsafe { &*(self.addr as *const UartBlock) }
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DerefMut for Regs {
|
2020-01-21 23:43:30 +08:00
|
|
|
|
fn deref_mut(&mut self) -> &mut UartBlock {
|
|
|
|
|
unsafe { &mut *(self.addr as *mut UartBlock) }
|
|
|
|
|
}
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
一旦到位,使用这些寄存器就像 `read()` 和 `modify()` 一样简单:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
fn main() {
|
2020-01-21 23:43:30 +08:00
|
|
|
|
// A pretend register block.
|
|
|
|
|
let mut x = [0_u32; 33];
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
let mut regs = Regs {
|
|
|
|
|
// Some shenanigans to get at `x` as though it were a
|
|
|
|
|
// pointer. Normally you'd be given some address like
|
|
|
|
|
// `0xDEADBEEF` over which you'd instantiate a `Regs`.
|
|
|
|
|
addr: &mut x as *mut [u32; 33] as usize,
|
|
|
|
|
};
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
assert_eq!(regs.rx.read(), 0);
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
regs.control1
|
|
|
|
|
.modify(UartControl1::Enable::Set + UartControl1::RecvReadyInterrupt::Set);
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
// The first bit and the 10th bit should be set.
|
|
|
|
|
assert_eq!(regs.control1.read(), 0b_10_0000_0001);
|
2020-01-18 00:59:30 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
当我们使用运行时值时,我们使用如前所述的 `Option`。这里我使用的是 `unwrap`,但是在一个实际的输入未知的程序中,你可能想检查一下从新调用中返回的 “Some”: [^1] [^2]
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
fn main() {
|
|
|
|
|
// A pretend register block.
|
|
|
|
|
let mut x = [0_u32; 33];
|
|
|
|
|
|
|
|
|
|
let mut regs = Regs {
|
|
|
|
|
// Some shenanigans to get at `x` as though it were a
|
|
|
|
|
// pointer. Normally you'd be given some address like
|
|
|
|
|
// `0xDEADBEEF` over which you'd instantiate a `Regs`.
|
|
|
|
|
addr: &mut x as *mut [u32; 33] as usize,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let input = regs.rx.get_field(UartRX::Data::Field::Read).unwrap();
|
|
|
|
|
regs.tx.modify(UartTX::Data::Field::new(input).unwrap());
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
### 解码失败条件
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
根据你的个人痛苦阈值,你可能已经注意到错误几乎是无法理解的。看一下我在说什么的微妙提醒:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-21 23:43:30 +08:00
|
|
|
|
error[E0271]: type mismatch resolving `<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B0>, typenum::B1>, typenum::B0>, typenum::B0> as typenum::IsLessOrEqual<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UInt<typenum::UTerm, typenum::B1>, typenum::B0>, typenum::B1>, typenum::B0>>>::Output == typenum::B1`
|
|
|
|
|
--> src/main.rs:12:5
|
|
|
|
|
|
|
|
|
|
|
12 | less_than_ten::<U20>();
|
|
|
|
|
| ^^^^^^^^^^^^^^^^^^^^ expected struct `typenum::B0`, found struct `typenum::B1`
|
|
|
|
|
|
|
|
|
|
|
= note: expected type `typenum::B0`
|
|
|
|
|
found type `typenum::B1`
|
2020-01-18 00:59:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
` expected typenum::B0 found typenum::B1` 部分是有意义的,但是 ` typenum::UInt<typenum::UInt, typenum::UInt…` 到底是什么呢?好吧,`typenum` 将数字表示为二进制[cons][13] 单元!像这样的错误使操作变得很困难,尤其是当你将多个这些类型级别的数字限制在狭窄的范围内时,你很难知道它在说哪个数字。当然,除非你将巴洛克式二进制表示形式转换为十进制表示形式是第二天性。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
在第 U100 次试图从这个混乱中破译任何含义之后,一个队友简直《疯了,地狱了,不再要再忍受了》,并做了一个小工具,`tnfilt`,从痛苦中解脱了含义,它是命名空间的二进制约束单元。`tnfilt` 采用 cons 单元格样式表示法,并将其替换为明智的十进制数字。我们认为其他人也会遇到类似的困难,所以我们分享了 [tnfilt][14]。你可以像这样使用它:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-21 23:43:30 +08:00
|
|
|
|
$ cargo build 2>&1 | tnfilt
|
2020-01-18 00:59:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
它将上面的输出转换为如下所示:
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
```
|
2020-01-21 23:43:30 +08:00
|
|
|
|
error[E0271]: type mismatch resolving `<U20 as typenum::IsLessOrEqual<U10>>::Output == typenum::B1`
|
2020-01-18 00:59:30 +08:00
|
|
|
|
```
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
现在*这*很有意义!
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
### 结论
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
当与软件中的硬件进行交互时,普遍使用内存映射寄存器,并且有无数种方法来描述这些交互,每种方法在易用性和安全性上都有不同的权衡。我们发现使用类型级编程来获取内存映射寄存器交互的编译时检查为我们提供了制作更安全软件的必要信息。该代码可在 [bounded-registers][15] crate(Rust包)中找到。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
我们的团队从安全性较高的一面开始,然后尝试找出如何将易用滑块移近易用端。从这些雄心壮志中,“边界寄存器”就诞生了,我们在 Auxon 冒险中遇到遇到内存映射设备的任何时候都可以使用它。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
* * *
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
[^1]: 从技术上讲,从定义上看,从寄存器字段读取的值只能在规定的范围内,但是我们当中没有一个人生活在一个纯净的世界中,而且你永远都不知道外部系统发挥作用时会发生什么。你是在这里接受硬件之神的命令,因此与其强迫你进入“可能的恐慌”状态,还不如给你提供处理“这将永远不会发生”的机会。
|
|
|
|
|
[^2]: `get_field` 看起来有点奇怪。我正在专门查看 `Field::Read` 部分。`Field` 是一种类型,你需要该类型的实例才能传递给 `get_field`。更干净的 API 可能类似于:`regs.rx.get_field::<UartRx::Data::Field>();`但是请记住,`Field` 是类型的同义词,它具有固定的宽度、偏移量等索引。要像这样对 `get_field` 进行参数化,你需要使用更高级的类型。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
* * *
|
|
|
|
|
|
2020-01-21 23:43:30 +08:00
|
|
|
|
此内容最初出现在 [Auxon Engineering 博客] [16]上,并经许可进行编辑和重新发布。
|
2020-01-18 00:59:30 +08:00
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
via: https://opensource.com/article/20/1/c-vs-rust-abstractions
|
|
|
|
|
|
|
|
|
|
作者:[Dan Pittman][a]
|
|
|
|
|
选题:[lujun9972][b]
|
2020-01-21 23:43:30 +08:00
|
|
|
|
译者:[wxy](https://github.com/wxy)
|
2020-01-18 00:59:30 +08:00
|
|
|
|
校对:[校对者ID](https://github.com/校对者ID)
|
|
|
|
|
|
|
|
|
|
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
|
|
|
|
|
|
|
|
|
[a]: https://opensource.com/users/dan-pittman
|
|
|
|
|
[b]: https://github.com/lujun9972
|
|
|
|
|
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/tools_hardware_purple.png?itok=3NdVoYhl (Tools illustration)
|
|
|
|
|
[2]: https://cs-fundamentals.com/c-programming/history-of-c-programming-language.php
|
|
|
|
|
[3]: https://www.w3schools.in/c-tutorial/history-of-c/
|
|
|
|
|
[4]: https://www.rust-lang.org/
|
|
|
|
|
[5]: https://en.wikipedia.org/wiki/Rust_(programming_language)
|
|
|
|
|
[6]: https://docs.rs/crate/typenum
|
|
|
|
|
[7]: https://docs.rs/tock-registers/0.3.0/tock_registers/
|
|
|
|
|
[8]: https://en.wikipedia.org/wiki/Enumerated_type
|
|
|
|
|
[9]: https://github.com/auxoncorp/bounded-registers#the-register-api
|
|
|
|
|
[10]: https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter
|
|
|
|
|
[11]: tmp.shpxgDsodx#1
|
|
|
|
|
[12]: tmp.shpxgDsodx#2
|
|
|
|
|
[13]: https://en.wikipedia.org/wiki/Cons
|
|
|
|
|
[14]: https://github.com/auxoncorp/tnfilt
|
|
|
|
|
[15]: https://crates.io/crates/bounded-registers
|
|
|
|
|
[16]: https://blog.auxon.io/2019/10/25/type-level-registers/
|