mirror of
https://github.com/nkbai/book.git
synced 2026-08-24 20:23:29 +08:00
keep cleaning
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# Global Instances
|
||||
# A First Attempt
|
||||
|
||||
Great! We have all of the superpowers of Rust, but how do we interact with those peripherals? They are just arbitrary memory locations, and dereferencing those would be `unsafe`! Do we need to do something like this every time we want to use a peripheral?
|
||||
## Arbitrary Memory Locations and Rust
|
||||
|
||||
Although Rust is capable of interacting with arbitrary memory locations, dereferencing any pointer is considered an `unsafe` operation. The most direct way to expose reading from or writing to a peripheral would look something like this:
|
||||
|
||||
```rust
|
||||
use core::ptr;
|
||||
@@ -18,11 +20,7 @@ fn write_serial_port_speed(val: u32) {
|
||||
}
|
||||
```
|
||||
|
||||
* Messy
|
||||
* Not very Rusty
|
||||
* Lets try something else...
|
||||
|
||||
This is a little messy, so the first reaction might be to wrap these related things up in to a `struct` to organize them better. Maybe you would come up with something like this:
|
||||
Although this works it is subjectively a little messy, so the first reaction might be to wrap these related things up in to a `struct` to better organize them. A second attempt could come up with something like this:
|
||||
|
||||
```rust
|
||||
use core::ptr;
|
||||
@@ -30,9 +28,12 @@ use core::ptr;
|
||||
struct SerialPort;
|
||||
|
||||
impl SerialPort {
|
||||
// Private Constants (addresses)
|
||||
const SER_PORT_SPEED_REG: *mut u32 = 0x4000_1000 as _;
|
||||
pub const SER_PORT_SPEED_HI: u32 = 0x8000_0000;
|
||||
pub const SER_PORT_SPEED_LO: u32 = 0x0800_0000;
|
||||
|
||||
// Public Constants (enumerated values)
|
||||
pub const SER_PORT_SPEED_8MBPS: u32 = 0x8000_0000;
|
||||
pub const SER_PORT_SPEED_125KBPS: u32 = 0x0200_0000;
|
||||
|
||||
fn new() -> SerialPort {
|
||||
SerialPort
|
||||
@@ -52,9 +53,6 @@ impl SerialPort {
|
||||
}
|
||||
```
|
||||
|
||||
* A little better
|
||||
* This works!
|
||||
|
||||
And this is a little better! We've hidden that random looking memory address, and presented something that feels a little more rusty. We can even use our new interface:
|
||||
|
||||
```rust
|
||||
@@ -67,10 +65,9 @@ fn do_something() {
|
||||
}
|
||||
```
|
||||
|
||||
* Problem is you can make these anywhere
|
||||
* When you can make these anywhere, you have aliased pointers!
|
||||
But the problem with this is that our `SerialPort` struct could be created anywhere. By creating multiple instances if `SerialPort`, we would create aliased mutable pointers, which are typically avoided in Rust.
|
||||
|
||||
But the problem with this is that you can create one of these structs anywhere! Imagine this:
|
||||
Consider the following example:
|
||||
|
||||
```rust
|
||||
fn do_something() {
|
||||
@@ -95,17 +92,6 @@ fn something_else() {
|
||||
}
|
||||
```
|
||||
|
||||
In this case, if we were only looking at the code in `do_something()`, we would think, we are definitely sending our serial data slowly, why isn't that thing working?
|
||||
In this case, if we were only looking at the code in `do_something()`, we would think that we are definitely sending our serial data slowly, and would be confused why our embedded code is not working as expected.
|
||||
|
||||
In this example, it is easy to see. However, once this code is spread out over multiple modules, drivers, developers, and days, it gets easier and easier to make these kinds of mistakes.
|
||||
|
||||
## This smells like mutable global state
|
||||
|
||||
> hardware is basically nothing but mutable global state
|
||||
|
||||
## What should our rules be?
|
||||
|
||||
1. We should be able to share any number of read-only accesses to these peripherals
|
||||
2. If something has read-write access to a peripheral, it should be the only reference
|
||||
|
||||
If you are already familiar with Rust, this should start to sound familiar, as this is what the Borrow Checker already does!
|
||||
In this case, it is easy to see where the error was introduced. However, once this code is spread out over multiple modules, drivers, developers, and days, it gets easier and easier to make these kinds of mistakes.
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
# The Borrow Checker
|
||||
## Mutable Global State
|
||||
|
||||
Which, sounds suspiciously exactly like what the Borrow Checker does already!
|
||||
Unfortunately, hardware is basically nothing but mutable global state, which can feel very frightening for a Rust developer. Hardware exists independently from the structures of the code we write, and can be modified at any time by the real world.
|
||||
|
||||
## What should our rules be?
|
||||
|
||||
How can we reliably interact with these peripherals?
|
||||
|
||||
1. Always use `volatile` methods to read or write to peripheral memory, as it can change at any time
|
||||
2. In software, ee should be able to share any number of read-only accesses to these peripherals
|
||||
3. If some software should have read-write access to a peripheral, it should hold the only reference to that peripheral
|
||||
|
||||
## The Borrow Checker
|
||||
|
||||
The last two of these rules sound suspiciously exactly like what the Borrow Checker does already!
|
||||
|
||||
Imagine if we could pass around ownership of these peripherals, or offer immutable or mutable references to them?
|
||||
|
||||
Well, we can, but for the Borrow Checker, we need to have exactly one instance of each peripheral, so Rust can handle this correctly. Well, luckliy in the hardware, there is only one instance of this specific serial port, but how can we expose that in code?
|
||||
Well, we can, but for the Borrow Checker, we need to have exactly one instance of each peripheral, so Rust can handle this correctly. Well, luckliy in the hardware, there is only one instance of any given peripheral, but how can we expose that in the structure of our code?
|
||||
@@ -1,11 +1,10 @@
|
||||
# Singletons
|
||||
|
||||
> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object.
|
||||
>
|
||||
> *Wikipedia: [Singleton Pattern]*
|
||||
|
||||
https://en.wikipedia.org/wiki/Singleton_pattern
|
||||
|
||||
* Old concept
|
||||
* But how in Rust?
|
||||
[Singleton Pattern]: https://en.wikipedia.org/wiki/Singleton_pattern
|
||||
|
||||
|
||||
## But why can't we just use global variable(s)?
|
||||
|
||||
Reference in New Issue
Block a user