From 9d32d49488abfe30f4b8cce7ec92f1fc8a841e56 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 5 Oct 2017 17:01:09 +0200 Subject: [PATCH 01/29] Begin new post about freestanding executables --- blog/content/second-edition/_index.md | 4 + .../posts/01-introduction/index.md | 11 + .../second-edition/posts/02-booting/index.md | 21 + .../03-freestanding-rust-binary/index.md | 399 ++++++++++++++++++ blog/content/second-edition/posts/_index.md | 6 + 5 files changed, 441 insertions(+) create mode 100644 blog/content/second-edition/_index.md create mode 100644 blog/content/second-edition/posts/01-introduction/index.md create mode 100644 blog/content/second-edition/posts/02-booting/index.md create mode 100644 blog/content/second-edition/posts/03-freestanding-rust-binary/index.md create mode 100644 blog/content/second-edition/posts/_index.md diff --git a/blog/content/second-edition/_index.md b/blog/content/second-edition/_index.md new file mode 100644 index 00000000..5a157eb1 --- /dev/null +++ b/blog/content/second-edition/_index.md @@ -0,0 +1,4 @@ ++++ +title = "Second Edition" +template = "second-edition/index.html" ++++ diff --git a/blog/content/second-edition/posts/01-introduction/index.md b/blog/content/second-edition/posts/01-introduction/index.md new file mode 100644 index 00000000..0cda4303 --- /dev/null +++ b/blog/content/second-edition/posts/01-introduction/index.md @@ -0,0 +1,11 @@ ++++ +title = "Introduction" +order = 1 +path = "introduction" +date = "0000-01-01" +template = "second-edition/page.html" ++++ + +Needed? + + diff --git a/blog/content/second-edition/posts/02-booting/index.md b/blog/content/second-edition/posts/02-booting/index.md new file mode 100644 index 00000000..972383b5 --- /dev/null +++ b/blog/content/second-edition/posts/02-booting/index.md @@ -0,0 +1,21 @@ ++++ +title = "Booting" +order = 2 +path = "booting" +date = "0000-01-01" +template = "second-edition/page.html" ++++ + +This post describes the boot process of an x86_64 machine. It presents different ways to boot an operating system and describes the necessary CPU initialization steps. It also provides an overview over the BIOS and UEFI systems and the Multiboot standard. + + + +## The Boot Process +The + + +## BIOS + +### Multiboot + +## UEFI diff --git a/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md b/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md new file mode 100644 index 00000000..a8b33a3a --- /dev/null +++ b/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md @@ -0,0 +1,399 @@ ++++ +title = "A Freestanding Rust Binary" +order = 3 +path = "freestanding-rust-binary" +date = "0000-01-01" +template = "second-edition/page.html" ++++ + +This post describes how to create a Rust executable that does not link the standard library. This makes it possible to run Rust code on the [bare metal] without an underlying operating system. + +[bare metal]: https://en.wikipedia.org/wiki/Bare_machine + + + +TODO github, issues, comments, etc + +## Introduction +To write an operating system kernel, we need code that does not depend on any operating system features. This means that we can't use threads, files, heap memory, the network, random numbers, standard output, or any other features requiring OS abstractions or specific hardware. Which makes sense, since we're trying to write our own OS and our own drivers. + +This means that we can't use most of the [Rust standard library], but there are a lot of Rust features that we _can_ use. For example, we can use [iterators], [closures], [pattern matching], [option] and [result], [string formatting], and of course the [ownership system]. These features make it possible to write a kernel in a very expressive, high level way without worrying about [undefined behavior] or [memory safety]. + +[option]: https://doc.rust-lang.org/core/option/ +[result]:https://doc.rust-lang.org/core/result/ +[Rust standard library]: https://doc.rust-lang.org/std/ +[iterators]: https://doc.rust-lang.org/book/second-edition/ch13-02-iterators.html +[closures]: https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html +[pattern matching]: https://doc.rust-lang.org/book/second-edition/ch06-00-enums.html +[string formatting]: https://doc.rust-lang.org/core/macro.write.html +[ownership system]: https://doc.rust-lang.org/book/second-edition/ch04-00-understanding-ownership.html +[undefined behavior]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs +[memory safety]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention + +In order to create an OS kernel in Rust, we need to create an executable that can be run without an underlying operating system. Such an executable is often called a “freestanding” or “bare-metal” executable. + +This post describes the necessary steps to get a freestanding Rust binary and explains why the steps are needed. If you're just interested in a minimal example, you can **[jump to the summary](#summary)**. + +## Disabling the Standard Library +By default, all Rust crates link the [standard library], which dependends on the operating system for features such as threads, files, or networking. It also depends on the C standard library `libc`, which closely interacts with OS services. Since our plan is to write an operating system, we can not use any OS-dependent libraries. So we have to disable the automatic inclusion of the standard library through the [`no_std` attribute]. + +[standard library]: https://doc.rust-lang.org/std/ +[`no_std` attribute]: https://doc.rust-lang.org/book/first-edition/using-rust-without-the-standard-library.html + +We start by creating a new cargo application project. The easiest way to do this is through the command line: + +``` +> cargo new blog_os --bin +``` + +I named the project `blog_os`, but of course you can choose your own name. The `--bin` flag specifies that we want to create an executable binary (in contrast to a library). When we run the command, cargo creates the following directory structure for us: + +``` +blog_os +├── Cargo.toml +└── src + └── main.rs +``` + +The `Cargo.toml` contains the crate configuration, for example the crate name, the author, the [semantic version] number, and dependencies. The `src/main.rs` file contains the root module of our crate and our `main` function. You can compile your crate through `cargo build` and then run the compiled `blog_os` binary in the `target/debug` subfolder. + +[semantic version]: http://semver.org/ + +### The `no_std` Attribute + +Right now our crate implicitly links the standard library. Let's try to disable this by adding the [`no_std` attribute]: + +```rust +// main.rs + +#![no_std] + +fn main() { + println!("Hello, world!"); +} +``` + +When we try to build it now (by running `cargo build`), the following error occurs: + +``` +error: cannot find macro `println!` in this scope + --> src/main.rs:4:5 + | +4 | println!("Hello, world!"); + | ^^^^^^^ +``` + +The reason for this error is that the [`println` macro] is part of the standard library, which we no longer include. So we can no longer print things. This makes sense, since `println` writes to [standard output], which is a special file descriptor provided by the operating system. + +[`println` macro]: https://doc.rust-lang.org/std/macro.println.html +[standard output]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29 + +So let's remove the printing and try again with an empty main function: + +```rust +// main.rs + +#![no_std] + +fn main() {} +``` + +``` +> cargo build +error: language item required, but not found: `panic_fmt` +error: language item required, but not found: `eh_personality` +``` + +Now the compiler is missing some _language items_. Language items are special pluggable functions that the compiler invokes on certain conditions, for example when the application [panics]. Normally, these items are provided by the standard library, but we disabled it. So we need to provide our own implementations. + +[panics]: https://doc.rust-lang.org/stable/book/second-edition/ch09-01-unrecoverable-errors-with-panic.html + +### Enabling Unstable Features + +Implementing language items is unstable and protected by a so-called _feature gate_. A feature gate is a special attribute that you have to specify at the top of your `main.rs` in order to use the corresponding feature. By doing this you basically say: “I know that this feature is unstable and that it might stop working without warning. I want to use it anyway.” + +To limit the use of unstable features, the feature gates are not available in the stable or beta Rust compilers, only [nightly Rust] makes it possible to opt-in. This means that you have to use a nightly compiler for OS development for the near future (since we need to implement unstable language items). To install a nightly compiler using [rustup], you just need to run `rustup default nightly` (for more information check out [rustup's documentation]). + +[nightly Rust]: https://doc.rust-lang.org/book/first-edition/release-channels.html +[rustup]: https://rustup.rs/ +[rustup's documentation]: https://github.com/rust-lang-nursery/rustup.rs#rustup-the-rust-toolchain-installer + +After installing a nightly Rust compiler, you can enable the unstable `lang_items` feature by inserting `#![feature(lang_items)]` right at the top of `main.rs`. + +### Implementing the Language Items + +To create a `no_std` binary, we have to implement the `panic_fmt` and the `eh_personality` language items. The `panic_fmt` items specifies a function that should be invoked when a panic occurs. This function should format an error message (hence the `_fmt` suffix) and then invoke the panic routine. In our case, there is not much we can do, since we can neither print anything nor do we have a panic routine. So we just loop indefinitely: + +```rust +#![feature(lang_items)] +#![no_std] + +fn main() {} + +#[lang = "panic_fmt"] +#[no_mangle] +pub extern fn rust_begin_panic(_msg: core::fmt::Arguments, + _file: &'static str, + _line: u32, + _column: u32) -> ! { + loop {} +} +``` + +The function signature is taken from the [unstable Rust book]. The signature isn't verified by the compiler, so implement it carefully. TODO: https://github.com/rust-lang/rust/issues/44489 + +[unstable Rust book]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib + +Instead of implementing the second language item, `eh_personality`, we remove the need for it by disabling unwinding. + +### Disabling Unwinding + +The `eh_personality` language item is used for implementing [stack unwinding]. By default, Rust uses unwinding to run the destructors of all live stack variables in case of panic. This ensures that all used memory is freed and allows the parent thread to catch the panic and continue execution. Unwinding, however, is a complicated process and requires some OS specific libraries (e.g. [libunwind] on Linux or [structured exception handling] on Windows), so we don't want to use it for our operating system. + +[stack unwinding]: http://www.bogotobogo.com/cplusplus/stackunwinding.php +[libunwind]: http://www.nongnu.org/libunwind/ +[structured exception handling]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680657(v=vs.85).aspx + +There are other use cases as well for which unwinding is undesireable, so Rust provides an option to [abort on panic] instead. This disables the generation of unwinding symbol and thus considerably reduces binary size. There are multiple places where we can disable unwinding. The easiest way is to add the following lines to our `Cargo.toml`: + +```toml +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +``` + +This sets the panic strategy to `abort` for both the `dev` profile (used for `cargo build`) and the `release` profile (used for `cargo build --release`). Now the `eh_personality` language item should no longer be required. + +[abort on panic]: https://github.com/rust-lang/rust/pull/32900 + +However, if we try to compile it now, another language item is required: + +``` +> cargo build +error: requires `start` lang_item +``` + +### The `start` attribute + +One might think that the `main` function is the first function called when you run a program. However, most languages have a [runtime system], which is responsible for things such as garbage collection (e.g. in Java) or software threads (e.g. goroutines in Go). This runtime needs to be called before `main`, since it needs to initialize itself. + +[runtime system]: https://en.wikipedia.org/wiki/Runtime_system + +In a typical Rust binary that links the standard library, execution starts in a C runtime library called `crt0` (“C runtime zero”), which sets up the environment for a C application. This includes creating a stack and placing the arguments in the right registers. The C runtime then invokes the [entry point of the Rust runtime][rt::lang_start], which is marked by the `start` language item. Rust only has a very minimal runtime, which takes care of some small things such as setting up stack overflow guards or printing a backtrace on panic. The runtime then finally calls the `main` function. + +[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73 + +Our freestanding executable does not have access to the standard library and the Rust runtime, so we need to define the `start` language item ourselves: + +```rust +#[lang = "start"] +fn lang_start(main: fn(), argc: isize, argv: *const *const u8) -> isize { 1 } +``` + +The signature is copied [from the standard library][rt::lang_start]. We should probably call `main` from this function at some point, but for now we just try to get it to compile. + +TODO #[start] + +### `-nostartfiles` +If we try to build it now, an ugly linker error occurs: + +``` +error: linking with `cc` failed: exit code: 1 + | + = note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-m64" "-L" + "/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" + "/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09.0.o" […] + "-o" "/…/blog_os/target/debug/deps/blog_os-f7d4ca7f1e3c3a09" + "-Wl,--gc-sections" "-pie" "-Wl,-z,relro,-z,now" "-nodefaultlibs" + "-L" "/…/blog_os/target/debug/deps" + "-L" "/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" + "-Wl,-Bstatic" + "/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-dd5bba80e2402629.rlib" + "-Wl,-Bdynamic" + = note: /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start': + (.text+0x12): undefined reference to `__libc_csu_fini' + /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start': + (.text+0x19): undefined reference to `__libc_csu_init' + /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/Scrt1.o: In function `_start': + (.text+0x25): undefined reference to `__libc_start_main' + collect2: error: ld returned 1 exit status + +``` + +The problem is that we still link the startup routine of the C runtime, which requires some symbols of the C standard library `libc`, which we don't link due to the `no_std` attribute. So we need to get rid of the C startup routine. We can do that by passing the `-nostartfiles` flag to the linker. + +One way to pass linker attributes via cargo is the `cargo rustc` command. The command behaves exactly like `cargo build`, but allows to pass options to `rustc`, the underlying Rust compiler. `rustc` has the (unstable) `-Z pre-link-arg` flag, which passes an argument to the linker. Combined, our new build command looks like this: + +``` +> cargo rustc -- -Z pre-link-arg=-nostartfiles +``` + +With this command, our crate builds again. However, it won't work in its current state. + +## Setting the Entry Point + +TODO: use elfkit instead of objdump? https://github.com/aep/elfkit + +Let's take a look at the compiled binary using the [`objdump` tool]: + +[`objdump` tool]: http://sourceware.org/binutils/docs/binutils/objdump.html + +``` +> objdump --disassemble target/debug/blog_os + +target/debug/blog_os_test: file format elf64-x86-64 +``` + +The `--disassemble` flag should output all the assembly instructions in our binary, but the output is empty. So our binary does not contain any code. That's bad. Let's look at the file headers of the [ELF] binary to see what's wrong: + +[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format + +``` +> objdump --file-headers target/debug/blog_os + +target/debug/blog_os: file format elf64-x86-64 +architecture: i386:x86-64, flags 0x00000150: +HAS_SYMS, DYNAMIC, D_PAGED +start address 0x0000000000000000 +``` + +The start address should point to the entry point, but it is zero. So what's going wrong here? To understand what's happening, we have to look at the three layers of entry points again: + +- We saw above that the `main` function is not the real entry point of a Rust binary. In fact, the compiler [mangles its name][mangling] to something like `_ZN3blog_os4main17hb173fedf945531caE`. +- The `start` language item (which can be overridden by the `start` attribute), defines the Rust-level entry point and normally points to the Rust runtime. The compiler generates a C-compatible, unmangled `main` function for this entry point. +- The C runtime defines the real entry point for the binary through a function named `_start`. The linker looks for a function of this name for setting the entry point in the [ELF] binary. + +[mangling]: https://en.wikipedia.org/wiki/Name_mangling + +Each layer is invoked by the layer below. So the `start` language item is called by the C runtime (which thinks that it's calling a normal C `main` function) and the `main` function is called by the Rust runtime. The problem is that we removed the lowest layer through the `-nostartfiles` linker argument, so no `_start` symbol exist anymore. + +### A `_start` Function + +The solution to this problem is to define our own `_start` function: + +```rust +#[no_mangle] +pub fn _start() -> ! { + loop {} +} +``` + +It's important that we disable the [name mangling][mangling] through the `no_mangle` attribute, otherwise the compiler would generate some cryptic `_ZN3blog_os4_start7hb173fedf945531caE` symbol that the linker wouldn't recognize. + +The `!` return type means that the function is diverging, i.e. not allowed to ever return. This is required because the entry point is not called by any function, but invoked directly by the operating system or bootloader. So instead of returning, the entry point should e.g. invoke the [`exit` system call] of the operating system. In our case, shutting down the machine could be a reasonable action, since there's nothing left to do if a freestanding binary returns. For now, we fulfil the requirement by looping endlessly. + +[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call) + +If we build our crate now (by running `cargo rustc -- -Z pre-link-arg=-nostartfiles`), the resulting binary has a valid entry point and contains code: + +``` +> objdump -fd target/debug/blog_os + +target/debug/blog_os: file format elf64-x86-64 +architecture: i386:x86-64, flags 0x00000150: +HAS_SYMS, DYNAMIC, D_PAGED +start address 0x0000000000000340 + + +Disassembly of section .text: + +0000000000000340 <_start>: + 340: 55 push %rbp + 341: 48 89 e5 mov %rsp,%rbp + 344: eb 00 jmp 346 <_start+0x6> + 346: eb fe jmp 346 <_start+0x6> +``` + +The `-fd` argument to `objdump` is a combination of the short forms of the `--file-headers` (`-f`) and `--disassemble` (`-d`) arguments we saw above. + +We see that our binary contains very few instructions, which is expected since our `_start` function is just an endless loop. The `jmp` instruction at address `346`, which jumps to itself forever, is the translation of this endless loop. The other instructions are just there because the build is unoptimized. If we compile with optimizations using the `--release` flag (`cargo rustc --release -- -Z pre-link-arg=-nostartfiles`), only one `jmp` instruction is left: + +``` +> objdump -d target/release/blog_os_test + +target/release/blog_os_test: file format elf64-x86-64 + + +Disassembly of section .text: + +0000000000000310 <_start>: + 310: eb fe jmp 310 <_start> +``` + +### Cleaning up: `no_main` + +Our `main.rs` now contains multiple entry points, but only the `_start` entry point is actually used. To remove the unused entry points (`main` and `start`), we can use the `no_main` attribute. This attribute disables the Rust-level entry points, so we can remove the `main` function and the `start` language item completely: + +```rust +#![feature(lang_items)] +#![no_std] +#![no_main] + +#[no_mangle] +pub fn _start() -> ! { + loop {} +} + +// [panic_fmt language item] +``` + +## Summary + +A minimal freestanding Rust binary looks like this: + +`src/main.rs`: + +```rust +#![feature(lang_items)] // required for defining the panic handler +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +#[no_mangle] // don't mangle the name of this function +pub fn _start() -> ! { + // this function is the entry point, since the linker looks for a function + // named `_start_` by default + loop {} +} + +#[lang = "panic_fmt"] // define a function that should be called on panic +#[no_mangle] // TODO required? +pub extern fn rust_begin_panic(_msg: core::fmt::Arguments, + _file: &'static str, + _line: u32, + _column: u32) -> ! { + loop {} +} +``` + +`Cargo.toml`: + +```toml +[package] +name = "crate_name" +version = "0.1.0" +authors = ["Author Name "] + +# the profile used for `cargo build` +[profile.dev] +panic = "abort" # disable stack unwinding on panic + +# the profile used for `cargo build --release` +[profile.release] +panic = "abort" # disable stack unwinding on panic +``` + +It can be compiled with: + +``` +cargo rustc -- -Z pre-link-arg=-nostartfiles +``` + +Note that this is just a minimal example of a freestanding Rust binary. This binary expects various things, for example that a stack is initialized when the `_start` function is called. **So for any real use of such a binary, more steps are required**. + +## What's next? + +The [next post] build upon our minimal freestanding binary by explaining the steps needed for creating a minimal operating system kernel. It explains how to configure the kernel for the target system, how to start it using a bootloader, and how to print something to the screen. + +[next post]: TODO diff --git a/blog/content/second-edition/posts/_index.md b/blog/content/second-edition/posts/_index.md new file mode 100644 index 00000000..bd698bdf --- /dev/null +++ b/blog/content/second-edition/posts/_index.md @@ -0,0 +1,6 @@ ++++ +title = "Posts" +sort_by = "order" +insert_anchor_links = "left" +render = false ++++ From 75b4e09c4e1d22cd3221642c7c028eadcef61d0e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 10 Oct 2017 12:29:10 +0200 Subject: [PATCH 02/29] Make page title an h1 for semantics --- blog/static/css/main.css | 12 ++++++++++-- blog/static/css/poole.css | 2 +- blog/templates/base.html | 6 +++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/blog/static/css/main.css b/blog/static/css/main.css index b04e43d8..a4cade58 100644 --- a/blog/static/css/main.css +++ b/blog/static/css/main.css @@ -1,7 +1,15 @@ +.masthead-title { + font-size: 1.25rem; + display: inline; +} -.masthead-title small { +.masthead p { + font-size: 1.25rem; + display: inline; + margin: 0; margin-left: 1rem; - white-space: nowrap; + padding: 0; + line-height: 1; } .front-page-introduction { diff --git a/blog/static/css/poole.css b/blog/static/css/poole.css index e8572304..2efff964 100644 --- a/blog/static/css/poole.css +++ b/blog/static/css/poole.css @@ -285,7 +285,7 @@ tbody tr:nth-child(odd) th { .masthead-title a { color: #505050; } -.masthead-title small { +.masthead small { font-size: 75%; font-weight: 400; color: #c0c0c0; diff --git a/blog/templates/base.html b/blog/templates/base.html index 155069b2..bc28fdc2 100644 --- a/blog/templates/base.html +++ b/blog/templates/base.html @@ -20,10 +20,10 @@
-

