Improve wording and add some more QEMU screenshots

This commit is contained in:
Philipp Oppermann
2016-10-30 17:23:17 +01:00
parent cfccffca39
commit 41c87636e0

View File

@@ -253,10 +253,12 @@ pub fn print_something() {
writer.write_byte(b'H'); writer.write_byte(b'H');
} }
``` ```
It just creates a new Writer that points to the VGA buffer at `0xb8000`. Then it writes the byte `b'H'` to it. The `b` prefix creates a [byte character], which represents an ASCII code point. When we call `vga_buffer::print_something` in main, a `H` should be printed in the _lower_ left corner of the screen in light green. It just creates a new Writer that points to the VGA buffer at `0xb8000`. Then it writes the byte `b'H'` to it. The `b` prefix creates a [byte character], which represents an ASCII code point. When we call `vga_buffer::print_something` in main, a `H` should be printed in the _lower_ left corner of the screen in light green:
[byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings [byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings
![QEMU output with a green `H` in the lower left corner](images/vga-H-lower-left.png)
### Volatile ### Volatile
We just saw that our `H` was printed correctly. However, it might not work with future Rust compilers that optimize more aggressively. We just saw that our `H` was printed correctly. However, it might not work with future Rust compilers that optimize more aggressively.
@@ -371,15 +373,16 @@ The `Ok(())` is just a `Ok` Result containing the `()` type. We can drop the `pu
Now we can use Rust's built-in `write!`/`writeln!` formatting macros: Now we can use Rust's built-in `write!`/`writeln!` formatting macros:
```rust {{< highlight rust "hl_lines=2 4 5 6" >}}
// in the `print_something` function // in the `print_something` function
use core::fmt::Write; use core::fmt::Write;
let mut writer = Writer {...}; let mut writer = Writer {...};
writer.write_byte(b'H'); writer.write_byte(b'H');
writer.write_str("ello! "); writer.write_str("ello! ");
write!(writer, "The numbers are {} and {}", 42, 1.0/3.0); write!(writer, "The numbers are {} and {}", 42, 1.0/3.0);
``` {{< / highlight >}}
Now you should see a `Hello! The numbers are 42 and 0.3333333333333333` in strange colors at the bottom of the screen.
Now you should see a `Hello! The numbers are 42 and 0.3333333333333333` at the bottom of the screen.
[core::fmt::Write]: https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html [core::fmt::Write]: https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html
@@ -558,7 +561,7 @@ pub fn clear_screen() {
### Hello World using `println` ### Hello World using `println`
To use `println` in `lib.rs`, we need to import the macros of the VGA buffer module first. Therefore we add a `#[macro_use]` attribute to the module declaration: To use `println` in `lib.rs`, we need to import the macros of the VGA buffer module first. Therefore we add a `#[macro_use]` attribute to the module declaration:
```rust {{< highlight rust "hl_lines=3 9 10" >}}
// in src/lib.rs // in src/lib.rs
#[macro_use] #[macro_use]
@@ -572,24 +575,29 @@ pub extern fn rust_main() {
loop{} loop{}
} }
``` {{< / highlight >}}
Since we imported the macros at crate level, they are available in all modules and thus provide an easy and safe interface to the VGA buffer. Since we imported the macros at crate level, they are available in all modules and thus provide an easy and safe interface to the VGA buffer.
### Deadlock As expected, we now see a _“Hello World!”_ on a cleared screen:
Whenever use locks, we must be careful to not accidentally introduce deadlocks. A [deadlock] occurs when a thread/program waits for a lock that will never be released. Normally, this happens when multiple threads access multiple locks. For example, when thread A holds lock 1 and tries to acquire lock 2 and -- at the same time -- thread B holds lock 2 and tries to acquire lock 1.
![QEMU printing “Hello World!” on a cleared screen](images/vga-hello-world.png)
### Deadlocks
Whenever we use locks, we must be careful to not accidentally introduce _deadlocks_. A [deadlock] occurs when a thread/program waits for a lock that will never be released. Normally, this happens when multiple threads access multiple locks. For example, when thread A holds lock 1 and tries to acquire lock 2 and -- at the same time -- thread B holds lock 2 and tries to acquire lock 1.
[deadlock]: https://en.wikipedia.org/wiki/Deadlock [deadlock]: https://en.wikipedia.org/wiki/Deadlock
However, a deadlock can also occur when a thread tries to acquire the same lock twice. Thus, we can trigger a deadlock in our VGA driver: However, a deadlock can also occur when a thread tries to acquire the same lock twice. This way we can trigger a deadlock in our VGA driver:
```rust ```rust
// in rust_main in src/lib.rs // in rust_main in src/lib.rs
println!("{}", { println!("inner"); "outer" }); println!("{}", { println!("inner"); "outer" });
``` ```
The first argument is new block that resolves to the string _“outer”_ (a block always returns the result of the last expression). But before returning “outer”, the block tries to print the string _“inner”_. The argument passed to `println` is new block that resolves to the string _“outer”_ (a block always returns the result of the last expression). But before returning “outer”, the block tries to print the string _“inner”_.
When we try it, we see that neither of the strings are printed. To understand what's happening, we take a look at our `print` macro again: When we try this code in QEMU, we see that neither of the strings are printed. To understand what's happening, we take a look at our `print` macro again:
```rust ```rust
macro_rules! print { macro_rules! print {
@@ -600,10 +608,10 @@ macro_rules! print {
}); });
} }
``` ```
So we _first_ lock the `WRITER` and then we evaluate the arguments using `format_args`. The problem is that the first argument in our code example contains another `println`, which tries to lock the `WRITER` again. So now the inner `println` waits for the outer `println` and vice versa. Thus, a deadlock occurs and the CPU spins endlessly. So we _first_ lock the `WRITER` and then we evaluate the arguments using `format_args`. The problem is that the argument in our code example contains another `println`, which tries to lock the `WRITER` again. So now the inner `println` waits for the outer `println` and vice versa. Thus, a deadlock occurs and the CPU spins endlessly.
### Fixing the Deadlock ### Fixing the Deadlock
In order to fix the deadlock, we need to evaluate the arguments _before_ locking the `WRITER`. We can do so by using the same approach as the standard library: In order to fix the deadlock, we need to evaluate the arguments _before_ locking the `WRITER`. We can do so by moving the locking and printing logic into a new `print` function (like it's done in the standard library):
```rust ```rust
// in src/vga_buffer.rs // in src/vga_buffer.rs
@@ -619,12 +627,14 @@ pub fn print(args: fmt::Arguments) {
WRITER.lock().write_fmt(args).unwrap(); WRITER.lock().write_fmt(args).unwrap();
} }
``` ```
Now the macro evaluates the arguments (through `format_args!`) and passes it to the new `print` function. The function then locks the `WRITER` and prints the formatting arguments using `write_fmt`. Now the macro only evaluates the arguments (through `format_args!`) and passes them to the new `print` function. The `print` function then locks the `WRITER` and prints the formatting arguments using `write_fmt`. So now the arguments are evaluated before locking the `WRITER`.
So the macro evaluates the arguments before locking the `WRITER` now. Thus, we fixed the deadlock problem: Thus, we fixed the deadlock:
![QEMU printing “inner” and then “outer”](images/fixed-println-deadlock.png) ![QEMU printing “inner” and then “outer”](images/fixed-println-deadlock.png)
We see that both “inner” and “outer” are printed.
## What's next? ## What's next?
In the next posts we will map the kernel pages correctly so that accessing `0x0` or writing to `.rodata` is not possible anymore. To obtain the loaded kernel sections we will read the Multiboot information structure. Then we will create a paging module and use it to switch to a new page table where the kernel sections are mapped correctly. In the next posts we will map the kernel pages correctly so that accessing `0x0` or writing to `.rodata` is not possible anymore. To obtain the loaded kernel sections we will read the Multiboot information structure. Then we will create a paging module and use it to switch to a new page table where the kernel sections are mapped correctly.