mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-17 06:47:49 +00:00
Rewrite post to use and explain volatiles
Also extends some other explanations.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "Printing to Screen"
|
title = "Printing to Screen"
|
||||||
date = "2015-10-23"
|
date = "2015-10-23"
|
||||||
|
updated = "2016-10-06"
|
||||||
aliases = [
|
aliases = [
|
||||||
"/2015/10/23/printing-to-screen/",
|
"/2015/10/23/printing-to-screen/",
|
||||||
"/rust-os/printing-to-screen.html",
|
"/rust-os/printing-to-screen.html",
|
||||||
@@ -149,7 +150,7 @@ The writer will always write to the last line and shift lines up when a line is
|
|||||||
#![feature(unique)]
|
#![feature(unique)]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Printing to Screen
|
## Printing Characters
|
||||||
Now we can use the `Writer` to modify the buffer's characters. First we create a method to write a single ASCII byte (it doesn't compile yet):
|
Now we can use the `Writer` to modify the buffer's characters. First we create a method to write a single ASCII byte (it doesn't compile yet):
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -165,9 +166,10 @@ impl Writer {
|
|||||||
let row = BUFFER_HEIGHT - 1;
|
let row = BUFFER_HEIGHT - 1;
|
||||||
let col = self.column_position;
|
let col = self.column_position;
|
||||||
|
|
||||||
|
let color_code = self.color_code;
|
||||||
self.buffer().chars[row][col] = ScreenChar {
|
self.buffer().chars[row][col] = ScreenChar {
|
||||||
ascii_character: byte,
|
ascii_character: byte,
|
||||||
color_code: self.color_code,
|
color_code: color_code,
|
||||||
};
|
};
|
||||||
self.column_position += 1;
|
self.column_position += 1;
|
||||||
}
|
}
|
||||||
@@ -194,13 +196,48 @@ The `buffer()` auxiliary method converts the raw pointer in the `buffer` field i
|
|||||||
When we try to compile it, we get the following error:
|
When we try to compile it, we get the following error:
|
||||||
|
|
||||||
```
|
```
|
||||||
error: cannot move out of borrowed content [E0507]
|
error[E0507]: cannot move out of borrowed content
|
||||||
color_code: self.color_code,
|
--> src/vga_buffer.rs:79:34
|
||||||
^~~~
|
|
|
||||||
|
79 | let color_code = self.color_code;
|
||||||
|
| ^^^^ cannot move out of borrowed content
|
||||||
```
|
```
|
||||||
The reason it that Rust _moves_ values by default instead of copying them like other languages. And we cannot move `color_code` out of `self` because we only borrowed `self`. For more information check out the [ownership section] in the Rust book. To fix it, we can implement the [Copy trait] for the `ColorCode` type by adding `#[derive(Clone, Copy)]` to its struct.
|
The reason it that Rust _moves_ values by default instead of copying them like other languages. And we cannot move `color_code` out of `self` because we only borrowed `self`. For more information check out the [ownership section] in the Rust book.
|
||||||
|
|
||||||
[ownership section]: https://doc.rust-lang.org/book/ownership.html
|
[ownership section]: https://doc.rust-lang.org/book/ownership.html
|
||||||
[Copy trait]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
[by reference]: http://rust-lang.github.io/book/ch04-02-references-and-borrowing.html
|
||||||
|
|
||||||
|
To fix it, we can implement the [Copy] trait for the `ColorCode` type. The easiest way to do this is to use the built-in [derive macro]:
|
||||||
|
|
||||||
|
[Copy]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
|
||||||
|
[derive macro]: http://rustbyexample.com/trait/derive.html
|
||||||
|
|
||||||
|
{{< highlight rust "hl_lines=1" >}}
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct ColorCode(u8);
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
We also derive the [Clone] trait, since it's a requirement for `Copy`, and the [Debug] trait, which allows us to print this field for debugging purposes.
|
||||||
|
|
||||||
|
[Clone]: https://doc.rust-lang.org/nightly/core/clone/trait.Clone.html
|
||||||
|
[Debug]: https://doc.rust-lang.org/nightly/core/fmt/trait.Debug.html
|
||||||
|
|
||||||
|
Now our project should compile again.
|
||||||
|
|
||||||
|
However, the [documentation for Copy] says: _“if your type can implement Copy, it should”_. Therefore we also derive Copy for `Color` and `ScreenChar`:
|
||||||
|
|
||||||
|
[documentation for Copy]: https://doc.rust-lang.org/core/marker/trait.Copy.html#when-should-my-type-be-copy
|
||||||
|
|
||||||
|
{{< highlight rust "hl_lines=2 6" >}}
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Color {...}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
struct ScreenChar {...}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
### Try it out!
|
### Try it out!
|
||||||
To write some characters to the screen, you can create a temporary function:
|
To write some characters to the screen, you can create a temporary function:
|
||||||
@@ -220,7 +257,81 @@ It just creates a new Writer that points to the VGA buffer at `0xb8000`. Then it
|
|||||||
|
|
||||||
[byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings
|
[byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings
|
||||||
|
|
||||||
### Printing Strings
|
### Volatile
|
||||||
|
We just saw that our `H` was printed correctly. However, it might not work with future Rust compilers that optimize more aggressively.
|
||||||
|
|
||||||
|
The problem is that we only write to the `Buffer` and never read from it again. The compiler doesn't know about the side effects (some characters appear on the screen), so it might decide that these writes are unnecessary.
|
||||||
|
|
||||||
|
To avoid this erroneous optimization, we need to specify these writes as _volatile_. This tells the compiler that the write has side effects and should not be removed.
|
||||||
|
|
||||||
|
In order to use volatile writes for the VGA buffer, we use the [volatile][volatile crate] library. This _crate_ (this is how libraries are called in the Rust world) provides a `Volatile` wrapper type with `read` and `write` methods. These methods internally use the [read_volatile] and [write_volatile] functions of the standard library and thus guarantee that the reads/writes are not optimized away.
|
||||||
|
|
||||||
|
[volatile crate]: https://docs.rs/volatile
|
||||||
|
[read_volatile]: https://doc.rust-lang.org/nightly/core/ptr/fn.read_volatile.html
|
||||||
|
[write_volatile]: https://doc.rust-lang.org/nightly/core/ptr/fn.write_volatile.html
|
||||||
|
|
||||||
|
We can add a dependency on the `volatile` crate by adding it to the `dependencies` section of our `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# in Cargo.toml
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
volatile = "0.1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `0.1.0` is the [semantic] version number. For more information, see the [Specifying Dependencies] guide of the cargo documentation.
|
||||||
|
|
||||||
|
[semantic]: http://semver.org/
|
||||||
|
[Specifying Dependencies]: http://doc.crates.io/specifying-dependencies.html
|
||||||
|
|
||||||
|
Now we've declared that our project depends on the `volatile` crate and are able to import it in `src/lib.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in src/lib.rs
|
||||||
|
|
||||||
|
extern crate volatile;
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's use this crate to make writes to the VGA buffer volatile. We update our `Buffer` type as follows:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// in src/vga_buffer.rs
|
||||||
|
|
||||||
|
use volatile::Volatile;
|
||||||
|
|
||||||
|
struct Buffer {
|
||||||
|
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Instead of a `ScreenChar`, we're now using a `Volatile<ScreenChar>`. (The `Volatile` type is [generic] and can wrap (almost) any type). This ensures that we can't accidentally write to it through a “normal” write. Instead, we have to use the `write` method now.
|
||||||
|
|
||||||
|
[generic]: https://doc.rust-lang.org/book/generics.html
|
||||||
|
|
||||||
|
This means that we have to update our `Writer::write_byte` method:
|
||||||
|
|
||||||
|
{{< highlight rust "hl_lines=8 11" >}}
|
||||||
|
impl Writer {
|
||||||
|
pub fn write_byte(&mut self, byte: u8) {
|
||||||
|
match byte {
|
||||||
|
b'\n' => self.new_line(),
|
||||||
|
byte => {
|
||||||
|
...
|
||||||
|
|
||||||
|
self.buffer().chars[row][col].write(ScreenChar {
|
||||||
|
ascii_character: byte,
|
||||||
|
color_code: color_code,
|
||||||
|
});
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
{{< / highlight >}}
|
||||||
|
|
||||||
|
Instead of a normal assignment using `=`, we're now using the `write` method. This guarantees that the compiler will never optimize away this write.
|
||||||
|
|
||||||
|
## Printing Strings
|
||||||
|
|
||||||
To print whole strings, we can convert them to bytes and print them one-by-one:
|
To print whole strings, we can convert them to bytes and print them one-by-one:
|
||||||
|
|
||||||
@@ -275,9 +386,12 @@ Right now, we just ignore newlines and characters that don't fit into the line a
|
|||||||
// in `impl Writer`
|
// in `impl Writer`
|
||||||
|
|
||||||
fn new_line(&mut self) {
|
fn new_line(&mut self) {
|
||||||
for row in 0..(BUFFER_HEIGHT-1) {
|
for row in 1..BUFFER_HEIGHT {
|
||||||
|
for col in 0..BUFFER_WIDTH {
|
||||||
let buffer = self.buffer();
|
let buffer = self.buffer();
|
||||||
buffer.chars[row] = buffer.chars[row + 1]
|
let character = buffer.chars[row][col].read();
|
||||||
|
buffer.chars[row - 1][col].write(character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.clear_row(BUFFER_HEIGHT-1);
|
self.clear_row(BUFFER_HEIGHT-1);
|
||||||
self.column_position = 0;
|
self.column_position = 0;
|
||||||
@@ -285,14 +399,7 @@ fn new_line(&mut self) {
|
|||||||
|
|
||||||
fn clear_row(&mut self, row: usize) {/* TODO */}
|
fn clear_row(&mut self, row: usize) {/* TODO */}
|
||||||
```
|
```
|
||||||
We just move each line to the line above. Notice that the range notation (`..`) is exclusive the upper bound. But when we try to compile it, we get an borrow checker error again:
|
We iterate over all screen characters and move each characters one row up. Note that the range notation (`..`) is exclusive the upper bound. We also omit the 0th row (the first range starts at `1`) because it's the row that is shifted off screen.
|
||||||
|
|
||||||
```
|
|
||||||
error: cannot move out of indexed content [E0507]
|
|
||||||
buffer.chars[row] = buffer.chars[row + 1]
|
|
||||||
^~~~~~~~~~~~~~~~~~~~~
|
|
||||||
```
|
|
||||||
It's because of Rust's move semantics again: We try to move out the `ScreenChar` at `row + 1`. If Rust would allow that, the array would become invalid as it would contain some valid and some moved out values. Fortunately, the `ScreenChar` type meets all criteria for the [Copy trait], so we can fix the problem by adding `#[derive(Clone, Copy)]` to `ScreenChar`.
|
|
||||||
|
|
||||||
Now we only need to implement the `clear_row` method to finish the newline code:
|
Now we only need to implement the `clear_row` method to finish the newline code:
|
||||||
|
|
||||||
@@ -303,9 +410,12 @@ fn clear_row(&mut self, row: usize) {
|
|||||||
ascii_character: b' ',
|
ascii_character: b' ',
|
||||||
color_code: self.color_code,
|
color_code: self.color_code,
|
||||||
};
|
};
|
||||||
self.buffer().chars[row] = [blank; BUFFER_WIDTH];
|
for col in 0..BUFFER_WIDTH {
|
||||||
|
self.buffer().chars[row][col].write(blank);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
This method clears a row by overwriting all of its characters with a space character.
|
||||||
|
|
||||||
## Providing an Interface
|
## Providing an Interface
|
||||||
To provide a global writer that can used as an interface from other modules, we can add a `static` writer:
|
To provide a global writer that can used as an interface from other modules, we can add a `static` writer:
|
||||||
|
|||||||
Reference in New Issue
Block a user