+

{{ config.title }} - {{ config.extra.subtitle }} -

+ +

{{ config.extra.subtitle }}

{% block main %}{% endblock main %}
From 7a263355f34276e18a35e8c65613a314e48c5b83 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 10 Oct 2017 12:34:10 +0200 Subject: [PATCH 03/29] Add custom templates for second edition posts --- blog/static/css/main.css | 8 ++++ blog/templates/second-edition/base.html | 59 ++++++++++++++++++++++++ blog/templates/second-edition/index.html | 38 +++++++++++++++ blog/templates/second-edition/page.html | 51 ++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 blog/templates/second-edition/base.html create mode 100644 blog/templates/second-edition/index.html create mode 100644 blog/templates/second-edition/page.html diff --git a/blog/static/css/main.css b/blog/static/css/main.css index a4cade58..e928c375 100644 --- a/blog/static/css/main.css +++ b/blog/static/css/main.css @@ -228,3 +228,11 @@ a.gutenberg-anchor { a.gutenberg-anchor:hover { text-decoration: none; } + +div.warning { + padding: .7rem 1rem; + margin: 1rem .2rem; + border: 2px solid orange; + border-radius: 5px; + background-color: #ffa50022; +} diff --git a/blog/templates/second-edition/base.html b/blog/templates/second-edition/base.html new file mode 100644 index 00000000..bc28fdc2 --- /dev/null +++ b/blog/templates/second-edition/base.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + {% block title %}{% endblock title %} + + + +
+
+

+ {{ config.title }} +

+

{{ config.extra.subtitle }}

+
+ +
{% block main %}{% endblock main %}
+ +
{% block after_main %}{% endblock after_main %}
+ +
+
+ + © . All rights reserved. + Contact + +
+
+ + + + + + + diff --git a/blog/templates/second-edition/index.html b/blog/templates/second-edition/index.html new file mode 100644 index 00000000..97c1fe37 --- /dev/null +++ b/blog/templates/second-edition/index.html @@ -0,0 +1,38 @@ +{% extends "second-edition/base.html" %} + +{% import "macros.html" as macros %} + +{% block title %}{{ config.title }}{% endblock title %} + +{% block main %} +{% set posts_section = get_section(path = "second-edition/posts/_index.md") %} +{% set posts = posts_section.pages | reverse %} +
+ Note: You are viewing the upcoming second edition of “Writing an OS in Rust”, which is still under construction. To read the first edition, go here. +
+
+

+ This blog series creates a small operating system in the + Rust programming language. Each post is a small tutorial and includes all + needed code, so you can follow along if you like. The source code is also available in the corresponding + Github repository. +

+

Latest post: + {% set latest_post = posts|last %} + {{ latest_post.title }} +

+
+ + +
+ {{ macros::post_link(page=posts.0) }} + {{ macros::post_link(page=posts.1) }} + {{ macros::post_link(page=posts.2) }} +
+ + + +{% endblock main %} diff --git a/blog/templates/second-edition/page.html b/blog/templates/second-edition/page.html new file mode 100644 index 00000000..cfd0dbec --- /dev/null +++ b/blog/templates/second-edition/page.html @@ -0,0 +1,51 @@ +{% extends "second-edition/base.html" %} + +{% block title %}{{ page.title }} | {{ config.title }}{% endblock title %} + +{% block main %} +

{{ page.title }}

+ + +
+ Note: This post is part of the upcoming second edition of “Writing an OS in Rust”, which is still under construction. To read the first edition, go here. +
+ {{ page.content | safe }} +{% endblock main %} + +{% block after_main %} +
+ + +
+ +
+

Comments

+
+
+ +{% endblock after_main %} + + From 25441a8773dd8fb4d9d18acea679fa26ab9c58e0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 19 Jan 2018 14:20:20 +0100 Subject: [PATCH 04/29] Use absolute `path`s instead of `permalink` This makes links also work in deploy previews. --- blog/templates/index.html | 6 +++--- blog/templates/macros.html | 2 +- blog/templates/page.html | 4 ++-- blog/templates/second-edition/index.html | 2 +- blog/templates/second-edition/page.html | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/blog/templates/index.html b/blog/templates/index.html index 547de502..b041e8a8 100644 --- a/blog/templates/index.html +++ b/blog/templates/index.html @@ -16,7 +16,7 @@

Latest post: {% set latest_post = posts|last %} - {{ latest_post.title }} + {{ latest_post.title }}

@@ -47,10 +47,10 @@

{{ extra.title }}

diff --git a/blog/templates/macros.html b/blog/templates/macros.html index aa62e7fe..a1a8b05a 100644 --- a/blog/templates/macros.html +++ b/blog/templates/macros.html @@ -1,6 +1,6 @@ {% macro post_link(page) %} {% endmacro post_link %} diff --git a/blog/templates/page.html b/blog/templates/page.html index 463e937d..66b6d9cf 100644 --- a/blog/templates/page.html +++ b/blog/templates/page.html @@ -28,10 +28,10 @@
diff --git a/blog/templates/second-edition/index.html b/blog/templates/second-edition/index.html index 97c1fe37..63c1e9fc 100644 --- a/blog/templates/second-edition/index.html +++ b/blog/templates/second-edition/index.html @@ -19,7 +19,7 @@

Latest post: {% set latest_post = posts|last %} - {{ latest_post.title }} + {{ latest_post.title }}

diff --git a/blog/templates/second-edition/page.html b/blog/templates/second-edition/page.html index cfd0dbec..c89d5b0b 100644 --- a/blog/templates/second-edition/page.html +++ b/blog/templates/second-edition/page.html @@ -31,10 +31,10 @@
From d38e97ee0b168aafc0f790b9436ec86e8af2abc2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 19 Jan 2018 14:33:49 +0100 Subject: [PATCH 05/29] Link to #id instead of permalink in TOC This makes table of content links also work in deploy previews. --- blog/templates/page.html | 4 ++-- blog/templates/second-edition/page.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/blog/templates/page.html b/blog/templates/page.html index 66b6d9cf..8a65cfe7 100644 --- a/blog/templates/page.html +++ b/blog/templates/page.html @@ -12,10 +12,10 @@

Table of Contents

    {% for h2 in page.toc %}
  1. - {{ h2.title | safe }} + {{ h2.title | safe }} {% if h2.children %}
      {% for h3 in h2.children %}
    1. - {{ h3.title | safe }} + {{ h3.title | safe }}
    2. {% endfor %}
    {% endif %}
  2. {% endfor %} diff --git a/blog/templates/second-edition/page.html b/blog/templates/second-edition/page.html index c89d5b0b..bd29134a 100644 --- a/blog/templates/second-edition/page.html +++ b/blog/templates/second-edition/page.html @@ -12,10 +12,10 @@

    Table of Contents

      {% for h2 in page.toc %}
    1. - {{ h2.title | safe }} + {{ h2.title | safe }} {% if h2.children %}
        {% for h3 in h2.children %}
      1. - {{ h3.title | safe }} + {{ h3.title | safe }}
      2. {% endfor %}
      {% endif %}
    2. {% endfor %} From abc9f350d2674036e21bdafee2464984dbc1bb25 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 19 Jan 2018 14:49:40 +0100 Subject: [PATCH 06/29] Use utterances for comments on second edition posts --- blog/templates/second-edition/page.html | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/blog/templates/second-edition/page.html b/blog/templates/second-edition/page.html index bd29134a..e7b8fb7a 100644 --- a/blog/templates/second-edition/page.html +++ b/blog/templates/second-edition/page.html @@ -39,11 +39,14 @@
      -

      Comments

      -
      +
      {% endblock after_main %} From 7f3de8dcb450b966d4e0932dedc1da181c750c0f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Jan 2018 20:01:35 +0100 Subject: [PATCH 07/29] Update to gutenberg 0.3.0 --- .../extra/naked-exceptions/01-catching-exceptions/index.md | 2 +- .../naked-exceptions/02-better-exception-messages/index.md | 2 +- .../naked-exceptions/03-returning-from-exceptions/index.md | 2 +- blog/content/posts/01-multiboot-kernel/index.md | 2 +- blog/content/posts/02-entering-longmode/index.md | 2 +- blog/content/posts/03-set-up-rust/index.md | 2 +- blog/content/posts/04-printing-to-screen/index.md | 2 +- blog/content/posts/05-allocating-frames/index.md | 2 +- blog/content/posts/06-page-tables/index.md | 2 +- blog/content/posts/07-remap-the-kernel/index.md | 2 +- blog/content/posts/08-kernel-heap/index.md | 2 +- blog/content/posts/09-handling-exceptions/index.md | 2 +- blog/content/posts/10-double-faults/index.md | 2 +- blog/content/second-edition/posts/01-introduction/index.md | 2 +- blog/content/second-edition/posts/02-booting/index.md | 2 +- .../second-edition/posts/03-freestanding-rust-binary/index.md | 2 +- blog/templates/base.html | 2 +- blog/templates/second-edition/base.html | 4 ++-- 18 files changed, 19 insertions(+), 19 deletions(-) diff --git a/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md b/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md index 818fa085..ab6456f9 100644 --- a/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md +++ b/blog/content/extra/naked-exceptions/01-catching-exceptions/index.md @@ -2,7 +2,7 @@ title = "Catching Exceptions" order = 1 path = "catching-exceptions" -date = "2016-05-28" +date = 2016-05-28 [extra] updated = "2016-06-25" +++ diff --git a/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md b/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md index 286bb078..a221bb9a 100644 --- a/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md +++ b/blog/content/extra/naked-exceptions/02-better-exception-messages/index.md @@ -2,7 +2,7 @@ title = "Better Exception Messages" order = 2 path = "better-exception-messages" -date = "2016-08-03" +date = 2016-08-03 [extra] updated = "2016-11-01" +++ diff --git a/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md b/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md index 36ef6d09..88e1470b 100644 --- a/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md +++ b/blog/content/extra/naked-exceptions/03-returning-from-exceptions/index.md @@ -2,7 +2,7 @@ title = "Returning from Exceptions" order = 3 path = "returning-from-exceptions" -date = "2016-09-21" +date = 2016-09-21 [extra] updated = "2016-11-01" +++ diff --git a/blog/content/posts/01-multiboot-kernel/index.md b/blog/content/posts/01-multiboot-kernel/index.md index f0c43335..560350db 100644 --- a/blog/content/posts/01-multiboot-kernel/index.md +++ b/blog/content/posts/01-multiboot-kernel/index.md @@ -2,7 +2,7 @@ title = "A minimal x86 kernel" order = 1 path = "multiboot-kernel/" -date = "2015-08-18" +date = 2015-08-18 +++ This post explains how to create a minimal x86 operating system kernel. In fact, it will just boot and print `OK` to the screen. In subsequent blog posts we will extend it using the [Rust] programming language. diff --git a/blog/content/posts/02-entering-longmode/index.md b/blog/content/posts/02-entering-longmode/index.md index 5e6cedd9..01dcb1d9 100644 --- a/blog/content/posts/02-entering-longmode/index.md +++ b/blog/content/posts/02-entering-longmode/index.md @@ -2,7 +2,7 @@ title = "Entering Long Mode" order = 2 path = "entering-longmode" -date = "2015-08-25" +date = 2015-08-25 [extra] updated = "2015-10-29" +++ diff --git a/blog/content/posts/03-set-up-rust/index.md b/blog/content/posts/03-set-up-rust/index.md index f6320da7..d4f46cff 100644 --- a/blog/content/posts/03-set-up-rust/index.md +++ b/blog/content/posts/03-set-up-rust/index.md @@ -2,7 +2,7 @@ title = "Set Up Rust" order = 3 path = "set-up-rust" -date = "2015-09-02" +date = 2015-09-02 [extra] updated = "2017-04-12" +++ diff --git a/blog/content/posts/04-printing-to-screen/index.md b/blog/content/posts/04-printing-to-screen/index.md index b0aaa680..5aa973e8 100644 --- a/blog/content/posts/04-printing-to-screen/index.md +++ b/blog/content/posts/04-printing-to-screen/index.md @@ -2,7 +2,7 @@ title = "Printing to Screen" order = 4 path = "printing-to-screen" -date = "2015-10-23" +date = 2015-10-23 [extra] updated = "2016-10-31" +++ diff --git a/blog/content/posts/05-allocating-frames/index.md b/blog/content/posts/05-allocating-frames/index.md index 8a26f04c..da675ee8 100644 --- a/blog/content/posts/05-allocating-frames/index.md +++ b/blog/content/posts/05-allocating-frames/index.md @@ -2,7 +2,7 @@ title = "Allocating Frames" order = 5 path = "allocating-frames" -date = "2015-11-15" +date = 2015-11-15 +++ In this post we create an allocator that provides free physical frames for a future paging module. To get the required information about available and used memory we use the Multiboot information structure. Additionally, we improve the `panic` handler to print the corresponding message and source line. diff --git a/blog/content/posts/06-page-tables/index.md b/blog/content/posts/06-page-tables/index.md index 7fa21ec5..721717dc 100644 --- a/blog/content/posts/06-page-tables/index.md +++ b/blog/content/posts/06-page-tables/index.md @@ -2,7 +2,7 @@ title = "Page Tables" order = 6 path = "page-tables" -date = "2015-12-09" +date = 2015-12-09 +++ In this post we will create a paging module, which allows us to access and modify the 4-level page table. We will explore recursive page table mapping and use some Rust features to make it safe. Finally we will create functions to translate virtual addresses and to map and unmap pages. diff --git a/blog/content/posts/07-remap-the-kernel/index.md b/blog/content/posts/07-remap-the-kernel/index.md index f61c8f6c..912e9d24 100644 --- a/blog/content/posts/07-remap-the-kernel/index.md +++ b/blog/content/posts/07-remap-the-kernel/index.md @@ -2,7 +2,7 @@ title = "Remap the Kernel" order = 7 path = "remap-the-kernel" -date = "2016-01-01" +date = 2016-01-01 [extra] updated = "2016-03-06" +++ diff --git a/blog/content/posts/08-kernel-heap/index.md b/blog/content/posts/08-kernel-heap/index.md index 2cdacc8e..05623e99 100644 --- a/blog/content/posts/08-kernel-heap/index.md +++ b/blog/content/posts/08-kernel-heap/index.md @@ -2,7 +2,7 @@ title = "Kernel Heap" order = 8 path = "kernel-heap" -date = "2016-04-11" +date = 2016-04-11 updated = "2017-11-19" +++ diff --git a/blog/content/posts/09-handling-exceptions/index.md b/blog/content/posts/09-handling-exceptions/index.md index ea77aff1..05d0b135 100644 --- a/blog/content/posts/09-handling-exceptions/index.md +++ b/blog/content/posts/09-handling-exceptions/index.md @@ -2,7 +2,7 @@ title = "Handling Exceptions" order = 9 path = "handling-exceptions" -date = "2017-03-26" +date = 2017-03-26 +++ In this post, we start exploring CPU exceptions. Exceptions occur in various erroneous situations, for example when accessing an invalid memory address or when dividing by zero. To catch them, we have to set up an _interrupt descriptor table_ that provides handler functions. At the end of this post, our kernel will be able to catch [breakpoint exceptions] and to resume normal execution afterwards. diff --git a/blog/content/posts/10-double-faults/index.md b/blog/content/posts/10-double-faults/index.md index c357c680..48692ebd 100644 --- a/blog/content/posts/10-double-faults/index.md +++ b/blog/content/posts/10-double-faults/index.md @@ -2,7 +2,7 @@ title = "Double Faults" order = 10 path = "double-faults" -date = "2017-01-02" +date = 2017-01-02 +++ In this post we explore double faults in detail. We also set up an _Interrupt Stack Table_ to catch double faults on a separate kernel stack. This way, we can completely prevent triple faults, even on kernel stack overflow. diff --git a/blog/content/second-edition/posts/01-introduction/index.md b/blog/content/second-edition/posts/01-introduction/index.md index 0cda4303..0812c70e 100644 --- a/blog/content/second-edition/posts/01-introduction/index.md +++ b/blog/content/second-edition/posts/01-introduction/index.md @@ -2,7 +2,7 @@ title = "Introduction" order = 1 path = "introduction" -date = "0000-01-01" +date = 0000-01-01 template = "second-edition/page.html" +++ diff --git a/blog/content/second-edition/posts/02-booting/index.md b/blog/content/second-edition/posts/02-booting/index.md index 972383b5..215439ae 100644 --- a/blog/content/second-edition/posts/02-booting/index.md +++ b/blog/content/second-edition/posts/02-booting/index.md @@ -2,7 +2,7 @@ title = "Booting" order = 2 path = "booting" -date = "0000-01-01" +date = 0000-01-01 template = "second-edition/page.html" +++ diff --git a/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md b/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md index a8b33a3a..11125d43 100644 --- a/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md +++ b/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md @@ -2,7 +2,7 @@ title = "A Freestanding Rust Binary" order = 3 path = "freestanding-rust-binary" -date = "0000-01-01" +date = 0000-01-01 template = "second-edition/page.html" +++ diff --git a/blog/templates/base.html b/blog/templates/base.html index bc28fdc2..957e2643 100644 --- a/blog/templates/base.html +++ b/blog/templates/base.html @@ -1,6 +1,6 @@ - + diff --git a/blog/templates/second-edition/base.html b/blog/templates/second-edition/base.html index bc28fdc2..941fcf80 100644 --- a/blog/templates/second-edition/base.html +++ b/blog/templates/second-edition/base.html @@ -1,6 +1,6 @@ - + @@ -21,7 +21,7 @@

      - {{ config.title }} + {{ config.title }} (Second Edition)

      {{ config.extra.subtitle }}

      From 082c9f0f4f5ba3223021af51ff6f3a5dd7b45c6d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 2 Feb 2018 13:21:34 +0100 Subject: [PATCH 08/29] New favicon --- blog/static/favicon.ico | Bin 486 -> 45842 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/blog/static/favicon.ico b/blog/static/favicon.ico index 34b42063d5e65cf11a2f3ce3e9e04190a3d4e41c..0eb5eb16fc9cbc618a068d354347227fd4e9d7e6 100644 GIT binary patch literal 45842 zcmZQzU}Ruq$jk)M3JiZXF))ZRFfeE^Ffe2?Ffd$VfQT~;eZa@Sz#vM%8wv~z45?78 zOhBy&0|NudOajVArHKpyP_j#5U|>jNU|_Jop@)rufguK}2Ba>Ifq{VohguV;I#7H{ z;7~IvOj-yyLBkXj_8@GCMY}bc8jw0WEb4U7)VW|$Gb)4{0-*G-hNs*C$tR(OD=7T6 zk&FSAfuKAA5=Y0#`228JN086a_V9Lm7JAjH7HpbD+0(B(mVZ1!q0FfhQN(>APdRW2=n|f-9889#~C^9fGMB%U# zxy(dLLc^X3D(k5czVt8yRIU!k2;pI1U;wqrtZ=jm%y6VxdW1Qtc35JGAW;7bBo6W; zh>bc5{Nz29A_mE=ptLm@!Zb{K)iR>K$=7(nGHXlxWTzAeqbzyRuN zf!bD}aa2$rm>()81s!8BWnf?crS%X71_qEB)OMFG7XO3X016vW7-5m7ix8-dhV0i^ z1_lNw!Kz4w}%?YI`P<(>MK(M)&kQgY< zlHw+6sRflSgxrQK4;p8YVPIfjVqjnZrDf0@1A4gwiW^XR1dZiU%bmoU!_2_I02*IK zb~A_#8kYr)-NMX~XJB9exf3KuEJoH3GlyO@vFQrb-viYd=^(?gZ7<*xUu0H$c{JgiR5h#Xx;*n43W58#e1f z?Gl(8LNsVR1)CZ45<{QY#O2;#91a4t2a)xF%0e7U=_U-CkB7M_8i%cy@RdHFh(ou2E#lJXkG*sM<7}jOJI^R9!!rhbI|xTXxqxlFqzu;nz<|9ac!9|YdBl7bRsGWf;-O)>&)OpL1*7hEaBV0Kj zzc@9^1^hNa=E>-lUvSwonvd|u(r7-yA3nHbNAnS`kb#Jg)<=-`2d;977UH929RtH) zSkHiOtw#s+90W)%AxsX(d^8yRL^$M$S1}m;L+$!#F!+i17$B^2F!*OQj_}9RXdEHh z4AdOo!XN6S$&SX+XdDrfVn)k4{Gl|Ok7$>VKpFm@(52(~;K=&Uq9~1`o!iXMn*xEVBZX}gG82khZH_*9wq`DoO`oZ9z(Kx~u zGYGNKI6}0`L1~|wcr+g&(iu8yFr+(BT#v>PddwjBqj5z2vSGA7LZq)z)@TTfhQMeD zjMNYqJ(rk)0sU;@!FDe3==t85afBXcP2cD_`G`2D);am0`xiiF zf$m5E-IV~kCt>uy2}C$!ONYo}^tf*VUp+i}ZwukLqQ|{0_~K~v{v*P1G|=}SfyzDP ze2t4edXE^slulbYP#Y9iydaB%?!OqlkB_!-fz5Q#{YuF3K!`nhuO+rnq^%gS?FIC> z8@(Tvw(){&`sh8&i1q@geGQrm8f^D0gW?Nx7dPlmZqWUdAU-k%^`X)85T$(3T~?#_ zl_Q5TWo%GCm(n;HD*7EL3#tLrP3Q7KFJ8bdUF734cQF z0L2f?-J%Q(44|?N4tchTKW%6-zXuox)X65a6(3=AMY zfy6<61hKIw8S+AENcn7_!k$=r2YT#!pduSIeupiLKx2TQd)@Wu7>5Q73=E143=E+0 zDr{~7^#cbgoCeZf&>e{A;Q=c5L3_nu?gF(h(A7|z9|*IIfq{XIfq?-upNVbhb?lrg4$WQ!XLSe0>vq^86Y+&ZE%1bf(3)t zLxJ2jlJgLjV8JQ`YBzzx6NU}3sIrEpd6*gy4H}C=SBgGw3~EE8s~P2!9s*(v3=E*L zR8Y8s){$W|0hHfBZ4Hnb5RPMDV8E3>OrY@z%6rK944b*5V#J34s4N7PY4~)2?&Sl8 zI|wV|Q^>%;0BTc$%p{;};HCc~g9Zi$WbXe_{?HGP0}Kod^8f#VQW`iusm*TyoBs!9 z{sV^p|35I)GqC^v-@x3!z(6BDD48OgPisCr{Gk4!HXmE~{RhPt)ILynQk#E(5iNeG e!w02=dJw??GVedN(;K>ZpqPi@(ei4@msbD*xmVBt literal 486 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4kiW$hCdQ-7c($0a29w(7Bes~c!06n$!t52 zU|E49cmtnzn7)?)K`UqDRfv<#~7awx4El`}5NwIJENcvabc}b}f?&`!6akl7Dlj^lHwO zV+W_r-QCUI_3+L;&b^14n67MTXl#7=W98)&F|oS7TG0(++ih%YLbf${IP7CBx-{v; z9dZ4*pqotk{QMTbAN}M`*Na=UjMeVlx>nWJg1qo_e!ZzWE;HGgb9=w?@bGA@I`ylK zO< z^7P|7c1S$=)i8Um?}YyMZ+Dm9|F}$MkxoH**_*i+x^t|UnVT1GoVdGvH-rBDd(7;e zTN(bP0l+XkKwHwXh From f5797b7fd22094332ddc737dce686095f5ea32a2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 31 Jan 2018 01:46:07 +0100 Subject: [PATCH 09/29] Begin post about creating a minimal Rust kernel --- .../second-edition/posts/02-booting/bios.md | 20 ++ .../03-freestanding-rust-binary/index.md | 4 +- .../posts/04-minimal-rust-kernel/index.md | 174 ++++++++++++++++++ blog/templates/second-edition/index.html | 1 + 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 blog/content/second-edition/posts/02-booting/bios.md create mode 100644 blog/content/second-edition/posts/04-minimal-rust-kernel/index.md diff --git a/blog/content/second-edition/posts/02-booting/bios.md b/blog/content/second-edition/posts/02-booting/bios.md new file mode 100644 index 00000000..cd5ee65c --- /dev/null +++ b/blog/content/second-edition/posts/02-booting/bios.md @@ -0,0 +1,20 @@ ++++ +title = "BIOS Booting" +path = "booting/bios" +date = 0000-01-01 +template = "second-edition/page.html" ++++ + +This post describes the BIOS boot process of an x86_64 machine. + + + +## The Boot Process +The + + +## BIOS + +### Multiboot + +## UEFI diff --git a/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md b/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md index 11125d43..62a68bb9 100644 --- a/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md +++ b/blog/content/second-edition/posts/03-freestanding-rust-binary/index.md @@ -14,6 +14,8 @@ This post describes how to create a Rust executable that does not link the stand TODO github, issues, comments, etc +TODO build for x86_64-unknown-linux-gnu? Or use platform specific binary inspection tools (e.g. objdump on linux, xxx on Windows and yyy on Mac)? + ## Introduction To write an operating system kernel, we need code that does not depend on any operating system features. This means that we can't use threads, files, heap memory, the network, random numbers, standard output, or any other features requiring OS abstractions or specific hardware. Which makes sense, since we're trying to write our own OS and our own drivers. @@ -396,4 +398,4 @@ Note that this is just a minimal example of a freestanding Rust binary. This bin The [next post] build upon our minimal freestanding binary by explaining the steps needed for creating a minimal operating system kernel. It explains how to configure the kernel for the target system, how to start it using a bootloader, and how to print something to the screen. -[next post]: TODO +[next post]: ./second-edition/posts/04-minimal-rust-kernel/index.md diff --git a/blog/content/second-edition/posts/04-minimal-rust-kernel/index.md b/blog/content/second-edition/posts/04-minimal-rust-kernel/index.md new file mode 100644 index 00000000..21e847eb --- /dev/null +++ b/blog/content/second-edition/posts/04-minimal-rust-kernel/index.md @@ -0,0 +1,174 @@ ++++ +title = "A Minimal Rust Kernel" +order = 4 +path = "minimal-rust-kernel" +date = 0000-01-01 +template = "second-edition/page.html" ++++ + +In this post we create a minimal 64-bit Rust kernel. We built upon the [freestanding Rust binary] from the previous post to create a bootable disk image, that prints something to the screen. + +[freestanding Rust binary]: ./second-edition/posts/03-freestanding-rust-binary/index.md + + + +TODO github, issues, comments, etc + +## The Boot Process +When you turn on a computer, it begins executing firmware code that is stored in motherboard [ROM]. This code performs a [power-on self-test], detects available RAM, and pre-initializes the CPU and hardware. Afterwards it looks for a bootable disk and starts booting the operating system kernel. + +[ROM]: https://en.wikipedia.org/wiki/Read-only_memory +[power-on self-test]: https://en.wikipedia.org/wiki/Power-on_self-test + +On x86, there are two firmware standards: the “Basic Input/Output System“ (**[BIOS]**) and the newer “Unified Extensible Firmware Interface” (**[UEFI]**). The BIOS standard is old and outdated, but simple and well-supported on any x86 machine since the 1980s. UEFI, in contrast, is more modern and has much more features, but is more complex to set up (at least in my opinion). + +[BIOS]: https://en.wikipedia.org/wiki/BIOS +[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface + +Currently, we only provide BIOS support, but support for UEFI is planned, too. If you'd like to help us with this, check out the [Github issue](https://github.com/phil-opp/blog_os/issues/349). + +### BIOS Boot +Almost all x86 systems have support for BIOS booting, even newer UEFI-based machines (they include an emulated BIOS). This is great, because you can use the same boot logic across all machines from the last centuries. But this wide compatibility is at the same time the biggest disadvantage of BIOS booting, because it means that the CPU is put into a 16-bit compability mode called [real mode] before booting so that that arcane bootloaders from the 1980s would still work. + +But let's start from the beginning: + +When you turn on a computer, it loads the BIOS from some special flash memory located on the motherboard. The BIOS runs self test and initialization routines of the hardware, then it looks for bootable disks. If it finds one, the control is transferred to its _bootloader_, which is a 512-byte portion of executable code stored at the disk's beginning. Most bootloaders are larger than 512 bytes, so bootloaders are commonly split into a small first stage, which fits into 512 bytes, and a second stage, which is subsequently loaded by the first stage. + +The bootloader has to determine the location of the kernel image on the disk and load it into memory. It also needs to switch the CPU from the 16-bit [real mode] first to the 32-bit [protected mode], and then to the 64-bit [long mode], where 64-bit registers and the complete main memory are available. Its third job is to query certain information (such as a memory map) from the BIOS and pass it to the OS kernel. + +[real mode]: https://en.wikipedia.org/wiki/Real_mode +[protected mode]: https://en.wikipedia.org/wiki/Protected_mode +[long mode]: https://en.wikipedia.org/wiki/Long_mode +[memory segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation + +Writing a bootloader is a bit cumbersome as it requires assembly language and a lot of non insightful steps like “write this magic value to this processor register”. Therefore we don't cover bootloader creation in this post and instead provide a tool named [bootimage] that automatically appends a bootloader to your kernel. + +[bootimage]: https://github.com/phil-opp/bootimage + +If you are interested in building your own bootloader, check out our “[Booting]” posts, where we explain in detail how a bootloader is built. + +[Booting]: TODO + +### The Multiboot Standard + +### UEFI + +## A Minimal Kernel +Now that we know how a computer boots, it's time to create our own minimal kernel. Our goal is to create a bootable disk image that prints a green “OK” to the screen when booted. For that we build upon the [freestanding Rust binary] we created in the previous post. + +We already have our `_start` entry point, which will be called by the boot loader. So let's output something to screen from it. + +### Printing to Screen +The easiest way to print text to the screen at this stage is the [VGA text buffer]. It is a special memory area mapped to the VGA hardware that contains the contents displayed on screen. It normally consists of 50 lines that each contain 80 character cells. Each character cell displays an ASCII character with some foreground and background colors. The screen output looks like this: + +[VGA text buffer]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode + +![screen output for common ASCII characters](https://upload.wikimedia.org/wikipedia/commons/6/6d/Codepage-737.png) + +We will discuss the exact layout of the VGA buffer in the next post, where we write a first small driver for it. For printing “OK”, we just need to know that the buffer is located at address `0xb8000` and that each character cell consists of an ASCII byte and a color byte. + +So let's extend our `main.rs` to write `OK` to the screen: + +```rust +#[no_mangle] +pub fn _start(boot_info: &'static mut BootInfo) -> ! { + let vga_buffer = 0xb8000 as *const u8 as *mut u8; + unsafe { + *vga_buffer.offset(0) = b'O'; + *vga_buffer.offset(1) = 0xa; // foreground color green + *vga_buffer.offset(0) = b'K'; + *vga_buffer.offset(3) = 0xa; // foreground color green + } + + loop {} +} +``` + +First, we cast the integer `0xb8000` into a [raw pointer]. Then we use the [`offset`] method to write the first four bytes individually. We write the ASCII character `b'O'` (the `b` prefix creates an single-byte ASCII character instead of a four-byte Unicode character), then we write the color `0xa` (which translates to “green foreground, black background”). We repeat the same for the second character. + +[raw pointer]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer +[`offset`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset + +Note that there's a big [`unsafe`] block around all memory writes. The reason is that the Rust compiler can't prove that the raw pointers we create are valid. They could point anywhere and lead to data corruption. By putting them into an `unsafe` block we're basically telling the compiler that we are absolutely sure that the operations are valid. Note that an `unsafe` block does not turn off Rust's safety checks. It only allows you to do [four additional things]. + +[`unsafe`]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html +[four additional things]: https://doc.rust-lang.org/stable/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers + +I want to emphasize that **this is not the way we want to do things in Rust!** It's very easy to mess up when working with raw pointers inside unsafe blocks, for example, we could easily write behind the buffer's end if we're not careful. + +So we want to minimize the use of `unsafe` as much as possible. Rust gives us the ability to do this by creating safe abstractions. For example, we could create a VGA buffer type that encapsulates all unsafety and ensures that it is _impossible_ to do anything wrong from the outside. This way, we would only need minimal amounts of `unsafe` and can be sure that we don't violate [memory safety]. + +[memory safety]: https://en.wikipedia.org/wiki/Memory_safety + +We will create such a safe VGA buffer abstraction in the next post. For the remainder of post, we stick to our unsafe version to keep things simple. + +Now that we have an executable that does something perceptible, it is time to turn it into a bootable disk image. However, in order to be able do that, we need to cross-compile our kernel to our target system. + +### Target Specification +Until now, we compiled our kernel for the host system, that means the system you're currently running on. This could be a Windows machine with an ARM processor. Or a Mac with a 32-bit x86 processor. Or any other of the [many targets that Rust supports][platform-support]. Independent of the host system, we want to compile an executable for a bare-metal x86_64 system, which is our target system. + +[platform-support]: https://forge.rust-lang.org/platform-support.html + + +TODO rewrite + +We require some special configuration parameters for our target system (e.g. no underlying OS), so none of the [existing target triples][platform-support] fits. (A target triple describes the CPU architecture, the vendor, the operating system, and sometimes additionally the calling convention; “unknown” means that there is no reasonable value). Luckily Rust allows us to define our own target in a JSON file. For example, a JSON file that describes the `x86_64-unknown-linux-gnu` target looks like this: + +```json +TODO +``` + +Most fields are required by LLVM to generate code for that platform. For example, the `data-layout` field defines the size of various integer, floating point, and pointer types. Then there are fields that Rust uses for conditional compilation, such as max_atomic_width. The third kind of fields are the most interesting: They define how the crate should be built. For example, features? + +We also target `x86_64` systems with our kernel, so our target specification will look very similar to the `x86_64-unknown-linux-gnu` specification. Let's start by creating a `x86_64-unknown-blog_os.json` file (choose any name you like) with the common content: + +```json + +``` + +We add the following entries, where we changed the values: + +```json + +``` + +The reason for the change… + +Finally, we add some additional entries: + +```json + +``` + +The xxx does yyy… + +Our target specification file now looks like this: + +```json + +``` + +We can now build the kernel for our new target by passing the name of the JSON file (without extension) as `--target`. There's is currently an open bug with custom targets, so you also need to set the `RUST_TARGET_PATH` environment variable to the current directory, otherwise Rust might not be able to find your target. The full command is: + +``` +> RUST_TARGET_PATH=(pwd) cargo build --target x86_64-unknown-blog_os +``` + +### Creating a Bootimage +Now that we have an executable that does something perceptible, it is time to turn it into a bootable disk image. As we learned in the [section about booting], we need a bootloader for that, which initializes the CPU and loads our kernel. + +[section about booting]: #the-boot-process + +To make things easy, we created a tool named `bootimage` that automatically downloads and builds our bootloader, and combines it with the kernel executable to create a bootable disk image. To install it, execute `cargo install bootimage` in your terminal. + +After installing, creating a bootimage is as easy as executing `bootimage --target x86_64-unknown-blog_os`. + +You should now see a file named `bootimage.bin` in your crate root directory. This file is a bootable disk image, so can boot it in a virtual machine or copy it to an USB drive to boot it on real hardware. (Note that this is not a CD image (they have a different format), so burning to disk doesn't work). + +## Booting it! + +- qemu +- bochs? virtualbox? +- makefile? cargo-make? + +## Summary & What's next? diff --git a/blog/templates/second-edition/index.html b/blog/templates/second-edition/index.html index 63c1e9fc..b1fbe890 100644 --- a/blog/templates/second-edition/index.html +++ b/blog/templates/second-edition/index.html @@ -28,6 +28,7 @@ {{ macros::post_link(page=posts.0) }} {{ macros::post_link(page=posts.1) }} {{ macros::post_link(page=posts.2) }} + {{ macros::post_link(page=posts.3) }}