diff options
Diffstat (limited to 'doc/os.phil-opp.com_vga-text-mode.txt')
-rw-r--r-- | doc/os.phil-opp.com_vga-text-mode.txt | 983 |
1 files changed, 983 insertions, 0 deletions
diff --git a/doc/os.phil-opp.com_vga-text-mode.txt b/doc/os.phil-opp.com_vga-text-mode.txt new file mode 100644 index 0000000..466153f --- /dev/null +++ b/doc/os.phil-opp.com_vga-text-mode.txt @@ -0,0 +1,983 @@ + #[1]RSS feed for os.phil-opp.com + +[2]Writing an OS in Rust + + Philipp Oppermann's blog + + [3]« All Posts + +Table of Contents + + 1. [4]The VGA Text Buffer + 2. [5]A Rust Module + 1. [6]Colors + 2. [7]Text Buffer + 3. [8]Printing + 4. [9]Volatile + 5. [10]Formatting Macros + 6. [11]Newlines + 3. [12]A Global Interface + 1. [13]Lazy Statics + 2. [14]Spinlocks + 3. [15]Safety + 4. [16]A println Macro + 5. [17]Hello World using println + 6. [18]Printing Panic Messages + 4. [19]Summary + 5. [20]What's next? + 6. [21]Comments + +VGA Text Mode + + Feb 26, 2018 + + The [22]VGA text mode is a simple way to print text to the screen. In + this post, we create an interface that makes its usage safe and simple + by encapsulating all unsafety in a separate module. We also implement + support for Rust's [23]formatting macros. + + This blog is openly developed on [24]GitHub. If you have any problems + or questions, please open an issue there. You can also leave comments + [25]at the bottom. The complete source code for this post can be found + in the [26]post-03 branch. + Table of Contents + * [27]The VGA Text Buffer + * [28]A Rust Module + + [29]Colors + + [30]Text Buffer + + [31]Printing + + [32]Volatile + + [33]Formatting Macros + + [34]Newlines + * [35]A Global Interface + + [36]Lazy Statics + + [37]Spinlocks + + [38]Safety + + [39]A println Macro + + [40]Hello World using println + + [41]Printing Panic Messages + * [42]Summary + * [43]What's next? + * [44]Comments + +The VGA Text Buffer + + To print a character to the screen in VGA text mode, one has to write + it to the text buffer of the VGA hardware. The VGA text buffer is a + two-dimensional array with typically 25 rows and 80 columns, which is + directly rendered to the screen. Each array entry describes a single + screen character through the following format: + Bit(s) Value + 0-7 ASCII code point + 8-11 Foreground color + 12-14 Background color + 15 Blink + + The first byte represents the character that should be printed in the + [45]ASCII encoding. To be more specific, it isn't exactly ASCII, but a + character set named [46]code page 437 with some additional characters + and slight modifications. For simplicity, we will proceed to call it an + ASCII character in this post. + + The second byte defines how the character is displayed. The first four + bits define the foreground color, the next three bits the background + color, and the last bit whether the character should blink. The + following colors are available: + Number Color Number + Bright Bit Bright Color + 0x0 Black 0x8 Dark Gray + 0x1 Blue 0x9 Light Blue + 0x2 Green 0xa Light Green + 0x3 Cyan 0xb Light Cyan + 0x4 Red 0xc Light Red + 0x5 Magenta 0xd Pink + 0x6 Brown 0xe Yellow + 0x7 Light Gray 0xf White + + Bit 4 is the bright bit, which turns, for example, blue into light + blue. For the background color, this bit is repurposed as the blink + bit. + + The VGA text buffer is accessible via [47]memory-mapped I/O to the + address 0xb8000. This means that reads and writes to that address don't + access the RAM but directly access the text buffer on the VGA hardware. + This means we can read and write it through normal memory operations to + that address. + + Note that memory-mapped hardware might not support all normal RAM + operations. For example, a device could only support byte-wise reads + and return junk when a u64 is read. Fortunately, the text buffer + [48]supports normal reads and writes, so we don't have to treat it in a + special way. + +A Rust Module + + Now that we know how the VGA buffer works, we can create a Rust module + to handle printing: +//in src/main.rs +mod vga_buffer; + + For the content of this module, we create a new src/vga_buffer.rs file. + All of the code below goes into our new module (unless specified + otherwise). + +Colors + + First, we represent the different colors using an enum: +// in src/vga_buffer.rs + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + + We use a [49]C-like enum here to explicitly specify the number for each + color. Because of the repr(u8) attribute, each enum variant is stored + as a u8. Actually 4 bits would be sufficient, but Rust doesn't have a + u4 type. + + Normally the compiler would issue a warning for each unused variant. By + using the #[allow(dead_code)] attribute, we disable these warnings for + the Color enum. + + By [50]deriving the [51]Copy, [52]Clone, [53]Debug, [54]PartialEq, and + [55]Eq traits, we enable [56]copy semantics for the type and make it + printable and comparable. + + To represent a full color code that specifies foreground and background + color, we create a [57]newtype on top of u8: +// in src/vga_buffer.rs + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +struct ColorCode(u8); + +impl ColorCode { + fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } +} + + The ColorCode struct contains the full color byte, containing + foreground and background color. Like before, we derive the Copy and + Debug traits for it. To ensure that the ColorCode has the exact same + data layout as a u8, we use the [58]repr(transparent) attribute. + +Text Buffer + + Now we can add structures to represent a screen character and the text + buffer: +// in src/vga_buffer.rs + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +struct ScreenChar { + ascii_character: u8, + color_code: ColorCode, +} + +const BUFFER_HEIGHT: usize = 25; +const BUFFER_WIDTH: usize = 80; + +#[repr(transparent)] +struct Buffer { + chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + + Since the field ordering in default structs is undefined in Rust, we + need the [59]repr(C) attribute. It guarantees that the struct's fields + are laid out exactly like in a C struct and thus guarantees the correct + field ordering. For the Buffer struct, we use [60]repr(transparent) + again to ensure that it has the same memory layout as its single field. + + To actually write to screen, we now create a writer type: +// in src/vga_buffer.rs + +pub struct Writer { + column_position: usize, + color_code: ColorCode, + buffer: &'static mut Buffer, +} + + 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 reference to the VGA buffer is + stored in buffer. Note that we need an [61]explicit lifetime here to + tell the compiler how long the reference is valid. The [62]'static + lifetime specifies that the reference is valid for the whole program + run time (which is true for the VGA text buffer). + +Printing + + Now we can use the Writer to modify the buffer's characters. First we + create a method to write a single ASCII byte: +// in src/vga_buffer.rs + +impl Writer { + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => self.new_line(), + byte => { + if self.column_position >= BUFFER_WIDTH { + self.new_line(); + } + + let row = BUFFER_HEIGHT - 1; + let col = self.column_position; + + let color_code = self.color_code; + self.buffer.chars[row][col] = ScreenChar { + ascii_character: byte, + color_code, + }; + self.column_position += 1; + } + } + } + + fn new_line(&mut self) {/* TODO */} +} + + If the byte is the [63]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 used to wrap the line. Then it writes a + new ScreenChar to the buffer at the current position. Finally, the + current column position is advanced. + + To print whole strings, we can convert them to bytes and print them + one-by-one: +// in src/vga_buffer.rs + +impl Writer { + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + // printable ASCII byte or newline + 0x20..=0x7e | b'\n' => self.write_byte(byte), + // not part of printable ASCII range + _ => self.write_byte(0xfe), + } + + } + } +} + + The VGA text buffer only supports ASCII and the additional bytes of + [64]code page 437. Rust strings are [65]UTF-8 by default, so they might + contain bytes that are not supported by the VGA text buffer. We use a + match to differentiate printable ASCII bytes (a newline or anything in + between a space character and a ~ character) and unprintable bytes. For + unprintable bytes, we print a fS character, which has the hex code 0xfe + on the VGA hardware. + +Try it out! + + To write some characters to the screen, you can create a temporary + function: +// in src/vga_buffer.rs + +pub fn print_something() { + let mut writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }; + + writer.write_byte(b'H'); + writer.write_string("ello "); + writer.write_string("Wörld!"); +} + + It first creates a new Writer that points to the VGA buffer at 0xb8000. + The syntax for this might seem a bit strange: First, we cast the + integer 0xb8000 as a mutable [66]raw pointer. Then we convert it to a + mutable reference by dereferencing it (through *) and immediately + borrowing it again (through &mut). This conversion requires an + [67]unsafe block, since the compiler can't guarantee that the raw + pointer is valid. + + Then it writes the byte b'H' to it. The b prefix creates a [68]byte + literal, which represents an ASCII character. By writing the strings + "ello " and "Wörld!", we test our write_string method and the handling + of unprintable characters. To see the output, we need to call the + print_something function from our _start function: +// in src/main.rs + +#[no_mangle] +pub extern "C" fn _start() -> ! { + vga_buffer::print_something(); + + loop {} +} + + When we run our project now, a Hello WfSfSrld! should be printed in the + lower left corner of the screen in yellow: + + QEMU output with a yellow Hello WfSfSrld! in the lower left corner + + Notice that the ö is printed as two fS characters. That's because ö is + represented by two bytes in [69]UTF-8, which both don't fall into the + printable ASCII range. In fact, this is a fundamental property of + UTF-8: the individual bytes of multi-byte values are never valid ASCII. + +Volatile + + We just saw that our message 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 that we really access VGA buffer + memory (instead of normal RAM) and knows nothing about the side effect + that some characters appear on the screen. So it might decide that + these writes are unnecessary and can be omitted. To avoid this + erroneous optimization, we need to specify these writes as + [70]volatile. This tells the compiler that the write has side effects + and should not be optimized away. + + In order to use volatile writes for the VGA buffer, we use the + [71]volatile library. This crate (this is how packages are called in + the Rust world) provides a Volatile wrapper type with read and write + methods. These methods internally use the [72]read_volatile and + [73]write_volatile functions of the core library and thus guarantee + that the reads/writes are not optimized away. + + We can add a dependency on the volatile crate by adding it to the + dependencies section of our Cargo.toml: +# in Cargo.toml + +[dependencies] +volatile = "0.2.6" + + Make sure to specify volatile version 0.2.6. Newer versions of the + crate are not compatible with this post. 0.2.6 is the [74]semantic + version number. For more information, see the [75]Specifying + Dependencies guide of the cargo documentation. + + Let's use it to make writes to the VGA buffer volatile. We update our + Buffer type as follows: +// 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 [76]generic and can wrap (almost) any type). This + ensures that we can't accidentally write to it "normally". Instead, we + have to use the write method now. + + This means that we have to update our Writer::write_byte method: +// in src/vga_buffer.rs + +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, + }); + ... + } + } + } + ... +} + + Instead of a typical assignment using =, we're now using the write + method. Now we can guarantee that the compiler will never optimize away + this write. + +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 [77]core::fmt::Write trait. The only + required method of this trait is write_str, which looks quite similar + to our write_string method, just with a fmt::Result return type: +// in src/vga_buffer.rs + +use core::fmt; + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + + The Ok(()) is just a Ok Result containing the () type. + + Now we can use Rust's built-in write!/writeln! formatting macros: +// in src/vga_buffer.rs + +pub fn print_something() { + use core::fmt::Write; + let mut writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }; + + writer.write_byte(b'H'); + writer.write_string("ello! "); + write!(writer, "The numbers are {} and {}", 42, 1.0/3.0).unwrap(); +} + + Now you should see a Hello! The numbers are 42 and 0.3333333333333333 + at the bottom of the screen. The write! call returns a Result which + causes a warning if not used, so we call the [78]unwrap function on it, + which panics if an error occurs. This isn't a problem in our case, + since writes to the VGA buffer never fail. + +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: +// in src/vga_buffer.rs + +impl Writer { + fn new_line(&mut self) { + for row in 1..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row - 1][col].write(character); + } + } + self.clear_row(BUFFER_HEIGHT - 1); + self.column_position = 0; + } + + fn clear_row(&mut self, row: usize) {/* TODO */} +} + + We iterate over all the screen characters and move each character one + row up. Note that the upper bound of the range notation (..) is + exclusive. We also omit the 0th row (the first range starts at 1) + because it's the row that is shifted off screen. + + To finish the newline code, we add the clear_row method: +// in src/vga_buffer.rs + +impl Writer { + fn clear_row(&mut self, row: usize) { + let blank = ScreenChar { + ascii_character: b' ', + color_code: self.color_code, + }; + 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. + +A Global Interface + + To provide a global writer that can be used as an interface from other + modules without carrying a Writer instance around, we try to create a + static WRITER: +// in src/vga_buffer.rs + +pub static WRITER: Writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, +}; + + However, if we try to compile it now, the following errors occur: +error[E0015]: calls in statics are limited to constant functions, tuple structs +and tuple variants + --> src/vga_buffer.rs:7:17 + | +7 | color_code: ColorCode::new(Color::Yellow, Color::Black), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0396]: raw pointers cannot be dereferenced in statics + --> src/vga_buffer.rs:8:22 + | +8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereference of raw point +er in constant + +error[E0017]: references in statics may only refer to immutable values + --> src/vga_buffer.rs:8:22 + | +8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immutabl +e values + +error[E0017]: references in statics may only refer to immutable values + --> src/vga_buffer.rs:8:13 + | +8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immuta +ble values + + To understand what's happening here, we need to know that statics are + initialized at compile time, in contrast to normal variables that are + initialized at run time. The component of the Rust compiler that + evaluates such initialization expressions is called the "[79]const + evaluator". Its functionality is still limited, but there is ongoing + work to expand it, for example in the "[80]Allow panicking in + constants" RFC. + + The issue with ColorCode::new would be solvable by using [81]const + functions, but the fundamental problem here is that Rust's const + evaluator is not able to convert raw pointers to references at compile + time. Maybe it will work someday, but until then, we have to find + another solution. + +Lazy Statics + + The one-time initialization of statics with non-const functions is a + common problem in Rust. Fortunately, there already exists a good + solution in a crate named [82]lazy_static. This crate provides a + lazy_static! macro that defines a lazily initialized static. Instead of + computing its value at compile time, the static lazily initializes + itself when accessed for the first time. Thus, the initialization + happens at runtime, so arbitrarily complex initialization code is + possible. + + Let's add the lazy_static crate to our project: +# in Cargo.toml + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] + + We need the spin_no_std feature, since we don't link the standard + library. + + With lazy_static, we can define our static WRITER without problems: +// in src/vga_buffer.rs + +use lazy_static::lazy_static; + +lazy_static! { + pub static ref WRITER: Writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }; +} + + However, this WRITER is pretty useless since it is immutable. This + means that we can't write anything to it (since all the write methods + take &mut self). One possible solution would be to use a [83]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 were even proposals to [84]remove it. + But what are the alternatives? We could try to use an immutable static + with a cell type like [85]RefCell or even [86]UnsafeCell that provides + [87]interior mutability. But these types aren't [88]Sync (with good + reason), so we can't use them in statics. + +Spinlocks + + To get synchronized interior mutability, users of the standard library + can use [89]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 [90]spinlock. + Instead of blocking, the threads simply try to lock it again and again + in a tight loop, thus burning CPU time until the mutex is free again. + + To use a spinning mutex, we can add the [91]spin crate as a dependency: +# in Cargo.toml +[dependencies] +spin = "0.5.2" + + Then we can use the spinning mutex to add safe [92]interior mutability + to our static WRITER: +// in src/vga_buffer.rs + +use spin::Mutex; +... +lazy_static! { + pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }); +} + + Now we can delete the print_something function and print directly from + our _start function: +// in src/main.rs +#[no_mangle] +pub extern "C" fn _start() -> ! { + use core::fmt::Write; + vga_buffer::WRITER.lock().write_str("Hello again").unwrap(); + write!(vga_buffer::WRITER.lock(), ", some numbers: {} {}", 42, 1.337).unwrap +(); + + loop {} +} + + We need to import the fmt::Write trait in order to be able to use its + functions. + +Safety + + Note that we only have a single unsafe block in our code, which is + needed to create a Buffer reference pointing to 0xb8000. Afterwards, + all operations are safe. Rust uses bounds checking for array accesses + by default, so we can't accidentally write outside the buffer. Thus, we + encoded the required conditions in the type system and are able to + provide a safe interface to the outside. + +A println Macro + + Now that we have a global writer, we can add a println macro that can + be used from anywhere in the codebase. Rust's [93]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 [94]println! macro in the standard library: +#[macro_export] +macro_rules! println { + () => (print!("\n")); + ($($arg:tt)*) => (print!("{}\n", format_args!($($arg)*))); +} + + Macros are defined through one or more rules, similar to match arms. + The println macro has two rules: The first rule is for invocations + without arguments, e.g., println!(), which is expanded to print!("\n") + and thus just prints a newline. The second rule is for invocations with + parameters such as println!("Hello") or println!("Number: {}", 4). It + is also expanded to an invocation of the print! macro, passing all + arguments and an additional newline \n at the end. + + The #[macro_export] attribute makes the macro available to the whole + crate (not just the module it is defined in) and external crates. It + also places the macro at the crate root, which means we have to import + the macro through use std::println instead of std::macros::println. + + The [95]print! macro is defined as: +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*))); +} + + The macro expands to a call of the [96]_print function in the io + module. The [97]$crate variable ensures that the macro also works from + outside the std crate by expanding to std when it's used in other + crates. + + The [98]format_args macro builds a [99]fmt::Arguments type from the + passed arguments, which is passed to _print. The [100]_print function + of libstd calls print_to, which is rather complicated because it + supports different Stdout devices. We don't need that complexity since + we just want to print to the VGA buffer. + + To print to the VGA buffer, we just copy the println! and print! + macros, but modify them to use our own _print function: +// in src/vga_buffer.rs + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + WRITER.lock().write_fmt(args).unwrap(); +} + + One thing that we changed from the original println definition is that + we prefixed the invocations of the print! macro with $crate too. This + ensures that we don't need to import the print! macro too if we only + want to use println. + + Like in the standard library, we add the #[macro_export] attribute to + both macros to make them available everywhere in our crate. Note that + this places the macros in the root namespace of the crate, so importing + them via use crate::vga_buffer::println does not work. Instead, we have + to do use crate::println. + + The _print function locks our static WRITER and calls the write_fmt + method on it. This method is from the Write trait, which we need to + import. 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. + + Since the macros need to be able to call _print from outside of the + module, the function needs to be public. However, since we consider + this a private implementation detail, we add the [101]doc(hidden) + attribute to hide it from the generated documentation. + +Hello World using println + + Now we can use println in our _start function: +// in src/main.rs + +#[no_mangle] +pub extern "C" fn _start() { + println!("Hello World{}", "!"); + + loop {} +} + + Note that we don't have to import the macro in the main function, + because it already lives in the root namespace. + + As expected, we now see a "Hello World!" on the screen: + + QEMU printing "Hello World!" + +Printing Panic Messages + + Now that we have a println macro, we can use it in our panic function + to print the panic message and the location of the panic: +// in main.rs + +/// This function is called on panic. +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + println!("{}", info); + loop {} +} + + When we now insert panic!("Some panic message"); in our _start + function, we get the following output: + + QEMU printing "panicked at `Some panic message', src/main.rs:28:5 + + So we know not only that a panic has occurred, but also the panic + message and where in the code it happened. + +Summary + + In this post, we learned about the structure of the VGA text buffer and + how it can be written through the memory mapping at address 0xb8000. We + created a Rust module that encapsulates the unsafety of writing to this + memory-mapped buffer and presents a safe and convenient interface to + the outside. + + Thanks to cargo, we also saw how easy it is to add dependencies on + third-party libraries. The two dependencies that we added, lazy_static + and spin, are very useful in OS development and we will use them in + more places in future posts. + +What's next? + + The next post explains how to set up Rust's built-in unit test + framework. We will then create some basic unit tests for the VGA buffer + module from this post. + +Support Me + + Creating and [102]maintaining this blog and the associated libraries is + a lot of work, but I really enjoy doing it. By supporting me, you allow + me to invest more time in new content, new features, and continuous + maintenance. + + The best way to support me is to [103]sponsor me on GitHub, since they + don't charge any fees. If you prefer other platforms, I also have + [104]Patreon and [105]Donorbox accounts. The latter is the most + flexible as it supports multiple currencies and one-time contributions. + + Thank you! + __________________________________________________________________ + + [106]« A Minimal Rust Kernel [107]Testing » + __________________________________________________________________ + +Comments + + Do you have a problem, want to share feedback, or discuss further + ideas? Feel free to leave a comment here! Please stick to English and + follow Rust's [108]code of conduct. This comment thread directly maps + to a [109]discussion on GitHub, so you can also comment there if you + prefer. + + Instead of authenticating the [110]giscus application, you can also + comment directly [111]on GitHub. + +Other Languages + + * [112]Chinese (simplified) + * [113]Japanese + * [114]Persian + * [115]Korean + __________________________________________________________________ + + © 2022. All rights reserved. [116]License [117]Contact + +References + + Visible links: + 1. https://os.phil-opp.com/rss.xml + 2. https://os.phil-opp.com/ + 3. https://os.phil-opp.com/ + 4. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer + 5. https://os.phil-opp.com/vga-text-mode/#a-rust-module + 6. https://os.phil-opp.com/vga-text-mode/#colors + 7. https://os.phil-opp.com/vga-text-mode/#text-buffer + 8. https://os.phil-opp.com/vga-text-mode/#printing + 9. https://os.phil-opp.com/vga-text-mode/#volatile + 10. https://os.phil-opp.com/vga-text-mode/#formatting-macros + 11. https://os.phil-opp.com/vga-text-mode/#newlines + 12. https://os.phil-opp.com/vga-text-mode/#a-global-interface + 13. https://os.phil-opp.com/vga-text-mode/#lazy-statics + 14. https://os.phil-opp.com/vga-text-mode/#spinlocks + 15. https://os.phil-opp.com/vga-text-mode/#safety + 16. https://os.phil-opp.com/vga-text-mode/#a-println-macro + 17. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println + 18. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages + 19. https://os.phil-opp.com/vga-text-mode/#summary + 20. https://os.phil-opp.com/vga-text-mode/#what-s-next + 21. https://os.phil-opp.com/vga-text-mode/#comments + 22. https://en.wikipedia.org/wiki/VGA-compatible_text_mode + 23. https://doc.rust-lang.org/std/fmt/#related-macros + 24. https://github.com/phil-opp/blog_os + 25. https://os.phil-opp.com/vga-text-mode/#comments + 26. https://github.com/phil-opp/blog_os/tree/post-03 + 27. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer + 28. https://os.phil-opp.com/vga-text-mode/#a-rust-module + 29. https://os.phil-opp.com/vga-text-mode/#colors + 30. https://os.phil-opp.com/vga-text-mode/#text-buffer + 31. https://os.phil-opp.com/vga-text-mode/#printing + 32. https://os.phil-opp.com/vga-text-mode/#volatile + 33. https://os.phil-opp.com/vga-text-mode/#formatting-macros + 34. https://os.phil-opp.com/vga-text-mode/#newlines + 35. https://os.phil-opp.com/vga-text-mode/#a-global-interface + 36. https://os.phil-opp.com/vga-text-mode/#lazy-statics + 37. https://os.phil-opp.com/vga-text-mode/#spinlocks + 38. https://os.phil-opp.com/vga-text-mode/#safety + 39. https://os.phil-opp.com/vga-text-mode/#a-println-macro + 40. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println + 41. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages + 42. https://os.phil-opp.com/vga-text-mode/#summary + 43. https://os.phil-opp.com/vga-text-mode/#what-s-next + 44. https://os.phil-opp.com/vga-text-mode/#comments + 45. https://en.wikipedia.org/wiki/ASCII + 46. https://en.wikipedia.org/wiki/Code_page_437 + 47. https://en.wikipedia.org/wiki/Memory-mapped_I/O + 48. https://web.stanford.edu/class/cs140/projects/pintos/specs/freevga/vga/vgamem.htm#manip + 49. https://doc.rust-lang.org/rust-by-example/custom_types/enum/c_like.html + 50. https://doc.rust-lang.org/rust-by-example/trait/derive.html + 51. https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html + 52. https://doc.rust-lang.org/nightly/core/clone/trait.Clone.html + 53. https://doc.rust-lang.org/nightly/core/fmt/trait.Debug.html + 54. https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html + 55. https://doc.rust-lang.org/nightly/core/cmp/trait.Eq.html + 56. https://doc.rust-lang.org/1.30.0/book/first-edition/ownership.html#copy-types + 57. https://doc.rust-lang.org/rust-by-example/generics/new_types.html + 58. https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent + 59. https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#reprc + 60. https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent + 61. https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotation-syntax + 62. https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime + 63. https://en.wikipedia.org/wiki/Newline + 64. https://en.wikipedia.org/wiki/Code_page_437 + 65. https://www.fileformat.info/info/unicode/utf8.htm + 66. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer + 67. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html + 68. https://doc.rust-lang.org/reference/tokens.html#byte-literals + 69. https://www.fileformat.info/info/unicode/utf8.htm + 70. https://en.wikipedia.org/wiki/Volatile_(computer_programming) + 71. https://docs.rs/volatile + 72. https://doc.rust-lang.org/nightly/core/ptr/fn.read_volatile.html + 73. https://doc.rust-lang.org/nightly/core/ptr/fn.write_volatile.html + 74. https://semver.org/ + 75. https://doc.crates.io/specifying-dependencies.html + 76. https://doc.rust-lang.org/book/ch10-01-syntax.html + 77. https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html + 78. https://doc.rust-lang.org/core/result/enum.Result.html#method.unwrap + 79. https://rustc-dev-guide.rust-lang.org/const-eval.html + 80. https://github.com/rust-lang/rfcs/pull/2345 + 81. https://doc.rust-lang.org/reference/const_eval.html#const-functions + 82. https://docs.rs/lazy_static/1.0.1/lazy_static/ + 83. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable + 84. https://internals.rust-lang.org/t/pre-rfc-remove-static-mut/1437 + 85. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html#keeping-track-of-borrows-at-runtime-with-refcellt + 86. https://doc.rust-lang.org/nightly/core/cell/struct.UnsafeCell.html + 87. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html + 88. https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html + 89. https://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html + 90. https://en.wikipedia.org/wiki/Spinlock + 91. https://crates.io/crates/spin + 92. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html + 93. https://doc.rust-lang.org/nightly/book/ch19-06-macros.html#declarative-macros-with-macro_rules-for-general-metaprogramming + 94. https://doc.rust-lang.org/nightly/std/macro.println!.html + 95. https://doc.rust-lang.org/nightly/std/macro.print!.html + 96. https://github.com/rust-lang/rust/blob/29f5c699b11a6a148f097f82eaa05202f8799bbc/src/libstd/io/stdio.rs#L698 + 97. https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html#the-variable-crate + 98. https://doc.rust-lang.org/nightly/std/macro.format_args.html + 99. https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html + 100. https://github.com/rust-lang/rust/blob/29f5c699b11a6a148f097f82eaa05202f8799bbc/src/libstd/io/stdio.rs#L698 + 101. https://doc.rust-lang.org/nightly/rustdoc/write-documentation/the-doc-attribute.html#hidden + 102. https://os.phil-opp.com/status-update/ + 103. https://github.com/sponsors/phil-opp + 104. https://www.patreon.com/phil_opp + 105. https://donorbox.org/phil-opp + 106. https://os.phil-opp.com/minimal-rust-kernel/ + 107. https://os.phil-opp.com/testing/ + 108. https://www.rust-lang.org/policies/code-of-conduct + 109. https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22VGA%20Text%20Mode%22%20in%3Atitle + 110. https://giscus.app/ + 111. https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22VGA%20Text%20Mode%22%20in%3Atitle + 112. https://os.phil-opp.com/zh-CN/vga-text-mode/ + 113. https://os.phil-opp.com/ja/vga-text-mode/ + 114. https://os.phil-opp.com/fa/vga-text-mode/ + 115. https://os.phil-opp.com/ko/vga-text-mode/ + 116. https://github.com/phil-opp/blog_os#license + 117. https://os.phil-opp.com/contact/ + + Hidden links: + 119. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer + 120. https://os.phil-opp.com/vga-text-mode/#a-rust-module + 121. https://os.phil-opp.com/vga-text-mode/#colors + 122. https://os.phil-opp.com/vga-text-mode/#text-buffer + 123. https://os.phil-opp.com/vga-text-mode/#printing + 124. https://os.phil-opp.com/vga-text-mode/#try-it-out + 125. https://os.phil-opp.com/vga-text-mode/#volatile + 126. https://os.phil-opp.com/vga-text-mode/#formatting-macros + 127. https://os.phil-opp.com/vga-text-mode/#newlines + 128. https://os.phil-opp.com/vga-text-mode/#a-global-interface + 129. https://os.phil-opp.com/vga-text-mode/#lazy-statics + 130. https://os.phil-opp.com/vga-text-mode/#spinlocks + 131. https://os.phil-opp.com/vga-text-mode/#safety + 132. https://os.phil-opp.com/vga-text-mode/#a-println-macro + 133. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println + 134. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages + 135. https://os.phil-opp.com/vga-text-mode/#summary + 136. https://os.phil-opp.com/vga-text-mode/#what-s-next |