mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-23 21:20:42 +08:00
PART 1
This commit is contained in:
parent
ac5b0e57ab
commit
63edd7a8ea
@ -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 vs. Rust: Which to choose for programming hardware abstractions
|
||||
C 还是 Rust:选择哪个用于编程硬件抽象
|
||||
======
|
||||
Using type-level programming in Rust can make hardware abstractions
|
||||
safer.
|
||||
|
||||
> 在 Rust 中使用类型级编程可以使硬件抽象更加安全。
|
||||
|
||||
![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.
|
||||
Rust 是一种日益流行的编程语言,被视为硬件接口的最佳选择。通常会将其与 C 的抽象级别进行比较。本文介绍了 Rust 如何以多种方式处理按位运算,并提供了既安全又易于使用的解决方案。
|
||||
|
||||
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])
|
||||
C | 1972 年 | C 是一种通用编程语言,具有表达式简约、现代的控制流和数据结构,以及丰富的运算符集等特点。(来源:[CS 基础知识] [2])| C 是(一种)命令式语言,旨在以相对简单的方式进行编译,从而提供对内存的低级访问。(来源:[W3schools.in] [3])
|
||||
Rust | 2010 年 | 一种使所有人都能构建可靠、高效的软件的语言(来源:[Rust 网站] [4])| Rust 是一种专注于安全性(尤其是安全并发性)的多范式系统编程语言。(来源:[维基百科] [5])
|
||||
|
||||
### Bitwise operation over register values in C
|
||||
### 在 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:
|
||||
在系统编程领域,你可能经常需要编写硬件驱动程序或直接与内存映射的设备进行交互,而这些交互几乎总是通过硬件提供的内存映射的寄存器来完成的。通常,你通过对某些固定宽度的数字类型进行按位运算来与这些寄存器进行交互。
|
||||
|
||||
例如,假设一个具有三个字段的 8 位寄存器:
|
||||
|
||||
```
|
||||
+----------+------+-----------+---------+
|
||||
@ -34,28 +34,25 @@ For instance, imagine an 8-bit register with three fields:
|
||||
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`(以二进制表示为`0000_0001`)来设置 `Enabled` 字段的位。但是,通常情况下,你也不想干扰寄存器中的现有配置。假设你要在设备上启用中断功能,但也要确保设备保持启用状态。为此,必须将 `Interrupt` 字段的值与 `Enabled` 字段的值结合起来。你可以通过按位操作来做到这一点:
|
||||
|
||||
```
|
||||
`1 | (1 << 1)`
|
||||
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.
|
||||
通过将 1 和 2(左移 `1` 一位得到)进行“或”运算得到二进制值 `0000_0011` 。你可以将其写入寄存器,使其保持启用状态,但也允许中断。
|
||||
|
||||
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:
|
||||
有很多事情要记住,特别是当你要为一个完整的系统处理可能有数百个之多的寄存器时。实际上,你可以使用助记符来执行此操作,助记符可跟踪字段在寄存器中的位置以及字段的宽度(即它的上边界是什么?)
|
||||
|
||||
这是这些助记符之一的示例。它们是 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)
|
||||
#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);
|
||||
@ -65,9 +62,9 @@ fn enable_reg_with_interrupt(reg* u8) {
|
||||
}
|
||||
```
|
||||
|
||||
This is the state of the art. In fact, this is how the bulk of drivers appear in the Linux kernel.
|
||||
这就是现在的做法。实际上,这就是大多数驱动程序出现在 Linux 内核中的方式。
|
||||
|
||||
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
|
||||
|
||||
@ -114,7 +111,7 @@ impl Field {
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
|
||||
```
|
||||
@ -139,7 +136,7 @@ The first rewrite in Rust is nice, but it's not ideal. You have to remember to b
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
|
||||
```
|
||||
@ -164,7 +161,7 @@ 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:
|
||||
Now, when revisiting `Field`'s constructor, you can elide the mask and offset parameters because the type contains that information:
|
||||
|
||||
|
||||
```
|
||||
@ -184,7 +181,7 @@ fn enable_register(&mut reg) {
|
||||
}
|
||||
```
|
||||
|
||||
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**:
|
||||
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`:
|
||||
|
||||
|
||||
```
|
||||
@ -193,13 +190,13 @@ fn enable_register(&mut reg) {
|
||||
}
|
||||
```
|
||||
|
||||
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).
|
||||
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:
|
||||
You can add a `Width` parameter to `Field` and use it to verify that a given value can fit into the field:
|
||||
|
||||
|
||||
```
|
||||
@ -230,11 +227,11 @@ impl<Width: Unsigned, Mask: Unsigned, Offset: Unsigned> Field<Width, Ma
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
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:
|
||||
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:
|
||||
|
||||
|
||||
```
|
||||
@ -278,7 +275,7 @@ fn enable_register(&mut reg) {
|
||||
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.
|
||||
`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?
|
||||
|
||||
@ -311,7 +308,7 @@ register! {
|
||||
}
|
||||
```
|
||||
|
||||
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!
|
||||
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:
|
||||
|
||||
@ -341,7 +338,7 @@ What does it look like to use these definitions for a real device? Will the code
|
||||
|
||||
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**:
|
||||
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`:
|
||||
|
||||
|
||||
```
|
||||
@ -373,7 +370,7 @@ impl DerefMut for Regs {
|
||||
}
|
||||
```
|
||||
|
||||
Once this is in place, using these registers is as simple as **read()** and **modify()**:
|
||||
Once this is in place, using these registers is as simple as `read()` and `modify()`:
|
||||
|
||||
|
||||
```
|
||||
@ -398,7 +395,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
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]
|
||||
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]
|
||||
|
||||
|
||||
```
|
||||
@ -434,9 +431,9 @@ error[E0271]: type mismatch resolving `<typenum::UInt<typenum::UInt<typ
|
||||
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.
|
||||
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:
|
||||
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:
|
||||
|
||||
|
||||
```
|
||||
@ -454,22 +451,22 @@ 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).
|
||||
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.
|
||||
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.
|
||||
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:
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user