From 9d34da7c13f6ca3fce1c5fc2453a9a72f48264d8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 15 Sep 2015 15:26:31 +0200 Subject: [PATCH 01/12] WIP --- _drafts/printing-to-screen.md | 109 ++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index ad1784c6..e770df9c 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -14,6 +14,7 @@ Bit(s) | Value 8-11 | Foreground color 12-14 | Background color 15 | Blink + The following colors are available: Number | Color | Number + Bright Bit | Bright Color @@ -26,7 +27,8 @@ Number | Color | Number + Bright Bit | Bright Color 0x5 | Magenta | 0xd | Light Magenta 0x6 | Brown | 0xe | Yellow 0x7 | Light Gray | 0xf | White -Bit 4 is the _bright bit_, which is unavailable in background color as it's the blink bit. But it's possible to disable blinking through a [BIOS function][disable blinking] and use the full 16 colors as background. + +Bit 4 is the _bright bit_, which turns for example blue into light blue. It is unavailable in background color as the bit is used to enable blinking. However, it's possible to disable blinking through a [BIOS function][disable blinking]. Then the full 16 colors can be used as background. [disable blinking]: http://www.ctyme.com/intr/rb-0117.htm @@ -42,7 +44,16 @@ pub enum Color { Yellow = 14, White = 15, } +``` +We use a [C-like enum] here to explicitly specify the number for each color. Because of the `repr(u8)` attribute each enum variant is stored as an `u8`. Actually 4 bits would be sufficient, but Rust doesn't have an `u4` type. +[C-like enum]: http://rustbyexample.com/custom_types/enum/c_like.html + +To represent a full color code that specifies foreground and background color, we create a [newtype] on top of `u8`: + +[newtype]: https://aturon.github.io/features/types/newtype.html + +```rust struct ColorCode(u8); impl ColorCode { @@ -51,7 +62,11 @@ impl ColorCode { } } ``` -We use the `repr(u8)` attribute to represent each variant of `Color` as an `u8`. The `ColorCode` contains the full color byte, containing foreground and background color. The `new` function is a [const function] to allow it in static initializers. It's unstable so we need to add the `const_fn` feature in `src/lib.rs`. We ignore the blink bit here to keep the code short. Now we can represent a screen character and the text buffer: +The `ColorCode` contains the full color byte, containing foreground and background color. Blinking is enabled implicitly by using a bright background color (soon we will disable blinking anyway). The `new` function is a [const function] to allow it in static initializers. As `const` functions are unstable we need to add the `const_fn` feature in `src/lib.rs`. + +[const function]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md + +Now we can add structures to represent a screen character and the text buffer: ```rust struct ScreenChar { @@ -66,44 +81,42 @@ struct Buffer { chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], } ``` -Now we can represent the actual screen writer: +To ensure that `ScreenChar` is exactly 16-bits, one might be tempted to use the [repr(packed)] attribute. But Rust does not insert any padding around two `u8` values, so it's not needed here. And `repr(packed)` can cause [undefined behavior][repr(packed) issue] and that's always bad. + +[repr(packed)]: https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#repr(packed) +[repr(packed) issue]: https://github.com/rust-lang/rust/issues/27060 + +To actually write to screen, we now create a writer type: ```rust -pub struct Writer { +pub struct Writer<'a> { column_position: usize, color_code: ColorCode, -} - -impl Writer { - pub const fn new(foreground: Color, background: Color) -> Writer { - Writer { - column_position: 0, - color_code: ColorCode::new(foreground, background), - } - } + buffer: &'a mut Buffer, } ``` -It's `pub` because it's part of the public interface and `const` to allow it in static initializers. The plan is to write always to the last line and shift lines up when a line is full (or on `\n`). So we just need to store the current column position and the current `ColorCode`. +The explicit lifetime tells Rust that the writer lives as long as the mutual buffer reference. Thus Rust ensures statically that the writer does not write to invalid memory. -### Writing to screen -Now we can create a `write_byte` function: +The writer will always write to the last line and shift lines up when a line is full (or on `\n`). So the current row is always the last row and just the current column position needs to be stored. The current foreground and background colors are specified by `color_code`. + +### 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: ```rust impl Writer { pub fn write_byte(&mut self, byte: u8) { - const NEWLINE: u8 = '\n' as u8; - const BUFFER: *mut Buffer = 0xb8000 as *mut _; - match byte { - NEWLINE => {}, // TODO + b'\n' => { + self.new_line(); + }, byte => { if self.column_position >= BUFFER_WIDTH { - //TODO + self.new_line(); } let row = BUFFER_HEIGHT - 1; let col = self.column_position; - Self::buffer().chars[row][col] = ScreenChar { + self.buffer.chars[row][col] = ScreenChar { ascii_character: byte, color_code: self.color_code, }; @@ -111,37 +124,40 @@ impl Writer { } } } + fn new_line(&mut self) {} } ``` -Some Notes: +If the byte is the [newline] byte `\n`, the writer does not print anything. Instead it calls a `new_line` method, which we'll implement later. Other bytes get printed to the screen in the second match case. -- We write a new `ScreenChar` to the current field in the buffer and increase the column position -- We take `&mut self` as we modify the column position -- `Self` refers to the `Writer` type +When printing a byte, the writer checks if the current line is full. In that case, a `new_line` call is required before to wrap the line. Since the writer always writes to the last line, `row` is just the last line's index. The writer uses the mutable reference stored in `buffer` to set the screen character at `row` and `col`. Then the column position is advanced by one. -The `buffer()` function just converts the raw pointer to a mutable reference. We need the `unsafe` block because Rust doesn't know if the pointer is valid. It looks like this: +[newline]: https://en.wikipedia.org/wiki/Newline + +To test it, we add can add a `test` function to the module: ```rust -impl Writer { - fn buffer() -> &'static mut Buffer { - const BUFFER: *mut Buffer = 0xb8000 as *mut _; - unsafe{&mut *BUFFER} - } -} -``` -Now we can test it in `main`: +pub fn test() { + const BUFFER: *mut Buffer = 0xb8000 as *mut _; + let buffer = unsafe{&mut *BUFFER}; -```rust -pub extern fn main() { - use vga_buffer::{Writer, Color}; - ... - let mut writer = Writer::new(Color::Blue, Color::LightGreen); + let writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::LightGreen, Color::Black), + buffer: buffer, + }; writer.write_byte(b'H'); } ``` -The `b'H'` is a [byte character] and represents the ASCII code point for `H`. It should be printed in the _lower_ left corner of the screen on `make run`. +First, we create a mutable reference to the VGA text buffer at `0xb8000`. The `const` defines a raw pointer that we convert to a reference using the [`&mut *` pattern][references and raw pointers]. The `unsafe` block is needed because Rust doesn't know if the raw pointer is valid. Notice that creating a raw pointer is completely safe, only dereferencing it is `unsafe`. After creating the reference, we use it to create a new writer. -To print whole strings, we can convert them to bytes[^utf8-problems] and print them one-by-one: +Finally, we write the byte `b'H'`. The `b` in front specifies that it's a [byte character], which represents an ASCII code point. When we call `vga_buffer::test` in main, it's printed in the _lower_ left corner of the screen in light green. + +[references and raw pointers]: https://doc.rust-lang.org/book/raw-pointers.html#references-and-raw-pointers +[byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings + +### Printing Strings + +To print whole strings, we can convert them to bytes and print them one-by-one: ```rust pub fn write_str(&mut self, s: &str) { @@ -150,8 +166,13 @@ pub fn write_str(&mut self, s: &str) { } } ``` -[byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings -[^utf8-problems]: This approach works well for strings that contain only ASCII characters. For other Unicode characters, however, we get weird symbols on the screen. But they can't be printed in the VGA text buffer anyway. +You can try it yourself in the `test` function. When you try strings with some special characters like `ä` or `λ`, you'll notice that they cause weird symbols on screen. That's because they are represented by multiple bytes in [UTF-8]. By converting them to bytes, we of course get strange results. But since the VGA buffer doesn't support UTF-8, it's not possible to display these characters anyway. So let's just stick to ASCII strings for now. + +[UTF-8]: http://www.fileformat.info/info/unicode/utf8.htm + +## Providing an Interface + +## Synchronization ## Support Formatting Macros It would be nice to support Rust's formatting macros, too. That way, we can easily print different types like integers or floats. To support them, we need to implement the [core::fmt::Write] trait. The only required method of this trait is `write_str` and looks quite similar to our `write_str` method. We just need to move it into an `impl ::core::fmt::Write for Writer` block and add a return type: From 517c5eab0d1e2cf917a774a235c75bdfc48417cb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 26 Sep 2015 13:25:33 +0200 Subject: [PATCH 02/12] Some wording improvements --- _drafts/printing-to-screen.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index e770df9c..c83c7ed3 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -33,7 +33,9 @@ Bit 4 is the _bright bit_, which turns for example blue into light blue. It is u [disable blinking]: http://www.ctyme.com/intr/rb-0117.htm ## Creating a Rust Module -Let's create the Rust module `vga_buffer`. Therefor we create a file named `src/vga_buffer.rs` and add a `mod vga_buffer` line to `src/lib.rs`. Now we can create an enum for the colors ([full file](#TODO)): +Now that we know how the VGA buffer works, we can create a Rust module to handle printing. To create a new module named `vga_buffer`, we just need to create a file named `src/vga_buffer.rs` and add a `mod vga_buffer` line to `src/lib.rs`. + +First, we represent the different colors using an enum ([full file](#TODO)): ```rust #[repr(u8)] @@ -81,23 +83,23 @@ struct Buffer { chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], } ``` -To ensure that `ScreenChar` is exactly 16-bits, one might be tempted to use the [repr(packed)] attribute. But Rust does not insert any padding around two `u8` values, so it's not needed here. And `repr(packed)` can cause [undefined behavior][repr(packed) issue] and that's always bad. +To ensure that `ScreenChar` is exactly 16-bits, one might be tempted to use the [repr(packed)] attribute. But Rust does not insert any padding around two `u8` values, so it's not needed here. And using `repr(packed)` is generally discouraged because it can [cause undefined behavior][repr(packed) issue]. -[repr(packed)]: https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#repr(packed) +[repr(packed)]: https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#reprpacked [repr(packed) issue]: https://github.com/rust-lang/rust/issues/27060 To actually write to screen, we now create a writer type: ```rust -pub struct Writer<'a> { +pub struct Writer { column_position: usize, color_code: ColorCode, - buffer: &'a mut Buffer, + buffer: Unique, } ``` -The explicit lifetime tells Rust that the writer lives as long as the mutual buffer reference. Thus Rust ensures statically that the writer does not write to invalid memory. +The writer will always write to the last line and shift lines up when a line is full (or on `\n`). So just the current column position needs to be stored. The current foreground and background colors are specified by `color_code`. To make it possible to create a `static` Writer later, the `buffer` field stores an `Unique` instead of a plain `*mut Buffer`. [Unique] is a wrapper that implements Send/Sync and is thus usable as a `static` (we want to add static Writer later). -The writer will always write to the last line and shift lines up when a line is full (or on `\n`). So the current row is always the last row and just the current column position needs to be stored. The current foreground and background colors are specified by `color_code`. +[Unique]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html ### 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: From 93a7bb63332c60e54d60b08cd61ef18840021e80 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 27 Sep 2015 18:05:43 +0200 Subject: [PATCH 03/12] many improvements --- _drafts/printing-to-screen.md | 99 ++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index c83c7ed3..ca5f94e6 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -32,9 +32,10 @@ Bit 4 is the _bright bit_, which turns for example blue into light blue. It is u [disable blinking]: http://www.ctyme.com/intr/rb-0117.htm -## Creating a Rust Module +## A basic Rust Module Now that we know how the VGA buffer works, we can create a Rust module to handle printing. To create a new module named `vga_buffer`, we just need to create a file named `src/vga_buffer.rs` and add a `mod vga_buffer` line to `src/lib.rs`. +### Colors First, we represent the different colors using an enum ([full file](#TODO)): ```rust @@ -68,6 +69,7 @@ The `ColorCode` contains the full color byte, containing foreground and backgrou [const function]: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md +### The Text Buffer Now we can add structures to represent a screen character and the text buffer: ```rust @@ -97,7 +99,7 @@ pub struct Writer { buffer: Unique, } ``` -The writer will always write to the last line and shift lines up when a line is full (or on `\n`). So just the current column position needs to be stored. The current foreground and background colors are specified by `color_code`. To make it possible to create a `static` Writer later, the `buffer` field stores an `Unique` instead of a plain `*mut Buffer`. [Unique] is a wrapper that implements Send/Sync and is thus usable as a `static` (we want to add static Writer later). +The writer will always write to the last line and shift lines up when a line is full (or on `\n`). The `column_position` field keeps track of the current position in the last row. The current foreground and background colors are specified by `color_code` and a pointer to the VGA buffer is stored in `buffer`. To make it possible to create a `static` Writer later, the `buffer` field stores an `Unique` instead of a plain `*mut Buffer`. [Unique] is a wrapper that implements Send/Sync and is thus usable as a `static`. [Unique]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html @@ -115,10 +117,11 @@ impl Writer { if self.column_position >= BUFFER_WIDTH { self.new_line(); } + let row = BUFFER_HEIGHT - 1; let col = self.column_position; - self.buffer.chars[row][col] = ScreenChar { + self.buffer().chars[row][col] = ScreenChar { ascii_character: byte, color_code: self.color_code, }; @@ -126,35 +129,40 @@ impl Writer { } } } - fn new_line(&mut self) {} + + fn buffer(&mut self) -> &mut Buffer { + unsafe{ self.buffer.get_mut() } + } + + fn new_line(&mut self) {/* TODO */} } ``` If the byte is the [newline] byte `\n`, the writer does not print anything. Instead it calls a `new_line` method, which we'll implement later. Other bytes get printed to the screen in the second match case. -When printing a byte, the writer checks if the current line is full. In that case, a `new_line` call is required before to wrap the line. Since the writer always writes to the last line, `row` is just the last line's index. The writer uses the mutable reference stored in `buffer` to set the screen character at `row` and `col`. Then the column position is advanced by one. - [newline]: https://en.wikipedia.org/wiki/Newline -To test it, we add can add a `test` function to the module: +When printing a byte, the writer checks if the current line is full. In that case, a `new_line` call is required before to wrap the line. Then it writes a new `ScreenChar` to the buffer at the current position. Finally, the current column position is advanced. + +The `buffer()` auxiliary method converts the raw pointer in the `buffer` field into a safe mutable buffer reference. The unsafe block is needed because the [get_mut()] method of `Unique` is unsafe. But our `buffer()` method itself isn't marked as unsafe, so it must not introduce any unsafety (e.g. cause segfaults). To guarantee that, it's very important that the `buffer` field always points to a valid `Buffer`. It's like a contract that we must stand to every time we create a `Writer`. To ensure that it's not possible to create an invalid `Writer` from outside of the module, the struct must have at least one private field and public creation functions are not allowed either. + +[get_mut()]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html#method.get_mut + +### Try it out! +To write some characters to the screen, you can create a temporary function: ```rust -pub fn test() { - const BUFFER: *mut Buffer = 0xb8000 as *mut _; - let buffer = unsafe{&mut *BUFFER}; - - let writer = Writer { +pub fn print_something() { + let mut writer = Writer { column_position: 0, color_code: ColorCode::new(Color::LightGreen, Color::Black), - buffer: buffer, - }; + buffer: Unique::new(0xb8000 as *mut _), + } + writer.write_byte(b'H'); } ``` -First, we create a mutable reference to the VGA text buffer at `0xb8000`. The `const` defines a raw pointer that we convert to a reference using the [`&mut *` pattern][references and raw pointers]. The `unsafe` block is needed because Rust doesn't know if the raw pointer is valid. Notice that creating a raw pointer is completely safe, only dereferencing it is `unsafe`. After creating the reference, we use it to create a new writer. +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. -Finally, we write the byte `b'H'`. The `b` in front specifies that it's a [byte character], which represents an ASCII code point. When we call `vga_buffer::test` in main, it's printed in the _lower_ left corner of the screen in light green. - -[references and raw pointers]: https://doc.rust-lang.org/book/raw-pointers.html#references-and-raw-pointers [byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings ### Printing Strings @@ -168,16 +176,15 @@ pub fn write_str(&mut self, s: &str) { } } ``` -You can try it yourself in the `test` function. When you try strings with some special characters like `ä` or `λ`, you'll notice that they cause weird symbols on screen. That's because they are represented by multiple bytes in [UTF-8]. By converting them to bytes, we of course get strange results. But since the VGA buffer doesn't support UTF-8, it's not possible to display these characters anyway. So let's just stick to ASCII strings for now. +You can try it yourself in the `print_something` function. When you print strings with some special characters like `ä` or `λ`, you'll notice that they cause weird symbols on screen. That's because they are represented by multiple bytes in [UTF-8]. By converting them to bytes, we of course get strange results. But since the VGA buffer doesn't support UTF-8, it's not possible to display these characters anyway. To ensure that a string contains only ASCII characters, you can prefix a `b` to create a [Byte String]. [UTF-8]: http://www.fileformat.info/info/unicode/utf8.htm +[Byte String]: https://doc.rust-lang.org/reference.html#characters-and-strings -## Providing an Interface - -## Synchronization +## Providing an Interface/Printing from outside/Synchronization/... ## Support Formatting Macros -It would be nice to support Rust's formatting macros, too. That way, we can easily print different types like integers or floats. To support them, we need to implement the [core::fmt::Write] trait. The only required method of this trait is `write_str` and looks quite similar to our `write_str` method. We just need to move it into an `impl ::core::fmt::Write for Writer` block and add a return type: +It would be nice to support Rust's formatting macros, too. That way, we can easily print different types like integers or floats. To support them, we need to implement the [core::fmt::Write] trait. The only required method of this trait is `write_str` that looks quite similar to our `write_str` method. To implement the trait, we just need to move it into an `impl ::core::fmt::Write for Writer` block and add a return type: ```rust impl ::core::fmt::Write for Writer { @@ -189,7 +196,7 @@ impl ::core::fmt::Write for Writer { } } ``` -The `Ok(())` is just the `Ok` Result containing the `()` type. We can drop the `pub` because trait methods are always public. +The `Ok(())` is just a `Ok` Result containing the `()` type. We can drop the `pub` because trait methods are always public. Now we can use Rust's built-in `write!`/`writeln!` formatting macros: @@ -205,16 +212,19 @@ Now you should see a `Hello! The numbers are 42 and 0.3333333333333333` in stran [core::fmt::Write]: https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html ## Newlines -Right now, we just ignore newlines and characters that don't fit into the line anymore. Instead we want to move every character one line up (the top line gets deleted) and start at the beginning of the last line again. To do this, we add a `new_line` method to `Writer`: +Right now, we just ignore newlines and characters that don't fit into the line anymore. Instead we want to move every character one line up (the top line gets deleted) and start at the beginning of the last line again. To do this, we add an implementation for the `new_line` method of `Writer`: ```rust fn new_line(&mut self) { for row in 0..(BUFFER_HEIGHT-1) { - Self::buffer().chars[row] = Self::buffer().chars[row + 1] + let buffer = self.buffer(); + buffer.chars[row] = buffer.chars[row + 1] } self.clear_row(BUFFER_HEIGHT-1); self.column_position = 0; } + +fn clear_row(&mut self) {/* see below */} ``` We just move each line to the line above. Notice that the range notation (`..`) is exclusive the upper bound. @@ -226,55 +236,48 @@ fn clear_row(&mut self, row: usize) { ascii_character: b' ', color_code: self.color_code, }; - Self::buffer().chars[row] = [blank; BUFFER_WIDTH]; + self.buffer().chars[row] = [blank; BUFFER_WIDTH]; } ``` -Now we just need to call the `new_line()` method in the 2 cases marked with `//TODO` and our writer supports newlines. -## A `println!` macro +## A println macro Rust's [macro syntax] is a bit strange, so we won't try to write a macro from scratch. Instead we look at the source of the [`println!` macro] in the standard library: +[macro syntax]: https://doc.rust-lang.org/nightly/book/macros.html +[`println!` macro]: https://doc.rust-lang.org/nightly/std/macro.println!.html + ``` macro_rules! println { ($fmt:expr) => (print!(concat!($fmt, "\n"))); ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); } ``` -It just refers to the [`print!` macro] that is defined as: +It just adds a `\n` and invokes the [`print!` macro], which is defined as: + +[`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html ``` macro_rules! print { ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*))); } ``` -It just calls the _print_ method in the `io` module of the current crate (`$crate`), which is `std`. The [`_print` function] is rather complicated, as it supports different `Stdout`s. +It calls the _print_ method in the `io` module of the current crate (`$crate`), which is `std`. The [`_print` function] in libstd is rather complicated, as it supports different `Stdout`s. -To print to the VGA buffer, we just copy both macros and replace the `io` module with the `vga_buffer` buffer in the `print!` macro: +[`_print` function]: https://doc.rust-lang.org/nightly/src/std/io/stdio.rs.html#578 + +To print to the VGA buffer, we just copy both macros, but modify the `print!` macro: ``` macro_rules! print { - ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); + $crate::vga_buffer::WRITER.lock().write_fmt(format_args!($($arg)*)).unwrap(); } ``` -Now we can write our own `_print` function: - -```rust -pub fn _print(fmt: ::core::fmt::Arguments) { - use core::fmt::Write; - static mut WRITER: Writer = Writer::new(Color::LightGreen, Color::Black); - unsafe{WRITER.write_fmt(fmt)}; -} -``` -The function needs to be public because every `print!(…)` is expanded to `::vga_buffer::_print(…)`. It uses a `static mut` to store a writer and calls the `write_fmt` method of the `core::fmt::Write` trait (hence the import). It's highly discouraged to use `static mut`s because they introduce all kinds of data races (that's why every access is unsafe). We use it here anyway, as we have only a single thread at the moment. But we already have another data race: We can create multiple `Writer`s, that write to the same memory at `0xb8000`. So as soon as we add multithreading, we need to revisit this module again and find better solutions. - -[macro syntax]: https://doc.rust-lang.org/nightly/book/macros.html -[`println!` macro]: https://doc.rust-lang.org/nightly/std/macro.println!.html -[`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html +Instead of a `_print` function, we call the `write_fmt` method of our static `Writer`. The additional `unwrap()` at the end panics if printing isn't successful. But since we always return `Ok` in `write_str`, that should not happen. ## Clearing the screen We can now add a rather trivial last function: -``` +```rust pub fn clear_screen() { for _ in 0..BUFFER_HEIGHT { println!(""); From ec053b2586059c341b279de68dfdcef468bcc100 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 6 Oct 2015 18:15:10 +0200 Subject: [PATCH 04/12] Revise and extend post about printing to screen --- _drafts/printing-to-screen.md | 94 ++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index ca5f94e6..6dfa246e 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -103,7 +103,7 @@ The writer will always write to the last line and shift lines up when a line is [Unique]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html -### Printing Characters +## Printing to Screen Now we can use the `Writer` to modify the buffer's characters. First we create a method to write a single ASCII byte: ```rust @@ -181,9 +181,7 @@ You can try it yourself in the `print_something` function. When you print string [UTF-8]: http://www.fileformat.info/info/unicode/utf8.htm [Byte String]: https://doc.rust-lang.org/reference.html#characters-and-strings -## Providing an Interface/Printing from outside/Synchronization/... - -## Support Formatting Macros +### Support Formatting Macros It would be nice to support Rust's formatting macros, too. That way, we can easily print different types like integers or floats. To support them, we need to implement the [core::fmt::Write] trait. The only required method of this trait is `write_str` that looks quite similar to our `write_str` method. To implement the trait, we just need to move it into an `impl ::core::fmt::Write for Writer` block and add a return type: ```rust @@ -211,7 +209,7 @@ Now you should see a `Hello! The numbers are 42 and 0.3333333333333333` in stran [core::fmt::Write]: https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html -## Newlines +### Newlines Right now, we just ignore newlines and characters that don't fit into the line anymore. Instead we want to move every character one line up (the top line gets deleted) and start at the beginning of the last line again. To do this, we add an implementation for the `new_line` method of `Writer`: ```rust @@ -240,6 +238,73 @@ fn clear_row(&mut self, row: usize) { } ``` +## Providing an Interface/Printing from outside/Synchronization/... +To provide a global writer that can used as an interface from other modules, we can add a `static` writer: + +```rust +pub static WRITER: Writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::LightGreen, Color::Black), + buffer: Unique::new(0xb8000 as *mut _), +}; +``` + +This won't work! You can try it yourself in the `print_something` function. The reason is that we try to take a mutable reference (`&mut`) to a immutable `static` when calling `WRITER.print_byte`. + +To resolve it, we could use a [mutable static]. But then every read and write to it would be unsafe since it could easily introduce data races and other bad things. Using `static mut` is highly discouraged, there are even proposals to [remove it][remove static mut]. + +[mutable static]: https://doc.rust-lang.org/book/const-and-static.html#mutability +[remove static mut]: https://internals.rust-lang.org/t/pre-rfc-remove-static-mut/1437 + +But what are the alternatives? We could try to use a cell type like [RefCell] or even [UnsafeCell] to provide [interior mutability]. But these types aren't [Sync] (with good reason), so we can't use them in statics. + +[RefCell]: https://doc.rust-lang.org/nightly/core/cell/struct.RefCell.html +[UnsafeCell]: https://doc.rust-lang.org/nightly/core/cell/struct.UnsafeCell.html +[interior mutability]: https://doc.rust-lang.org/book/mutability.html#interior-vs.-exterior-mutability +[Sync]: https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html + +To get synchronized interior mutability, users of the standard library can use [Mutex]. It provides mutual exclusion by blocking threads when the resource is already locked. But our basic kernel does not have any blocking support or even a concept of threads, so we can't use it either. However there is a really basic kind of mutex in computer science that requires no operating system features: the [spinlock]. Instead of blocking, the threads simply try to lock it again and again in a tight loop and thus burn CPU time until the mutex is free again. + +[Mutex]: https://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html +[spinlock]: https://en.wikipedia.org/wiki/Spinlock + +To use a spinning mutex, we can add the [spin crate] as a dependency in Cargo.toml: + +[spin crate]: https://crates.io/crates/spin + +```toml +... +[dependencies] +rlibc = "*" +spin = "*" +``` +and a `extern crate spin;` definition in `src/lib.rs`. Then we can use the spinning Mutex to provide interior mutability to our static writer: + +```rust +use spin::Mutex; +... +pub static WRITER: Mutex = Mutex::new(Writer { + column_position: 0, + color_code: ColorCode::new(Color::LightGreen, Color::Black), + buffer: Unique::new(0xb8000 as *mut _), +}); +``` +[Mutex::new] is a const function, too, so it can be used in statics. + +Now we can easily print from our main function: + +[Mutex::new]: https://mvdnes.github.io/rust-docs/spinlock-rs/spin/struct.Mutex.html#method.new + +```rust +pub extern fn rust_main() { + use core::fmt::Write; + vga_buffer::WRITER.lock().write_str("Hello again"); + write!(vga_buffer::WRITER.lock(), ", some numbers: {} {}", 42, 1.337); + loop{} +} +``` +Note that we need to import the `Write` trait if we want to use its functions. + ## A println macro Rust's [macro syntax] is a bit strange, so we won't try to write a macro from scratch. Instead we look at the source of the [`println!` macro] in the standard library: @@ -252,7 +317,7 @@ macro_rules! println { ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); } ``` -It just adds a `\n` and invokes the [`print!` macro], which is defined as: +It just adds a `\n` and then invokes the [`print!` macro], which is defined as: [`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html @@ -261,21 +326,26 @@ macro_rules! print { ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*))); } ``` -It calls the _print_ method in the `io` module of the current crate (`$crate`), which is `std`. The [`_print` function] in libstd is rather complicated, as it supports different `Stdout`s. +It calls the `_print` method in the `io` module of the current crate (`$crate`), which is `std`. The [`_print` function] in libstd is rather complicated, as it supports different `Stdout` devices. [`_print` function]: https://doc.rust-lang.org/nightly/src/std/io/stdio.rs.html#578 -To print to the VGA buffer, we just copy both macros, but modify the `print!` macro: +To print to the VGA buffer, we just copy the `println!` macro and modify the `print!` macro to use our static `WRITER` instead of `_print`: ``` macro_rules! print { - $crate::vga_buffer::WRITER.lock().write_fmt(format_args!($($arg)*)).unwrap(); + ($($arg:tt)*) => ({ + use core::fmt::Write; + $crate::vga_buffer::WRITER.lock().write_fmt(format_args!($($arg)*)).unwrap(); + }); } ``` -Instead of a `_print` function, we call the `write_fmt` method of our static `Writer`. The additional `unwrap()` at the end panics if printing isn't successful. But since we always return `Ok` in `write_str`, that should not happen. +Instead of a `_print` function, we call the `write_fmt` method of our static `Writer`. Since we're using a method from the `Write` trait, we need to import it before. The additional `unwrap()` at the end panics if printing isn't successful. But since we always return `Ok` in `write_str`, that should not happen. -## Clearing the screen -We can now add a rather trivial last function: +Notice the additional scope around the macro: It's `=> ({…})` instead of `=> (…)`. The additional `{}` avoid a silent import of the `Write` trait on macro expansion. + +### Clearing the screen +We can now use `println!` to add a rather trivial function to clear the screen: ```rust pub fn clear_screen() { From fd889d86130ba662400dfdaac66d60a3223f08df Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 14 Oct 2015 10:46:55 +0200 Subject: [PATCH 05/12] Add an introduction for the VGA buffer post --- _drafts/printing-to-screen.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index 6dfa246e..19616d35 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -3,7 +3,12 @@ layout: post title: 'Printing to Screen' category: 'rust-os' --- -TODO Introduction +In the [previous post] we switched from assembly to [Rust], a much safer and more expressive language. But we still need unsafe features like [raw pointers] every time we want to print something to the screen. In this post we will create a Rust module that provides a safe and easy-to-use interface to the VGA text buffer. It will support Rust's [formatting macros], too. + +[previous post]: {{ site.url }}{{ page.previous.url }} +[Rust]: https://www.rust-lang.org/ +[raw pointers]: https://doc.rust-lang.org/book/raw-pointers.html +[formatting macros]: https://doc.rust-lang.org/std/fmt/#related-macros ## The VGA Text Buffer The text buffer starts at physical address `0xb8000` and contains the characters displayed on screen. It has 80 rows and 25 columns. Each screen character has the following format: From 8d90933c016af797e9aff2f23c0d48c4322fa2f1 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 22 Oct 2015 16:06:30 +0200 Subject: [PATCH 06/12] Update introduction --- _drafts/printing-to-screen.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index 19616d35..878f7070 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -3,13 +3,18 @@ layout: post title: 'Printing to Screen' category: 'rust-os' --- -In the [previous post] we switched from assembly to [Rust], a much safer and more expressive language. But we still need unsafe features like [raw pointers] every time we want to print something to the screen. In this post we will create a Rust module that provides a safe and easy-to-use interface to the VGA text buffer. It will support Rust's [formatting macros], too. +In the [previous post] we switched from assembly to [Rust], a systems programming language that provides great safety. But so far we are using unsafe features like [raw pointers] whenever we want to print to screen. In this post we will create a Rust module that provides a safe and easy-to-use interface for the VGA text buffer. It will support Rust's [formatting macros], too. [previous post]: {{ site.url }}{{ page.previous.url }} [Rust]: https://www.rust-lang.org/ [raw pointers]: https://doc.rust-lang.org/book/raw-pointers.html [formatting macros]: https://doc.rust-lang.org/std/fmt/#related-macros +Since we are using some recent unstable features, you will need an up-to-date nighly compiler. If you have any questions, problems, or suggestions please [file an issue] or create a comment at the bottom. The code from this post is also available on [Github][code repository]. + +[file an issue]: https://github.com/phil-opp/phil-opp.github.io/issues +[code repository]: https://github.com/phil-opp/blog_os/tree/printing_to_screen + ## The VGA Text Buffer The text buffer starts at physical address `0xb8000` and contains the characters displayed on screen. It has 80 rows and 25 columns. Each screen character has the following format: From 6602332094a16b3a403bdbfa6a4585fecb5ef95e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 23 Oct 2015 01:42:03 +0200 Subject: [PATCH 07/12] Update introduction --- _drafts/printing-to-screen.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index 878f7070..46740493 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -10,10 +10,10 @@ In the [previous post] we switched from assembly to [Rust], a systems programmin [raw pointers]: https://doc.rust-lang.org/book/raw-pointers.html [formatting macros]: https://doc.rust-lang.org/std/fmt/#related-macros -Since we are using some recent unstable features, you will need an up-to-date nighly compiler. If you have any questions, problems, or suggestions please [file an issue] or create a comment at the bottom. The code from this post is also available on [Github][code repository]. +This post uses recent unstable features, so you need an up-to-date nighly compiler. If you have any questions, problems, or suggestions please [file an issue] or create a comment at the bottom. The code from this post is also available on [Github][code repository]. [file an issue]: https://github.com/phil-opp/phil-opp.github.io/issues -[code repository]: https://github.com/phil-opp/blog_os/tree/printing_to_screen +[code repository]: https://github.com/phil-opp/blog_os/tree/printing_to_screen/src ## The VGA Text Buffer The text buffer starts at physical address `0xb8000` and contains the characters displayed on screen. It has 80 rows and 25 columns. Each screen character has the following format: From cc736733054e0d6982c6c4ec787333f2af69a8eb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 23 Oct 2015 01:42:40 +0200 Subject: [PATCH 08/12] Improve macro subsection --- _drafts/printing-to-screen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index 46740493..11d17f6a 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -352,7 +352,7 @@ macro_rules! print { ``` Instead of a `_print` function, we call the `write_fmt` method of our static `Writer`. Since we're using a method from the `Write` trait, we need to import it before. The additional `unwrap()` at the end panics if printing isn't successful. But since we always return `Ok` in `write_str`, that should not happen. -Notice the additional scope around the macro: It's `=> ({…})` instead of `=> (…)`. The additional `{}` avoid a silent import of the `Write` trait on macro expansion. +Note the additional `{}` scope around the macro: I wrote `=> ({…})` instead of `=> (…)`. The additional `{}` avoids that the `Write` trait is silently imported when `print` is used. ### Clearing the screen We can now use `println!` to add a rather trivial function to clear the screen: From 47ffda304dcb9ec3d40769cdf54db5676bc72973 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 23 Oct 2015 01:43:05 +0200 Subject: [PATCH 09/12] Use println to print hello world --- _drafts/printing-to-screen.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index 11d17f6a..0f87c8e5 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -364,6 +364,22 @@ pub fn clear_screen() { } } ``` +### 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: + +```rust +#[macro_use] +mod vga_buffer; + +#[no_mangle] +pub extern fn rust_main() { + // ATTENTION: we have a very small stack and no guard page + println!("Hello World{}", "!"); + + loop{} +} +``` +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. ## What's next? Soon we will tackle virtual memory management and map the kernel sections correctly. This will cause many strange bugs and boot loops. To understand what's going on a real debugger is indispensable. In the [next post] we will setup [GDB] to work with QEMU. From 9b61ace59dd0c458234c1ed56f0ef423a7b95049 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 23 Oct 2015 01:43:47 +0200 Subject: [PATCH 10/12] Rework `What's next?` --- _drafts/printing-to-screen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index 0f87c8e5..0050d610 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -382,4 +382,4 @@ pub extern fn rust_main() { 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. ## What's next? -Soon we will tackle virtual memory management and map the kernel sections correctly. This will cause many strange bugs and boot loops. To understand what's going on a real debugger is indispensable. In the [next post] we will setup [GDB] to work with QEMU. +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. From 44f512a90cdf6d0902f3994866928d2facd83bbb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 23 Oct 2015 01:44:05 +0200 Subject: [PATCH 11/12] Add section about other Rust OS projects --- _drafts/printing-to-screen.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/_drafts/printing-to-screen.md b/_drafts/printing-to-screen.md index 0050d610..d1a7653b 100644 --- a/_drafts/printing-to-screen.md +++ b/_drafts/printing-to-screen.md @@ -383,3 +383,23 @@ Since we imported the macros at crate level, they are available in all modules a ## 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. + +## Other Rust OS Projects +Now that you know the very basics of OS development in Rust, you should also check out the following projects: + +- [Rust Bare-Bones Kernel]: A basic kernel with roughly the same functionality as ours. Writes output to the serial port instead of the VGA buffer and maps the kernel to the [higher half] \(instead of our identity mapping). +_Note_: You need a [cross compiler] to build it (or you create some symbolic links[^fn-symlink]). +[Rust Bare-Bones Kernel]: https://github.com/thepowersgang/rust-barebones-kernel +[higher half]: http://wiki.osdev.org/Higher_Half_Kernel +[cross compiler]: http://wiki.osdev.org/GCC_Cross-Compiler +[^fn-symlink]: You will need symlink `x86_64-none_elf-XXX` to `/usr/bin/XXX` where `XXX` is in {`as`, `gcc`, `ld`, `objcopy`, `objdump`, `strip`}. The `x86_64-none_elf-XXX` files must be in some folder that is in your `$PATH`. But then you can only build for your x86_64 host architecture, so use this hack only for testing. + +- [RustOS]: More advanced kernel that supports allocation, keyboard inputs, and threads. It also has a scheduler and a basic network driver. +[RustOS]: https://github.com/RustOS-Fork-Holding-Ground/RustOS + +- ["Tifflin" Experimental Kernel]: Big kernel project by thepowersgang, that is actively developed and has over 650 commits. It has a separate userspace and supports multiple file systems, even a GUI is included. Needs a cross compiler, too. +["Tifflin" Experimental Kernel]:https://github.com/thepowersgang/rust_os + +- [Redox]: Probably the most complete Rust OS today. It has an active community and over 1000 Github stars. File systems, network, an audio player, a picture viewer, and much more. Just take a look at the [screenshots][redox screenshots]. +[Redox]: https://github.com/redox-os/redox +[redox screenshots]: https://github.com/redox-os/redox#what-it-looks-like From 4efb3c1eaeb5ec656f0ca6b86a738a310287214f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 23 Oct 2015 01:47:26 +0200 Subject: [PATCH 12/12] Publish poist about printing to screen --- .../2015-10-23-printing-to-screen.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename _drafts/printing-to-screen.md => _posts/2015-10-23-printing-to-screen.md (100%) diff --git a/_drafts/printing-to-screen.md b/_posts/2015-10-23-printing-to-screen.md similarity index 100% rename from _drafts/printing-to-screen.md rename to _posts/2015-10-23-printing-to-screen.md