mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-03-21 02:10:11 +08:00
Merge remote-tracking branch 'LCTT/master'
This commit is contained in:
commit
2d60f218f8
@ -0,0 +1,477 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (wxy)
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-11825-1.html)
|
||||
[#]: 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)
|
||||
|
||||
C 还是 Rust:选择哪个用于硬件抽象编程
|
||||
======
|
||||
|
||||
> 在 Rust 中使用类型级编程可以使硬件抽象更加安全。
|
||||
|
||||

|
||||
|
||||
Rust 是一种日益流行的编程语言,被视为硬件接口的最佳选择。通常会将其与 C 的抽象级别相比较。本文介绍了 Rust 如何通过多种方式处理按位运算,并提供了既安全又易于使用的解决方案。
|
||||
|
||||
语言 | 诞生于 | 官方描述 | 总览
|
||||
---|---|---|---
|
||||
C | 1972 年 | C 是一种通用编程语言,具有表达式简约、现代的控制流和数据结构,以及丰富的运算符集等特点。(来源:[CS 基础知识] [2])| C 是(一种)命令式语言,旨在以相对简单的方式进行编译,从而提供对内存的低级访问。(来源:[W3schools.in] [3])
|
||||
Rust | 2010 年 | 一种赋予所有人构建可靠、高效的软件的能力的语言(来源:[Rust 网站] [4])| Rust 是一种专注于安全性(尤其是安全并发性)的多范式系统编程语言。(来源:[维基百科] [5])
|
||||
|
||||
### 在 C 语言中对寄存器值进行按位运算
|
||||
|
||||
在系统编程领域,你可能经常需要编写硬件驱动程序或直接与内存映射设备进行交互,而这些交互几乎总是通过硬件提供的内存映射寄存器来完成的。通常,你通过对某些固定宽度的数字类型进行按位运算来与这些寄存器进行交互。
|
||||
|
||||
例如,假设一个 8 位寄存器具有三个字段:
|
||||
|
||||
```
|
||||
+----------+------+-----------+---------+
|
||||
| (unused) | Kind | Interrupt | Enabled |
|
||||
+----------+------+-----------+---------+
|
||||
5-7 2-4 1 0
|
||||
```
|
||||
|
||||
字段名称下方的数字规定了该字段在寄存器中使用的位。要启用该寄存器,你将写入值 `1`(以二进制表示为 `0000_0001`)来设置 `Enabled` 字段的位。但是,通常情况下,你也不想干扰寄存器中的现有配置。假设你要在设备上启用中断功能,但也要确保设备保持启用状态。为此,必须将 `Interrupt` 字段的值与 `Enabled` 字段的值结合起来。你可以通过按位操作来做到这一点:
|
||||
|
||||
```
|
||||
1 | (1 << 1)
|
||||
```
|
||||
|
||||
通过将 1 和 2(`1` 左移一位得到)进行“或”(`|`)运算得到二进制值 `0000_0011` 。你可以将其写入寄存器,使其保持启用状态,但也启用中断功能。
|
||||
|
||||
你的头脑中要记住很多事情,特别是当你要在一个完整的系统上和可能有数百个之多的寄存器打交道时。在实践上,你可以使用助记符来执行此操作,助记符可跟踪字段在寄存器中的位置以及字段的宽度(即它的上边界是什么)
|
||||
|
||||
下面是这些助记符之一的示例。它们是 C 语言的宏,用右侧的代码替换它们的出现的地方。这是上面列出的寄存器的简写。`&` 的左侧是该字段的起始位置,而右侧则限制该字段所占的位:
|
||||
|
||||
```
|
||||
#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)
|
||||
```
|
||||
|
||||
然后,你可以使用这些来抽象化寄存器值的操作,如下所示:
|
||||
|
||||
```
|
||||
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));
|
||||
}
|
||||
```
|
||||
|
||||
这就是现在的做法。实际上,这就是大多数驱动程序在 Linux 内核中的使用方式。
|
||||
|
||||
有没有更好的办法?如果能够基于对现代编程语言研究得出新的类型系统,就可能能够获得安全性和可表达性的好处。也就是说,如何使用更丰富、更具表现力的类型系统来使此过程更安全、更持久?
|
||||
|
||||
### 在 Rust 语言中对寄存器值进行按位运算
|
||||
|
||||
继续用上面的寄存器作为例子:
|
||||
|
||||
```
|
||||
+----------+------+-----------+---------+
|
||||
| (unused) | Kind | Interrupt | Enabled |
|
||||
+----------+------+-----------+---------+
|
||||
5-7 2-4 1 0
|
||||
```
|
||||
|
||||
你想如何用 Rust 类型来表示它呢?
|
||||
|
||||
你将以类似的方式开始,为每个字段的*偏移*定义常量(即,距最低有效位有多远)及其掩码。*掩码*是一个值,其二进制表示形式可用于更新或读取寄存器内部的字段:
|
||||
|
||||
```
|
||||
const ENABLED_MASK: u8 = 1;
|
||||
const ENABLED_OFFSET: u8 = 0;
|
||||
|
||||
const INTERRUPT_MASK: u8 = 2;
|
||||
const INTERRUPT_OFFSET: u8 = 1;
|
||||
|
||||
const KIND_MASK: u8 = 7 << 2;
|
||||
const KIND_OFFSET: u8 = 2;
|
||||
```
|
||||
|
||||
接下来,你将声明一个 `Field` 类型并进行操作,将给定值转换为与其位置相关的值,以供在寄存器内使用:
|
||||
|
||||
```
|
||||
struct Field {
|
||||
value: u8,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
fn new(mask: u8, offset: u8, val: u8) -> Self {
|
||||
Field {
|
||||
value: (val << offset) & mask,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后,你将使用一个 `Register` 类型,该类型会封装一个与你的寄存器宽度匹配的数字类型。 `Register` 具有 `update` 函数,可使用给定字段来更新寄存器:
|
||||
|
||||
```
|
||||
struct Register(u8);
|
||||
|
||||
impl Register {
|
||||
fn update(&mut self, val: Field) {
|
||||
self.0 = self.0 | field.value;
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(Field::new(ENABLED_MASK, ENABLED_OFFSET, 1));
|
||||
}
|
||||
```
|
||||
|
||||
使用 Rust,你可以使用数据结构来表示字段,将它们与特定的寄存器联系起来,并在与硬件交互时提供简洁明了的工效。这个例子使用了 Rust 提供的最基本的功能。无论如何,添加的结构都会减轻上述 C 示例中的某些晦涩的地方。现在,字段是个带有名字的事物,而不是从模糊的按位运算符派生而来的数字,并且寄存器是具有状态的类型 —— 这在硬件上多了一层抽象。
|
||||
|
||||
### 一个易用的 Rust 实现
|
||||
|
||||
用 Rust 重写的第一个版本很好,但是并不理想。你必须记住要带上掩码和偏移量,并且要手工进行临时计算,这容易出错。人类不擅长精确且重复的任务 —— 我们往往会感到疲劳或失去专注力,这会导致错误。一次一个寄存器地手动记录掩码和偏移量几乎可以肯定会以糟糕的结局而告终。这是最好留给机器的任务。
|
||||
|
||||
其次,从结构上进行思考:如果有一种方法可以让字段的类型携带掩码和偏移信息呢?如果可以在编译时就发现硬件寄存器的访问和交互的实现代码中存在错误,而不是在运行时才发现,该怎么办?也许你可以依靠一种在编译时解决问题的常用策略,例如类型。
|
||||
|
||||
你可以使用 [typenum][6] 来修改前面的示例,该库在类型级别提供数字和算术。在这里,你将使用掩码和偏移量对 `Field` 类型进行参数化,使其可用于任何 `Field` 实例,而无需将其包括在调用处:
|
||||
|
||||
```
|
||||
#[macro_use]
|
||||
extern crate typenum;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use typenum::*;
|
||||
|
||||
// Now we'll add Mask and Offset to Field's type
|
||||
struct Field<Mask: Unsigned, Offset: Unsigned> {
|
||||
value: u8,
|
||||
_mask: PhantomData<Mask>,
|
||||
_offset: PhantomData<Offset>,
|
||||
}
|
||||
|
||||
// We can use type aliases to give meaningful names to
|
||||
// our fields (and not have to remember their offsets and masks).
|
||||
type RegEnabled = Field<U1, U0>;
|
||||
type RegInterrupt = Field<U2, U1>;
|
||||
type RegKind = Field<op!(U7 << U2), U2>;
|
||||
```
|
||||
|
||||
现在,当重新访问 `Field` 的构造函数时,你可以忽略掩码和偏移量参数,因为类型中包含该信息:
|
||||
|
||||
```
|
||||
impl<Mask: Unsigned, Offset: Unsigned> Field<Mask, Offset> {
|
||||
fn new(val: u8) -> Self {
|
||||
Field {
|
||||
value: (val << Offset::U8) & Mask::U8,
|
||||
_mask: PhantomData,
|
||||
_offset: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And to enable our register...
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(RegEnabled::new(1));
|
||||
}
|
||||
```
|
||||
|
||||
看起来不错,但是……如果你在给定的值是否*适合*该字段方面犯了错误,会发生什么?考虑一个简单的输入错误,你在其中放置了 `10` 而不是 `1`:
|
||||
|
||||
```
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(RegEnabled::new(10));
|
||||
}
|
||||
```
|
||||
|
||||
在上面的代码中,预期结果是什么?好吧,代码会将启用位设置为 0,因为 `10&1 = 0`。那真不幸;最好在尝试写入之前知道你要写入字段的值是否适合该字段。事实上,我认为截掉错误字段值的高位是一种 1*未定义的行为*(哈)。
|
||||
|
||||
### 出于安全考虑使用 Rust
|
||||
|
||||
如何以一般方式检查字段的值是否适合其规定的位置?需要更多类型级别的数字!
|
||||
|
||||
你可以在 `Field` 中添加 `Width` 参数,并使用它来验证给定的值是否适合该字段:
|
||||
|
||||
```
|
||||
struct Field<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> {
|
||||
value: u8,
|
||||
_mask: PhantomData<Mask>,
|
||||
_offset: PhantomData<Offset>,
|
||||
_width: PhantomData<Width>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在,只有给定值适合时,你才能构造一个 `Field` !否则,你将得到 `None` 信号,该信号指示发生了错误,而不是截掉该值的高位并静默写入意外的值。
|
||||
|
||||
但是请注意,这将在运行时环境中引发错误。但是,我们事先知道我们想写入的值,还记得吗?鉴于此,我们可以教编译器完全拒绝具有无效字段值的程序 —— 我们不必等到运行它!
|
||||
|
||||
这次,你将向 `new` 的新实现 `new_checked` 中添加一个特征绑定(`where` 子句),该函数要求输入值小于或等于给定字段用 `Width` 所能容纳的最大可能值:
|
||||
|
||||
```
|
||||
struct Field<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> {
|
||||
value: u8,
|
||||
_mask: PhantomData<Mask>,
|
||||
_offset: PhantomData<Offset>,
|
||||
_width: PhantomData<Width>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
只有拥有此属性的数字才实现此特征,因此,如果使用不适合的数字,它将无法编译。让我们看一看!
|
||||
|
||||
```
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(RegEnabled::new_checked::<U10>());
|
||||
}
|
||||
12 | reg.update(RegEnabled::new_checked::<U10>());
|
||||
| ^^^^^^^^^^^^^^^^ expected struct `typenum::B0`, found struct `typenum::B1`
|
||||
|
|
||||
= note: expected type `typenum::B0`
|
||||
found type `typenum::B1`
|
||||
```
|
||||
|
||||
`new_checked` 将无法生成一个程序,因为该字段的值有错误的高位。你的输入错误不会在运行时环境中才爆炸,因为你永远无法获得一个可以运行的工件。
|
||||
|
||||
就使内存映射的硬件进行交互的安全性而言,你已经接近 Rust 的极致。但是,你在 C 的第一个示例中所写的内容比最终得到的一锅粥的类型参数更简洁。当你谈论潜在可能有数百甚至数千个寄存器时,这样做是否容易处理?
|
||||
|
||||
### 让 Rust 恰到好处:既安全又方便使用
|
||||
|
||||
早些时候,我认为手工计算掩码有问题,但我又做了同样有问题的事情 —— 尽管是在类型级别。虽然使用这种方法很不错,但要达到编写任何代码的地步,则需要大量样板和手动转录(我在这里谈论的是类型的同义词)。
|
||||
|
||||
我们的团队想要像 [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
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
由此,你可以生成寄存器和字段类型,如上例所示,其中索引:`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.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
生成的 API 包含名义上期望的读取和写入的原语,以获取原始寄存器的值,但它也有办法获取单个字段的值、执行集合操作以及确定是否设置了任何(或全部)位集合的方法。你可以阅读[完整生成的 API][9]上的文档。
|
||||
|
||||
### 粗略检查
|
||||
|
||||
将这些定义用于实际设备会是什么样?代码中是否会充斥着类型参数,从而掩盖了视图中的实际逻辑?
|
||||
|
||||
不会!通过使用类型同义词和类型推断,你实际上根本不必考虑程序的类型层面部分。你可以直接与硬件交互,并自动获得与边界相关的保证。
|
||||
|
||||
这是一个 [UART][10] 寄存器块的示例。我会跳过寄存器本身的声明,因为包括在这里就太多了。而是从寄存器“块”开始,然后帮助编译器知道如何从指向该块开头的指针中查找寄存器。我们通过实现 `Deref` 和 `DerefMut` 来做到这一点:
|
||||
|
||||
```
|
||||
#[repr(C)]
|
||||
pub struct UartBlock {
|
||||
rx: UartRX::Register,
|
||||
_padding1: [u32; 15],
|
||||
tx: UartTX::Register,
|
||||
_padding2: [u32; 15],
|
||||
control1: UartControl1::Register,
|
||||
}
|
||||
|
||||
pub struct Regs {
|
||||
addr: usize,
|
||||
}
|
||||
|
||||
impl Deref for Regs {
|
||||
type Target = 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) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
一旦到位,使用这些寄存器就像 `read()` 和 `modify()` 一样简单:
|
||||
|
||||
```
|
||||
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,
|
||||
};
|
||||
|
||||
assert_eq!(regs.rx.read(), 0);
|
||||
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
当我们使用运行时值时,我们使用如前所述的**选项**。这里我使用的是 `unwrap`,但是在一个输入未知的真实程序中,你可能想检查一下从新调用中返回的**某些东西**: [^1] [^2]
|
||||
|
||||
```
|
||||
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());
|
||||
}
|
||||
```
|
||||
|
||||
### 解码失败条件
|
||||
|
||||
根据你的个人痛苦忍耐程度,你可能已经注意到这些错误几乎是无法理解的。看一下我所说的不那么微妙的提醒:
|
||||
|
||||
```
|
||||
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`
|
||||
```
|
||||
|
||||
`expected struct typenum::B0, found struct typenum::B1` 部分是有意义的,但是 ` typenum::UInt<typenum::UInt, typenum::UInt...` 到底是什么呢?好吧,`typenum` 将数字表示为二进制 [cons][13] 单元!像这样的错误使操作变得很困难,尤其是当你将多个这些类型级别的数字限制在狭窄的范围内时,你很难知道它在说哪个数字。当然,除非你一眼就能将巴洛克式二进制表示形式转换为十进制表示形式。
|
||||
|
||||
在第 U100 次试图从这个混乱中破译出某些含义之后,我们的一个队友简直《<ruby>疯了,地狱了,不要再忍受了<rt>Mad As Hell And Wasn't Going To Take It Anymore</rt></ruby>》,并做了一个小工具 `tnfilt`,从这种命名空间的二进制 cons 单元的痛苦中解脱出来。`tnfilt` 将 cons 单元格式的表示法替换为可让人看懂的十进制数字。我们认为其他人也会遇到类似的困难,所以我们分享了 [tnfilt][14]。你可以像这样使用它:
|
||||
|
||||
```
|
||||
$ cargo build 2>&1 | tnfilt
|
||||
```
|
||||
|
||||
它将上面的输出转换为如下所示:
|
||||
|
||||
```
|
||||
error[E0271]: type mismatch resolving `<U20 as typenum::IsLessOrEqual<U10>>::Output == typenum::B1`
|
||||
```
|
||||
|
||||
现在*这*才有意义!
|
||||
|
||||
### 结论
|
||||
|
||||
当在软件与硬件进行交互时,普遍使用内存映射寄存器,并且有无数种方法来描述这些交互,每种方法在易用性和安全性上都有不同的权衡。我们发现使用类型级编程来取得内存映射寄存器交互的编译时检查可以为我们提供制作更安全软件的必要信息。该代码可在 [bounded-registers][15] crate(Rust 包)中找到。
|
||||
|
||||
我们的团队从安全性较高的一面开始,然后尝试找出如何将易用性滑块移近易用端。从这些雄心壮志中,“边界寄存器”就诞生了,我们在 Auxon 公司的冒险中遇到内存映射设备的任何时候都可以使用它。
|
||||
|
||||
* * *
|
||||
|
||||
[^1]: 从技术上讲,从定义上看,从寄存器字段读取的值只能在规定的范围内,但是我们当中没有一个人生活在一个纯净的世界中,而且你永远都不知道外部系统发挥作用时会发生什么。你是在这里接受硬件之神的命令,因此与其强迫你进入“可能的恐慌”状态,还不如给你提供处理“这将永远不会发生”的机会。
|
||||
[^2]: `get_field` 看起来有点奇怪。我正在专门查看 `Field::Read` 部分。`Field` 是一种类型,你需要该类型的实例才能传递给 `get_field`。更干净的 API 可能类似于:`regs.rx.get_field::<UartRx::Data::Field>();` 但是请记住,`Field` 是一种具有固定的宽度、偏移量等索引的类型的同义词。要像这样对 `get_field` 进行参数化,你需要使用更高级的类型。
|
||||
|
||||
* * *
|
||||
|
||||
此内容最初发布在 [Auxon Engineering 博客][16]上,并经许可进行编辑和重新发布。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/1/c-vs-rust-abstractions
|
||||
|
||||
作者:[Dan Pittman][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[wxy](https://github.com/wxy)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [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/
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (laingke)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
@ -474,7 +474,7 @@ via: https://opensource.com/article/20/1/javastream
|
||||
|
||||
作者:[Marty Kalin][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
译者:[laingke](https://github.com/laingke)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -1,509 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (wxy)
|
||||
[#]: 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)
|
||||
|
||||
C vs. Rust: Which to choose for programming hardware abstractions
|
||||
======
|
||||
Using type-level programming in Rust can make hardware abstractions
|
||||
safer.
|
||||
![Tools illustration][1]
|
||||
|
||||
Rust is an increasingly popular programming language positioned to be the best choice for hardware interfaces. It's often compared to C for its level of abstraction. This article explains how Rust can handle bitwise operations in a number of ways and offers a solution that provides both safety and ease of use.
|
||||
|
||||
Language | Origin | Official description | Overview
|
||||
---|---|---|---
|
||||
C | 1972 | C is a general-purpose programming language which features economy of expression, modern control flow and data structures, and a rich set of operators. (Source: [CS Fundamentals][2]) | C is [an] imperative language and designed to compile in a relatively straightforward manner which provides low-level access to the memory. (Source: [W3schools.in][3])
|
||||
Rust | 2010 | A language empowering everyone to build reliable and efficient software (Source: [Rust website][4]) | Rust is a multi-paradigm system programming language focused on safety, especially safe concurrency. (Source: [Wikipedia][5])
|
||||
|
||||
### Bitwise operation over register values in C
|
||||
|
||||
In the world of systems programming, where you may find yourself writing hardware drivers or interacting directly with memory-mapped devices, interaction is almost always done through memory-mapped registers provided by the hardware. You typically interact with these things through bitwise operations on some fixed-width numeric type.
|
||||
|
||||
For instance, imagine an 8-bit register with three fields:
|
||||
|
||||
|
||||
```
|
||||
+----------+------+-----------+---------+
|
||||
| (unused) | Kind | Interrupt | Enabled |
|
||||
+----------+------+-----------+---------+
|
||||
5-7 2-4 1 0
|
||||
```
|
||||
|
||||
The number below the field name prescribes the bits used by that field in the register. To enable this register, you would write the value **1**, represented in binary as **0000_0001**, to set the enabled field's bit. Often, though, you also have an existing configuration in the register that you don't want to disturb. Say you want to enable interrupts on the device but also want to be sure the device remains enabled. To do that, you must combine the Interrupt field's value with the Enabled field's value. You would do that with bitwise operations:
|
||||
|
||||
|
||||
```
|
||||
`1 | (1 << 1)`
|
||||
```
|
||||
|
||||
This gives you the binary value **0000_0011** by **or**-ing 1 with 2, which you get by shifting 1 left by 1. You can write this to your register, leaving it enabled but also enabling interrupts.
|
||||
|
||||
This is a lot to keep in your head, especially when you're dealing with potentially hundreds of registers for a complete system. In practice, you do this with mnemonics which track a field's position in a register and how wide the field is—i.e., _what's its upper bound?_
|
||||
|
||||
Here's an example of one of these mnemonics. They are C macros that replace their occurrences with the code on the right-hand side. This is the shorthand for the register laid out above. The left-hand side of the **&** puts you in position for that field, and the right-hand side limits you to only that field's bits:
|
||||
|
||||
|
||||
```
|
||||
#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)
|
||||
```
|
||||
|
||||
You'd then use these to abstract over the derivation of a register's value with something like:
|
||||
|
||||
|
||||
```
|
||||
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));
|
||||
}
|
||||
```
|
||||
|
||||
This is the state of the art. In fact, this is how the bulk of drivers appear in the Linux kernel.
|
||||
|
||||
Is there a better way? Consider the boon to safety and expressibility if the type system was borne out of research on modern programming languages. That is, what could you do with a richer, more expressive type system to make this process safer and more tenable?
|
||||
|
||||
### Bitwise operation over register values in Rust
|
||||
|
||||
Continuing with the register above as an example:
|
||||
|
||||
|
||||
```
|
||||
+----------+------+-----------+---------+
|
||||
| (unused) | Kind | Interrupt | Enabled |
|
||||
+----------+------+-----------+---------+
|
||||
5-7 2-4 1 0
|
||||
```
|
||||
|
||||
How might you want to express such a thing in Rust types?
|
||||
|
||||
You'll start in a similar way, by defining constants for each field's _offset_—that is, how far it is from the least significant bit—and its mask. A _mask_ is a value whose binary representation can be used to update or read the field from inside the register:
|
||||
|
||||
|
||||
```
|
||||
const ENABLED_MASK: u8 = 1;
|
||||
const ENABLED_OFFSET: u8 = 0;
|
||||
|
||||
const INTERRUPT_MASK: u8 = 2;
|
||||
const INTERRUPT_OFFSET: u8 = 1;
|
||||
|
||||
const KIND_MASK: u8 = 7 << 2;
|
||||
const KIND_OFFSET: u8 = 2;
|
||||
```
|
||||
|
||||
Next, you'll declare a field type and do your operations to convert a given value into its position-relevant value for use inside the register:
|
||||
|
||||
|
||||
```
|
||||
struct Field {
|
||||
value: u8,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
fn new(mask: u8, offset: u8, val: u8) -> Self {
|
||||
Field {
|
||||
value: (val << offset) & mask,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, you'll use a **Register** type, which wraps around a numeric type that matches the width of your register. **Register** has an **update** function that updates the register with the given field:
|
||||
|
||||
|
||||
```
|
||||
struct Register(u8);
|
||||
|
||||
impl Register {
|
||||
fn update(&mut self, val: Field) {
|
||||
self.0 = self.0 | field.value;
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(Field::new(ENABLED_MASK, ENABLED_OFFSET, 1));
|
||||
}
|
||||
```
|
||||
|
||||
With Rust, you can use data structures to represent fields, attach them to specific registers, and provide concise and sensible ergonomics while interacting with the hardware. This example uses the most basic facilities provided by Rust; regardless, the added structure alleviates some of the density from the C example above. Now a field is a named thing, not a number derived from shadowy bitwise operators, and registers are types with state—one extra layer of abstraction over the hardware.
|
||||
|
||||
### A Rust implementation for ease of use
|
||||
|
||||
The first rewrite in Rust is nice, but it's not ideal. You have to remember to bring the mask and offset, and you're calculating them ad hoc, by hand, which is error-prone. Humans aren't great at precise and repetitive tasks—we tend to get tired or lose focus, and this leads to mistakes. Transcribing the masks and offsets by hand, one register at a time, will almost certainly end badly. This is the kind of task best left to a machine.
|
||||
|
||||
Second, thinking more structurally: What if there were a way to have the field's type carry the mask and offset information? What if you could catch mistakes in your implementation for how you access and interact with hardware registers at compile time instead of discovering them at runtime? Perhaps you can lean on one of the strategies commonly used to suss out issues at compile time, like types.
|
||||
|
||||
You can modify the earlier example by using [**typenum**][6], a library that provides numbers and arithmetic at the type level. Here, you'll parameterize the **Field** type with its mask and offset, making it available for any instance of **Field** without having to include it at the call site:
|
||||
|
||||
|
||||
```
|
||||
#[macro_use]
|
||||
extern crate typenum;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use typenum::*;
|
||||
|
||||
// Now we'll add Mask and Offset to Field's type
|
||||
struct Field<Mask: Unsigned, Offset: Unsigned> {
|
||||
value: u8,
|
||||
_mask: PhantomData<Mask>,
|
||||
_offset: PhantomData<Offset>,
|
||||
}
|
||||
|
||||
// We can use type aliases to give meaningful names to
|
||||
// our fields (and not have to remember their offsets and masks).
|
||||
type RegEnabled = Field<U1, U0>;
|
||||
type RegInterrupt = Field<U2, U1>;
|
||||
type RegKind = Field<op!(U7 << U2), U2>;
|
||||
```
|
||||
|
||||
Now, when revisiting **Field**'s constructor, you can elide the mask and offset parameters because the type contains that information:
|
||||
|
||||
|
||||
```
|
||||
impl<Mask: Unsigned, Offset: Unsigned> Field<Mask, Offset> {
|
||||
fn new(val: u8) -> Self {
|
||||
Field {
|
||||
value: (val << Offset::U8) & Mask::U8,
|
||||
_mask: PhantomData,
|
||||
_offset: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And to enable our register...
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(RegEnabled::new(1));
|
||||
}
|
||||
```
|
||||
|
||||
It looks pretty good, but… what happens when you make a mistake regarding whether a given value will _fit_ into a field? Consider a simple typo where you put **10** instead of **1**:
|
||||
|
||||
|
||||
```
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(RegEnabled::new(10));
|
||||
}
|
||||
```
|
||||
|
||||
In the code above, what is the expected outcome? Well, the code will set that enabled bit to 0 because **10 & 1 = 0**. That's unfortunate; it would be nice to know whether a value you're trying to write into a field will fit into the field before attempting a write. As a matter of fact, I'd consider lopping off the high bits of an errant field value _undefined behavior_ (gasps).
|
||||
|
||||
### Using Rust with safety in mind
|
||||
|
||||
How can you check that a field's value fits in its prescribed position in a general way? More type-level numbers!
|
||||
|
||||
You can add a **Width** parameter to **Field** and use it to verify that a given value can fit into the field:
|
||||
|
||||
|
||||
```
|
||||
struct Field<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> {
|
||||
value: u8,
|
||||
_mask: PhantomData<Mask>,
|
||||
_offset: PhantomData<Offset>,
|
||||
_width: PhantomData<Width>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now you can construct a **Field** only if the given value fits! Otherwise, you have **None**, which signals that an error has occurred, rather than lopping off the high bits of the value and silently writing an unexpected value.
|
||||
|
||||
Note, though, this will raise an error at runtime. However, we knew the value we wanted to write beforehand, remember? Given that, we can teach the compiler to reject entirely a program which has an invalid field value—we don’t have to wait until we run it!
|
||||
|
||||
This time, you'll add a _trait bound_ (the **where** clause) to a new realization of new, called **new_checked**, that asks the incoming value to be less than or equal to the maximum possible value a field with the given **Width** can hold:
|
||||
|
||||
|
||||
```
|
||||
struct Field<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> {
|
||||
value: u8,
|
||||
_mask: PhantomData<Mask>,
|
||||
_offset: PhantomData<Offset>,
|
||||
_width: PhantomData<Width>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Only numbers for which this property holds has an implementation of this trait, so if you use a number that does not fit, it will fail to compile. Take a look!
|
||||
|
||||
|
||||
```
|
||||
fn enable_register(&mut reg) {
|
||||
reg.update(RegEnabled::new_checked::<U10>());
|
||||
}
|
||||
12 | reg.update(RegEnabled::new_checked::<U10>());
|
||||
| ^^^^^^^^^^^^^^^^ expected struct `typenum::B0`, found struct `typenum::B1`
|
||||
|
|
||||
= note: expected type `typenum::B0`
|
||||
found type `typenum::B1`
|
||||
```
|
||||
|
||||
**new_checked** will fail to produce a program that has an errant too-high value for a field. Your typo won't blow up at runtime because you could never have gotten an artifact to run.
|
||||
|
||||
You're nearing Peak Rust in terms of how safe you can make memory-mapped hardware interactions. However, what you wrote back in the first example in C was far more succinct than the type parameter salad you ended up with. Is doing such a thing even tractable when you're talking about potentially hundreds or even thousands of registers?
|
||||
|
||||
### Just right with Rust: both safe and accessible
|
||||
|
||||
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):
|
||||
|
||||
|
||||
```
|
||||
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
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
|
||||
```
|
||||
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.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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].
|
||||
|
||||
### 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**:
|
||||
|
||||
|
||||
```
|
||||
#[repr(C)]
|
||||
pub struct UartBlock {
|
||||
rx: UartRX::Register,
|
||||
_padding1: [u32; 15],
|
||||
tx: UartTX::Register,
|
||||
_padding2: [u32; 15],
|
||||
control1: UartControl1::Register,
|
||||
}
|
||||
|
||||
pub struct Regs {
|
||||
addr: usize,
|
||||
}
|
||||
|
||||
impl Deref for Regs {
|
||||
type Target = 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) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once this is in place, using these registers is as simple as **read()** and **modify()**:
|
||||
|
||||
|
||||
```
|
||||
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,
|
||||
};
|
||||
|
||||
assert_eq!(regs.rx.read(), 0);
|
||||
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
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]
|
||||
|
||||
|
||||
```
|
||||
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());
|
||||
}
|
||||
```
|
||||
|
||||
### 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`
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
|
||||
```
|
||||
`$ 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``
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
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.
|
||||
|
||||
* * *
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
_This originally appeared on the [Auxon Engineering blog][16] and is edited and republished with permission._
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/1/c-vs-rust-abstractions
|
||||
|
||||
作者:[Dan Pittman][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者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/
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -1,119 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Wine 5.0 is Released! Here’s How to Install it)
|
||||
[#]: via: (https://itsfoss.com/wine-5-release/)
|
||||
[#]: author: (Ankush Das https://itsfoss.com/author/ankush/)
|
||||
|
||||
Wine 5.0 is Released! Here’s How to Install it
|
||||
======
|
||||
|
||||
_**Brief: A new major release of Wine is here. With Wine 5.0, running Windows applications and games on Linux is further improved.**_
|
||||
|
||||
With some efforts, you can [run Windows applications on Linux][1] using Wine. Wine is a tool that you may try when you must use a software that is available only on Windows. It supports a number of such software.
|
||||
|
||||
A new major release for Wine has landed i.e Wine 5.0, almost after a year of its 4.0 release.
|
||||
|
||||
Wine 5.0 release introduces a couple of major features and a lot of significant changes/improvements. In this article, I’ll highlight what’s new and also mention the installation instructions.
|
||||
|
||||
### What’s New In Wine 5.0?
|
||||
|
||||
![][2]
|
||||
|
||||
The key changes in 5.0 release as mentioned in their [official announcement][3]:
|
||||
|
||||
* Builtin modules in PE format.
|
||||
* Multi-monitor support.
|
||||
* XAudio2 reimplementation.
|
||||
* Vulkan 1.1 support.
|
||||
* Microsoft Installer (MSI) Patch Files are supported.
|
||||
* Performance improvements.
|
||||
|
||||
|
||||
|
||||
So, with Vulkan 1.1 support and multi-monitor support – Wine 5.0 release is a big deal.
|
||||
|
||||
In addition to the key highlights, you can also expect better controller support in the new version considering thousands of changes/improvements involved in the new release.
|
||||
|
||||
It is also worth noting that this release is being dedicated to the memory of **Józef Kucia** (_lead developer of the vkd3d project_)
|
||||
|
||||
They’ve also mentioned this in their [release notes][4]:
|
||||
|
||||
> This release is dedicated to the memory of Józef Kucia, who passed away in August 2019 at the young age of 30. Józef was a major contributor to Wine’s Direct3D implementation, and the lead developer of the vkd3d project. His skills and his kindness are sorely missed by all of us.
|
||||
|
||||
### How to install Wine 5.0 on Ubuntu and Linux Mint
|
||||
|
||||
Note
|
||||
|
||||
_If you have Wine installed before, you should remove it completely to avoid any conflict (as you wish). Also, the WineHQ key repository key was changed recently, you should refer to its_ [_download page_][5] _for additional instructions on that according to your Linux distribution._
|
||||
|
||||
The source for Wine 5.0 is available on its [official website][3]. You can read more about [building wine][6] in order to make it work. Arch-based users should be getting it soon.
|
||||
|
||||
Here’ I’ll show you the steps to install Wine 5.0 on Ubuntu and other Ubuntu-based distributions.
|
||||
|
||||
First, remove existing Wine install with this command:
|
||||
|
||||
```
|
||||
sudo apt remove winehq-stable wine-stable wine1.6
|
||||
```
|
||||
|
||||
Download the official Wine repository key and add it:
|
||||
|
||||
```
|
||||
wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add -
|
||||
```
|
||||
|
||||
_**Now the next step involves adding the repository and for that, you need to [know your Ubuntu version][7] first.**_
|
||||
|
||||
For **Ubuntu 19.10**, add this repository:
|
||||
|
||||
```
|
||||
sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ eoan main'
|
||||
```
|
||||
|
||||
If you are using **Ubuntu 18.04** or **Linux Mint 19.x**, use this command to add the repository:
|
||||
|
||||
```
|
||||
sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ bionic main'
|
||||
```
|
||||
|
||||
For **Ubuntu 16.04 and Linux Mint 18.x series**, you can use this command:
|
||||
|
||||
```
|
||||
sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ xenial main'
|
||||
```
|
||||
|
||||
Now that you have added the correct repository, you can install Wine 5.0 using this command:
|
||||
|
||||
```
|
||||
sudo apt update && sudo apt install --install-recommends winehq-stable
|
||||
```
|
||||
|
||||
**Wrapping Up**
|
||||
|
||||
Have you tried the latest Wine 5.0 release yet? If yes, what improvements do you see in action?
|
||||
|
||||
Let me know your thoughts on the new release in the comments below.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/wine-5-release/
|
||||
|
||||
作者:[Ankush Das][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/ankush/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://itsfoss.com/use-windows-applications-linux/
|
||||
[2]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/01/wine_5.png?ssl=1
|
||||
[3]: https://www.winehq.org/news/2020012101
|
||||
[4]: https://www.winehq.org/announce/5.0
|
||||
[5]: https://wiki.winehq.org/Download
|
||||
[6]: https://wiki.winehq.org/Building_Wine
|
||||
[7]: https://itsfoss.com/how-to-know-ubuntu-unity-version/
|
@ -0,0 +1,119 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Wine 5.0 is Released! Here’s How to Install it)
|
||||
[#]: via: (https://itsfoss.com/wine-5-release/)
|
||||
[#]: author: (Ankush Das https://itsfoss.com/author/ankush/)
|
||||
|
||||
Wine 5.0 发布了!这里是如何安装它的方法
|
||||
======
|
||||
|
||||
_**简介:在这里,Wine 的一个新的主要版本发布。使用 Wine 5.0 ,在 Linux 上运行 Windows 应用程序和游戏得到进一步改进。**_
|
||||
|
||||
因为一些努力,你可以使用 Wine [在 Linux 上运行 Windows 应用程序][1] 。Wine 是一个当你必需使用一个仅在 Windows 上可用的软件时你可以尝试的工具。它支持许多这样的软件。
|
||||
|
||||
Wine 的一个新的主要发布版本已经降临,即 Wine 5.0 ,几乎在它的 4.0 发布一年之后。
|
||||
|
||||
Wine 5.0 发布版本引进了几个主要的特色和很多有重大意义的更改/改进。我将重点介绍新的特色是什么,并且也将提到安装说明。
|
||||
|
||||
### 在 Wine 5.0 中有什么新的特色?
|
||||
|
||||
![][2]
|
||||
|
||||
在 5.0 发布版本中的关键更改,正如在他们的[官方声明][3]所述一样:
|
||||
|
||||
* PE 格式的内置模块。
|
||||
* 多监视器支持。
|
||||
* XAudio2 重新实施。
|
||||
* Vulkan 1.1 支持。
|
||||
* 支持微软安装程序(MSI)补丁文件。
|
||||
* 性能改进。
|
||||
|
||||
|
||||
|
||||
因此,随着 Vulkan 1.1 和多监视器的支持 – Wine 5.0 发布版本是一件大事。
|
||||
|
||||
除了关键强调以外,就在新的版本中包含成千上万的更改/改进而言,你同样可以期待在新的版本中有更好的控制器支持。
|
||||
|
||||
这个发布版本致力于纪念 **Józef Kucia** (_vkd3d 项目的首席开发人员_)也是值得注意的
|
||||
|
||||
他们也已经在[发布说明][4]中提到这一点:
|
||||
|
||||
> 这个发布版本致力于纪念 Józef Kucia ,他在 2019 年 8 月去世,年仅 30 岁。Józef 是 Wine 的 Direct3D 实施的一个主要贡献者,并且是 vkd3d 项目的首席开发人员。我们都非常怀念他的技能和善良。
|
||||
|
||||
### 如何在 Ubuntu 和 Linux Mint 上安装 Wine 5.0
|
||||
|
||||
注意
|
||||
|
||||
_如果你在以前安装过 Wine ,你应该将其完全移除,以避免一些冲突(像你希望的一样)。此外,WineHQ 秘钥存储库最近已被更改,对于额外的操作指南,你可以根据你的 Linux 发行版来参考它的_ [_下载页面_][5]。_
|
||||
|
||||
Wine 5.0 的源码可在它的[官方网站][3]上获得。为了使其工作,你可以阅读更多关于[构建 wine][6]的信息。基于 Arch 的用户应该很快就会得到它。
|
||||
|
||||
在这里,我将向你展示在 Ubuntu 和其它基于 Ubuntu 的发行版上安装 Wine 5.0 的步骤。
|
||||
|
||||
首先,使用这个命令来移除现存的 Wine 安装:
|
||||
|
||||
```
|
||||
sudo apt remove winehq-stable wine-stable wine1.6
|
||||
```
|
||||
|
||||
下载并添加官方 Wine 存储库秘钥:
|
||||
|
||||
```
|
||||
wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add -
|
||||
```
|
||||
|
||||
_**现在,接下来的步骤需要添加存储库,为此, 你需要首先[知道你的 Ubuntu 版本][7]。**_
|
||||
|
||||
对于 **Ubuntu 19.10** ,添加这个存储库:
|
||||
|
||||
```
|
||||
sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ eoan main'
|
||||
```
|
||||
|
||||
如果你正在使用 **Ubuntu 18.04** 或 **Linux Mint 19.x** ,使用这个命令来添加存储库:
|
||||
|
||||
```
|
||||
sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ bionic main'
|
||||
```
|
||||
|
||||
对于 **Ubuntu 16.04 和 Linux Mint 18.x 系列** ,你可以使用这个命令:
|
||||
|
||||
```
|
||||
sudo apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ xenial main'
|
||||
```
|
||||
|
||||
现在,你已经添加了正确的存储库,你可以使用这个命令来安装 Wine 5.0 :
|
||||
|
||||
```
|
||||
sudo apt update && sudo apt install --install-recommends winehq-stable
|
||||
```
|
||||
|
||||
**总结**
|
||||
|
||||
你尝试过最新的 Wine 5.0 发布版本吗?如果是的话,在运行中你看到什么改进?
|
||||
|
||||
在下面的评论区域,让我知道你对新的发布版本的看法。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/wine-5-release/
|
||||
|
||||
作者:[Ankush Das][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[robsean](https://github.com/robsean)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/ankush/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://itsfoss.com/use-windows-applications-linux/
|
||||
[2]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/01/wine_5.png?ssl=1
|
||||
[3]: https://www.winehq.org/news/2020012101
|
||||
[4]: https://www.winehq.org/announce/5.0
|
||||
[5]: https://wiki.winehq.org/Download
|
||||
[6]: https://wiki.winehq.org/Building_Wine
|
||||
[7]: https://itsfoss.com/how-to-know-ubuntu-unity-version/
|
Loading…
Reference in New Issue
Block a user