mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
PART 5
This commit is contained in:
parent
0ef0271f2d
commit
2f6b53dc8c
@ -269,124 +269,119 @@ fn enable_register(&mut reg) {
|
||||
|
||||
就使内存映射的硬件进行交互的安全性而言,你已经接近 Peak Rust。但是,你在 C 的第一个示例中所写的内容比最终得到的一锅粥类型参数更简洁。当你谈论潜在的数百甚至数千个寄存器时,这样做是否容易处理?
|
||||
|
||||
### Just right with Rust: both safe and accessible
|
||||
### Rust 恰到好处:既安全又方便使用
|
||||
|
||||
Earlier, I called out calculating masks by hand as being problematic, but I just did that same problematic thing—albeit at the type level. While using such an approach is nice, getting to the point when you can write any code requires quite a bit of boilerplate and manual transcription (I'm talking about the type synonyms here).
|
||||
|
||||
Our team wanted something like the [TockOS mmio registers][7], but one that would generate typesafe implementations with the least amount of manual transcription possible. The result we came up with is a macro that generates the necessary boilerplate to get a Tock-like API plus type-based bounds checking. To use it, write down some information about a register, its fields, their width and offsets, and optional [enum][8]-like values (should you want to give "meaning" to the possible values a field can have):
|
||||
早些时候,我认为手工计算掩码有问题,但我只是做了同样有问题的事情 —— 尽管是在类型级别。虽然使用这种方法很不错,但要达到编写任何代码的地步,则需要大量样板和手动转录(我在这里谈论的是类型同义词)。
|
||||
|
||||
我们的团队想要像 [TockOS mmio 寄存器][7]之类的东西,但是他们会以最少的手动转录生成类型安全的实现。我们得出的结果是一个宏,该宏生成必要的样板以获得类似 Tock 的 API 以及基于类型的边界检查。 要使用它,请写下一些有关寄存器的信息,其字段、宽度和偏移量以及可选的[枚举][8]类的值(你应该为字段可能具有的值赋予“含义”):
|
||||
|
||||
```
|
||||
register! {
|
||||
// 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
|
||||
]
|
||||
]
|
||||
// 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
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
From this, you can generate register and field types like the previous example where the indices—the `Width`, `Mask`, and `Offset`—are derived from the values input in the `WIDTH` and `OFFSET` sections of a field's definition. Also, notice that all of these numbers are `typenums`; they're going to go directly into your `Field` definitions!
|
||||
|
||||
The generated code provides namespaces for registers and their associated fields through the name given for the register and the fields. That's a mouthful; here's what it looks like:
|
||||
由此,你可以生成寄存器和字段类型,如上例所示,其中索引:`Width`、`Mask` 和 `Offset` 是从一个字段定义的 `WIDTH` 和 `OFFSET` 部分的输入的值派生的。另外,请注意,所有这些数字都是 “类型数字”;他们将直接进入你的 `Field` 定义!
|
||||
|
||||
生成的代码通过为寄存器及字段指定名称来为寄存器及其相关字段提供名称空间。这很绕口,看起来是这样的:
|
||||
|
||||
```
|
||||
mod Status {
|
||||
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.
|
||||
}
|
||||
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.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generated API contains the nominally expected read and write primitives to get at the raw register value, but it also has ways to get a single field's value, do collective actions, and find out if any (or all) of a collection of bits is set. You can read the documentation on the [complete generated API][9].
|
||||
生成的 API 包含名义上期望的读取和写入的原语,以获取原始寄存器的值,但它也有办法获取单个字段的值、执行集体操作以及确定是否有任何(或全部)位集合的方法。你可以阅读[完整生成的 API][9]上的文档。
|
||||
|
||||
### Kicking the tires
|
||||
### 粗略检查
|
||||
|
||||
What does it look like to use these definitions for a real device? Will the code be littered with type parameters, obscuring any real logic from view?
|
||||
将这些定义用于实际设备会是什么样?代码中是否会充斥着类型参数,从而掩盖了视图中的实际逻辑?
|
||||
|
||||
No! By using type synonyms and type inference, you effectively never have to think about the type-level part of the program at all. You get to interact with the hardware in a straightforward way and get those bounds-related assurances automatically.
|
||||
|
||||
Here's an example of a [UART][10] register block. I'll skip the declaration of the registers themselves, as that would be too much to include here. Instead, it starts with a register "block" then helps the compiler know how to look up the registers from a pointer to the head of the block. We do that by implementing `Deref` and `DerefMut`:
|
||||
不会!通过使用类型同义词和类型推断,你实际上根本不必考虑程序的类型级别部分。你可以直接与硬件交互,并自动获得与边界相关的保证。
|
||||
|
||||
这是一个 [UART] [10] 寄存器块的示例。我将跳过寄存器本身的声明,因为包括在这里就太多了。而是从寄存器“块”开始,然后帮助编译器知道如何从指向该块开头的指针中查找寄存器。我们通过实现 `Deref` 和 `DerefMut` 来做到这一点:
|
||||
|
||||
```
|
||||
#[repr(C)]
|
||||
pub struct UartBlock {
|
||||
rx: UartRX::Register,
|
||||
_padding1: [u32; 15],
|
||||
tx: UartTX::Register,
|
||||
_padding2: [u32; 15],
|
||||
control1: UartControl1::Register,
|
||||
rx: UartRX::Register,
|
||||
_padding1: [u32; 15],
|
||||
tx: UartTX::Register,
|
||||
_padding2: [u32; 15],
|
||||
control1: UartControl1::Register,
|
||||
}
|
||||
|
||||
pub struct Regs {
|
||||
addr: usize,
|
||||
addr: usize,
|
||||
}
|
||||
|
||||
impl Deref for Regs {
|
||||
type Target = UartBlock;
|
||||
type Target = UartBlock;
|
||||
|
||||
fn deref(&self) -> &UartBlock {
|
||||
unsafe { &*(self.addr as *const UartBlock) }
|
||||
}
|
||||
fn deref(&self) -> &UartBlock {
|
||||
unsafe { &*(self.addr as *const UartBlock) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Regs {
|
||||
fn deref_mut(&mut self) -> &mut UartBlock {
|
||||
unsafe { &mut *(self.addr as *mut UartBlock) }
|
||||
}
|
||||
fn deref_mut(&mut self) -> &mut UartBlock {
|
||||
unsafe { &mut *(self.addr as *mut UartBlock) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once this is in place, using these registers is as simple as `read()` and `modify()`:
|
||||
|
||||
一旦到位,使用这些寄存器就像 `read()` 和 `modify()` 一样简单:
|
||||
|
||||
```
|
||||
fn main() {
|
||||
// A pretend register block.
|
||||
let mut x = [0_u32; 33];
|
||||
// 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 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,
|
||||
};
|
||||
|
||||
assert_eq!(regs.rx.read(), 0);
|
||||
assert_eq!(regs.rx.read(), 0);
|
||||
|
||||
regs.control1
|
||||
.modify(UartControl1::Enable::Set + UartControl1::RecvReadyInterrupt::Set);
|
||||
regs.control1
|
||||
.modify(UartControl1::Enable::Set + UartControl1::RecvReadyInterrupt::Set);
|
||||
|
||||
// The first bit and the 10th bit should be set.
|
||||
assert_eq!(regs.control1.read(), 0b_10_0000_0001);
|
||||
// The first bit and the 10th bit should be set.
|
||||
assert_eq!(regs.control1.read(), 0b_10_0000_0001);
|
||||
}
|
||||
```
|
||||
|
||||
When we're working with runtime values we use `Option` like we saw earlier. Here I'm using `unwrap`, but in a real program with unknown inputs, you'd probably want to check that you got a `Some` back from that new call:[1][11],[2][12]
|
||||
|
||||
当我们使用运行时值时,我们使用如前所述的 `Option`。这里我使用的是 `unwrap`,但是在一个实际的输入未知的程序中,你可能想检查一下从新调用中返回的 “Some”: [^1] [^2]
|
||||
|
||||
```
|
||||
fn main() {
|
||||
@ -405,65 +400,51 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Decoding failure conditions
|
||||
|
||||
Depending on your personal pain threshold, you may have noticed that the errors are nearly unintelligible. Take a look at a not-so-subtle reminder of what I'm talking about:
|
||||
### 解码失败条件
|
||||
|
||||
根据你的个人痛苦阈值,你可能已经注意到错误几乎是无法理解的。看一下我在说什么的微妙提醒:
|
||||
|
||||
```
|
||||
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`
|
||||
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`
|
||||
```
|
||||
|
||||
The `expected typenum::B0 found typenum::B1` part kind of makes sense, but what on earth is the `typenum::UInt<typenum::UInt, typenum::UInt…` nonsense? Well, `typenum` represents numbers as binary [cons][13] cells! Errors like this make it hard, especially when you have several of these type-level numbers confined to tight quarters, to know which number it's talking about. Unless, of course, it's second nature for you to translate baroque binary representations to decimal ones.
|
||||
|
||||
After the `U100`th time attempting to decipher any meaning from this mess, a teammate got Mad As Hell And Wasn't Going To Take It Anymore and made a little utility, `tnfilt`, to parse the meaning out from the misery that is namespaced binary cons cells. `tnfilt` takes the cons cell-style notation and replaces it with sensible decimal numbers. We imagine that others will face similar difficulties, so we shared [`tnfilt`][14]. You can use it like this:
|
||||
` expected typenum::B0 found typenum::B1` 部分是有意义的,但是 ` typenum::UInt<typenum::UInt, typenum::UInt…` 到底是什么呢?好吧,`typenum` 将数字表示为二进制[cons][13] 单元!像这样的错误使操作变得很困难,尤其是当你将多个这些类型级别的数字限制在狭窄的范围内时,你很难知道它在说哪个数字。当然,除非你将巴洛克式二进制表示形式转换为十进制表示形式是第二天性。
|
||||
|
||||
在第 U100 次试图从这个混乱中破译任何含义之后,一个队友简直《疯了,地狱了,不再要再忍受了》,并做了一个小工具,`tnfilt`,从痛苦中解脱了含义,它是命名空间的二进制约束单元。`tnfilt` 采用 cons 单元格样式表示法,并将其替换为明智的十进制数字。我们认为其他人也会遇到类似的困难,所以我们分享了 [tnfilt][14]。你可以像这样使用它:
|
||||
|
||||
```
|
||||
`$ cargo build 2>&1 | tnfilt`
|
||||
$ cargo build 2>&1 | tnfilt
|
||||
```
|
||||
|
||||
It transforms the output above into something like this:
|
||||
|
||||
它将上面的输出转换为如下所示:
|
||||
|
||||
```
|
||||
`error[E0271]: type mismatch resolving `<U20 as typenum::IsLessOrEqual<U10>>::Output == typenum::B1``
|
||||
error[E0271]: type mismatch resolving `<U20 as typenum::IsLessOrEqual<U10>>::Output == typenum::B1`
|
||||
```
|
||||
|
||||
Now _that_ makes sense!
|
||||
现在*这*很有意义!
|
||||
|
||||
### In conclusion
|
||||
### 结论
|
||||
|
||||
Memory-mapped registers are used ubiquitously when interacting with hardware from software, and there are myriad ways to portray those interactions, each of which has a different place on the spectra of ease-of-use and safety. We found that the use of type-level programming to get compile-time checking on memory-mapped register interactions gave us the necessary information to make safer software. That code is available in the `[bounded-registers][15] crate` (Rust package).
|
||||
当与软件中的硬件进行交互时,普遍使用内存映射寄存器,并且有无数种方法来描述这些交互,每种方法在易用性和安全性上都有不同的权衡。我们发现使用类型级编程来获取内存映射寄存器交互的编译时检查为我们提供了制作更安全软件的必要信息。该代码可在 [bounded-registers][15] crate(Rust包)中找到。
|
||||
|
||||
Our team started out right at the edge of the more-safe side of that safety spectrum and then tried to figure out how to move the ease-of-use slider closer to the easy end. From those ambitions, `bounded-registers` was born, and we use it anytime we encounter memory-mapped devices in our adventures at Auxon.
|
||||
我们的团队从安全性较高的一面开始,然后尝试找出如何将易用滑块移近易用端。从这些雄心壮志中,“边界寄存器”就诞生了,我们在 Auxon 冒险中遇到遇到内存映射设备的任何时候都可以使用它。
|
||||
|
||||
* * *
|
||||
|
||||
1. Technically, a read from a register field, by definition, will only give a value within the prescribed bounds, but none of us lives in a pure world, and you never know what's going to happen when external systems come into play. You're at the behest of the Hardware Gods here, so instead of forcing you into a "might panic" situation, it gives you the `Option` to handle a "This Should Never Happen" case.
|
||||
|
||||
2. `get_field` looks a little weird. I'm looking at the `Field::Read` part, specifically. `Field` is a type, and you need an instance of that type to pass to `get_field`. A cleaner API might be something like:
|
||||
|
||||
|
||||
```
|
||||
`regs.rx.get_field::<UartRx::Data::Field>();`
|
||||
```
|
||||
|
||||
But remember that `Field` is a type synonym that has fixed indices for width, offset, etc. To be able to parameterize `get_field` like this, you'd need higher-kinded types.
|
||||
|
||||
|
||||
|
||||
[^1]: 从技术上讲,从定义上看,从寄存器字段读取的值只能在规定的范围内,但是我们当中没有一个人生活在一个纯净的世界中,而且你永远都不知道外部系统发挥作用时会发生什么。你是在这里接受硬件之神的命令,因此与其强迫你进入“可能的恐慌”状态,还不如给你提供处理“这将永远不会发生”的机会。
|
||||
[^2]: `get_field` 看起来有点奇怪。我正在专门查看 `Field::Read` 部分。`Field` 是一种类型,你需要该类型的实例才能传递给 `get_field`。更干净的 API 可能类似于:`regs.rx.get_field::<UartRx::Data::Field>();`但是请记住,`Field` 是类型的同义词,它具有固定的宽度、偏移量等索引。要像这样对 `get_field` 进行参数化,你需要使用更高级的类型。
|
||||
|
||||
* * *
|
||||
|
||||
_This originally appeared on the [Auxon Engineering blog][16] and is edited and republished with permission._
|
||||
此内容最初出现在 [Auxon Engineering 博客] [16]上,并经许可进行编辑和重新发布。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -471,7 +452,7 @@ via: https://opensource.com/article/20/1/c-vs-rust-abstractions
|
||||
|
||||
作者:[Dan Pittman][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
译者:[wxy](https://github.com/wxy)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
Loading…
Reference in New Issue
Block a user