mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-02-03 23:40:14 +08:00
PRF
This commit is contained in:
parent
2f6b53dc8c
commit
d17cd544d3
@ -7,25 +7,25 @@
|
||||
[#]: via: (https://opensource.com/article/20/1/c-vs-rust-abstractions)
|
||||
[#]: author: (Dan Pittman https://opensource.com/users/dan-pittman)
|
||||
|
||||
C 还是 Rust:选择哪个用于编程硬件抽象
|
||||
C 还是 Rust:选择哪个用于硬件抽象编程
|
||||
======
|
||||
|
||||
> 在 Rust 中使用类型级编程可以使硬件抽象更加安全。
|
||||
|
||||
![Tools illustration][1]
|
||||
|
||||
Rust 是一种日益流行的编程语言,被视为硬件接口的最佳选择。通常会将其与 C 的抽象级别进行比较。本文介绍了 Rust 如何以多种方式处理按位运算,并提供了既安全又易于使用的解决方案。
|
||||
Rust 是一种日益流行的编程语言,被视为硬件接口的最佳选择。通常会将其与 C 的抽象级别相比较。本文介绍了 Rust 如何通过多种方式处理按位运算,并提供了既安全又易于使用的解决方案。
|
||||
|
||||
语言 | 源自 | 官方说明 | 总览
|
||||
语言 | 诞生于 | 官方描述 | 总览
|
||||
---|---|---|---
|
||||
C | 1972 年 | C 是一种通用编程语言,具有表达式简约、现代的控制流和数据结构,以及丰富的运算符集等特点。(来源:[CS 基础知识] [2])| C 是(一种)命令式语言,旨在以相对简单的方式进行编译,从而提供对内存的低级访问。(来源:[W3schools.in] [3])
|
||||
Rust | 2010 年 | 一种使所有人都能构建可靠、高效的软件的语言(来源:[Rust 网站] [4])| Rust 是一种专注于安全性(尤其是安全并发性)的多范式系统编程语言。(来源:[维基百科] [5])
|
||||
Rust | 2010 年 | 一种赋予所有人构建可靠、高效的软件的能力的语言(来源:[Rust 网站] [4])| Rust 是一种专注于安全性(尤其是安全并发性)的多范式系统编程语言。(来源:[维基百科] [5])
|
||||
|
||||
### 在 C 语言中对寄存器值进行按位运算
|
||||
|
||||
在系统编程领域,你可能经常需要编写硬件驱动程序或直接与内存映射的设备进行交互,而这些交互几乎总是通过硬件提供的内存映射的寄存器来完成的。通常,你通过对某些固定宽度的数字类型进行按位运算来与这些寄存器进行交互。
|
||||
在系统编程领域,你可能经常需要编写硬件驱动程序或直接与内存映射设备进行交互,而这些交互几乎总是通过硬件提供的内存映射寄存器来完成的。通常,你通过对某些固定宽度的数字类型进行按位运算来与这些寄存器进行交互。
|
||||
|
||||
例如,假设一个具有三个字段的 8 位寄存器:
|
||||
例如,假设一个 8 位寄存器具有三个字段:
|
||||
|
||||
```
|
||||
+----------+------+-----------+---------+
|
||||
@ -34,17 +34,17 @@ Rust | 2010 年 | 一种使所有人都能构建可靠、高效的软件的语
|
||||
5-7 2-4 1 0
|
||||
```
|
||||
|
||||
字段名称下方的数字规定了该字段在寄存器中使用的位。要启用该寄存器,你将写入值 `1`(以二进制表示为`0000_0001`)来设置 `Enabled` 字段的位。但是,通常情况下,你也不想干扰寄存器中的现有配置。假设你要在设备上启用中断功能,但也要确保设备保持启用状态。为此,必须将 `Interrupt` 字段的值与 `Enabled` 字段的值结合起来。你可以通过按位操作来做到这一点:
|
||||
字段名称下方的数字规定了该字段在寄存器中使用的位。要启用该寄存器,你将写入值 `1`(以二进制表示为 `0000_0001`)来设置 `Enabled` 字段的位。但是,通常情况下,你也不想干扰寄存器中的现有配置。假设你要在设备上启用中断功能,但也要确保设备保持启用状态。为此,必须将 `Interrupt` 字段的值与 `Enabled` 字段的值结合起来。你可以通过按位操作来做到这一点:
|
||||
|
||||
```
|
||||
1 | (1 << 1)
|
||||
```
|
||||
|
||||
通过将 1 和 2(左移 `1` 一位得到)进行“或”运算得到二进制值 `0000_0011` 。你可以将其写入寄存器,使其保持启用状态,但也允许中断。
|
||||
通过将 1 和 2(`1` 左移一位得到)进行“或”(`|`)运算得到二进制值 `0000_0011` 。你可以将其写入寄存器,使其保持启用状态,但也启用中断功能。
|
||||
|
||||
有很多事情要记住,特别是当你要为一个完整的系统处理可能有数百个之多的寄存器时。实际上,你可以使用助记符来执行此操作,助记符可跟踪字段在寄存器中的位置以及字段的宽度(即它的上边界是什么?)
|
||||
你的头脑中要记住很多事情,特别是当你要在一个完整的系统上和可能有数百个之多的寄存器打交道时。在实践上,你可以使用助记符来执行此操作,助记符可跟踪字段在寄存器中的位置以及字段的宽度(即它的上边界是什么)
|
||||
|
||||
这是这些助记符之一的示例。它们是 C 语言的宏,用右侧的代码替换它们的出现的地方。这是上面列出的寄存器的简写。`&` 的左侧是该字段的位置,而右侧则限制该字段的位:
|
||||
下面是这些助记符之一的示例。它们是 C 语言的宏,用右侧的代码替换它们的出现的地方。这是上面列出的寄存器的简写。`&` 的左侧是该字段的起始位置,而右侧则限制该字段所占的位:
|
||||
|
||||
```
|
||||
#define REG_ENABLED_FIELD(x) (x << 0) & 1
|
||||
@ -52,7 +52,7 @@ Rust | 2010 年 | 一种使所有人都能构建可靠、高效的软件的语
|
||||
#define REG_KIND_FIELD(x) (x << 2) & (7 << 2)
|
||||
```
|
||||
|
||||
然后,你将使用这些通过类似以下方式来抽象化寄存器值的操作:
|
||||
然后,你可以使用这些来抽象化寄存器值的操作,如下所示:
|
||||
|
||||
```
|
||||
void set_reg_val(reg* u8, val u8);
|
||||
@ -62,7 +62,7 @@ fn enable_reg_with_interrupt(reg* u8) {
|
||||
}
|
||||
```
|
||||
|
||||
这就是现在的做法。实际上,这就是大多数驱动程序出现在 Linux 内核中的方式。
|
||||
这就是现在的做法。实际上,这就是大多数驱动程序在 Linux 内核中的使用方式。
|
||||
|
||||
有没有更好的办法?如果能够基于对现代编程语言研究得出新的类型系统,就可能能够获得安全性和可表达性的好处。也就是说,如何使用更丰富、更具表现力的类型系统来使此过程更安全、更持久?
|
||||
|
||||
@ -77,7 +77,7 @@ fn enable_reg_with_interrupt(reg* u8) {
|
||||
5-7 2-4 1 0
|
||||
```
|
||||
|
||||
你可能想如何用 Rust 类型来表示它?
|
||||
你想如何用 Rust 类型来表示它呢?
|
||||
|
||||
你将以类似的方式开始,为每个字段的*偏移*定义常量(即,距最低有效位有多远)及其掩码。*掩码*是一个值,其二进制表示形式可用于更新或读取寄存器内部的字段:
|
||||
|
||||
@ -92,7 +92,7 @@ const KIND_MASK: u8 = 7 << 2;
|
||||
const KIND_OFFSET: u8 = 2;
|
||||
```
|
||||
|
||||
接下来,你将声明一个 `Field` 类型,并进行操作以将给定值转换为与其位置相关的值以供在寄存器内使用:
|
||||
接下来,你将声明一个 `Field` 类型并进行操作,将给定值转换为与其位置相关的值,以供在寄存器内使用:
|
||||
|
||||
```
|
||||
struct Field {
|
||||
@ -124,15 +124,15 @@ fn enable_register(&mut reg) {
|
||||
}
|
||||
```
|
||||
|
||||
使用 Rust,你可以使用数据结构来表示字段,将它们附加到特定的寄存器,并在与硬件交互时提供简洁明了的人机工程学。这个例子使用了 Rust 提供的最基本的功能。无论如何,添加的结构都会减轻上述 C 示例中的某些密度。现在,字段是个已命名的事物,而不是从模糊的按位运算符派生而来的数字,并且寄存器是具有状态的类型 —— 这在硬件上多了一层抽象。
|
||||
使用 Rust,你可以使用数据结构来表示字段,将它们附加到特定的寄存器,并在与硬件交互时提供简洁明了的工效。这个例子使用了 Rust 提供的最基本的功能。无论如何,添加的结构都会减轻上述 C 示例中的某些晦涩的地方。现在,字段是个带有名字的事物,而不是从模糊的按位运算符派生而来的数字,并且寄存器是具有状态的类型 —— 这在硬件上多了一层抽象。
|
||||
|
||||
### 一个易用的 Rust 实现
|
||||
|
||||
用 Rust 重写的第一个版本很好,但是并不理想。你必须记住要带上掩码和偏移量,并且要手工进行临时计算,这容易出错。人类不擅长精确且重复的任务 —— 我们往往会感到疲劳或失去专注力,这会导致错误。一次一个寄存器地手动记录掩码和偏移量几乎可以肯定会很糟糕。这是最好留给机器的任务。
|
||||
用 Rust 重写的第一个版本很好,但是并不理想。你必须记住要带上掩码和偏移量,并且要手工进行临时计算,这容易出错。人类不擅长精确且重复的任务 —— 我们往往会感到疲劳或失去专注力,这会导致错误。一次一个寄存器地手动记录掩码和偏移量几乎可以肯定会以糟糕的结局而告终。这是最好留给机器的任务。
|
||||
|
||||
其次,从结构上进行思考:如果有一种方法可以让字段的类型携带掩码和偏移信息呢?如果你要在访问硬件寄存器并与之交互的实现过程中就能发现错误,而不是在运行时才发现,该怎么办?也许你可以依靠一种通常用于在编译时解决问题的策略,例如类型。
|
||||
其次,从结构上进行思考:如果有一种方法可以让字段的类型携带掩码和偏移信息呢?如果可以在编译时就发现所实现的硬件寄存器的访问和交互中存在错误,而不是在运行时才发现,该怎么办?也许你可以依靠一种常用的策略在编译时解决问题,例如类型。
|
||||
|
||||
你可以使用 [typenum][6] 来修改前面的示例,该库在类型级别提供数字和算术。在这里,你将使用掩码和偏移量对 `Field` 类型进行参数化,使其可用于任何 `Field` 实例,而不必在调用站点中将其包括在内:
|
||||
你可以使用 [typenum][6] 来修改前面的示例,该库在类型级别提供数字和算术。在这里,你将使用掩码和偏移量对 `Field` 类型进行参数化,使其可用于任何 `Field` 实例,而无需将其包括在调用处:
|
||||
|
||||
```
|
||||
#[macro_use]
|
||||
@ -175,7 +175,7 @@ fn enable_register(&mut reg) {
|
||||
}
|
||||
```
|
||||
|
||||
看起来不错,但是……如果你对给定的值是否*适合*某个字段犯了错误,会发生什么?考虑一个简单的输入错误,你在其中放置了 `10` 而不是 `1`:
|
||||
看起来不错,但是……如果你在给定的值是否*适合*字段方面犯了错误,会发生什么?考虑一个简单的输入错误,你在其中放置了 `10` 而不是 `1`:
|
||||
|
||||
```
|
||||
fn enable_register(&mut reg) {
|
||||
@ -219,11 +219,11 @@ impl<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> Field<Width, Mask, Offse
|
||||
}
|
||||
```
|
||||
|
||||
现在,只有给定值适合时,您才能构造一个 `Field`!否则,你将得到 `None` 信号,该信号指示发生了错误,而不是放弃该值的高位并静默写入意外的值。
|
||||
现在,只有给定值适合时,你才能构造一个 `Field`!否则,你将得到 `None` 信号,该信号指示发生了错误,而不是放弃该值的高位并静默写入意外的值。
|
||||
|
||||
但是请注意,这将在运行时引发错误。但是,我们知道我们想事先写入的值,还记得吗?鉴于此,我们可以教编译器完全拒绝具有无效字段值的程序 —— 我们不必等到运行它!
|
||||
但是请注意,这将在运行时环境中引发错误。但是,我们知道我们想事先写入的值,还记得吗?鉴于此,我们可以教编译器完全拒绝具有无效字段值的程序 —— 我们不必等到运行它!
|
||||
|
||||
这次,您将向 `new` 的新实现 `new_checked` 中添加一个特征绑定(`where` 子句),该函数要求输入值小于或等于给定字段用 ``Width` 所控制的最大可能值:
|
||||
这次,你将向 `new` 的新实现 `new_checked` 中添加一个特征绑定(`where` 子句),该函数要求输入值小于或等于给定字段用 `Width` 所能控制的最大可能值:
|
||||
|
||||
```
|
||||
struct Field<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> {
|
||||
@ -252,7 +252,7 @@ impl<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> Field<Width, Mask, Offse
|
||||
}
|
||||
```
|
||||
|
||||
只有拥有此属性的数字才具有此特征的实现,因此,如果使用不适合的数字,它将无法编译。让我们看一看!
|
||||
只有此属性保存的数字才具有此特征的实现,因此,如果使用不适合的数字,它将无法编译。让我们看一看!
|
||||
|
||||
```
|
||||
fn enable_register(&mut reg) {
|
||||
@ -265,9 +265,9 @@ fn enable_register(&mut reg) {
|
||||
found type `typenum::B1`
|
||||
```
|
||||
|
||||
`new_checked` 将无法生成一个程序,因为该字段的值错误地过高。你的输入错误不会在运行时爆炸,因为你永远无法获得一个可以运行的工件。
|
||||
`new_checked` 将无法生成一个程序,因为该字段的值错误地过高。你的输入错误不会在运行时环境中爆炸,因为你永远无法获得一个可以运行的工件。
|
||||
|
||||
就使内存映射的硬件进行交互的安全性而言,你已经接近 Peak Rust。但是,你在 C 的第一个示例中所写的内容比最终得到的一锅粥类型参数更简洁。当你谈论潜在的数百甚至数千个寄存器时,这样做是否容易处理?
|
||||
就使内存映射的硬件进行交互的安全性而言,你已经接近 Rust 的极致。但是,你在 C 的第一个示例中所写的内容比最终得到的一锅粥类型参数更简洁。当你谈论潜在的数百甚至数千个寄存器时,这样做是否容易处理?
|
||||
|
||||
### Rust 恰到好处:既安全又方便使用
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user