mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
Merge pull request #148 from phil-opp/convert-posts-for-hugo
Convert posts for hugo
This commit is contained in:
@@ -34,4 +34,4 @@ You need to have `nasm`, `grub-mkrescue`, `xorriso`, `qemu` and a nighly Rust co
|
||||
Please file an issue if you run into any problems.
|
||||
|
||||
## License
|
||||
The source code is dual-licensed under MIT or the Apache License (Version 2.0). This excludes the `posts` and `pages` directories.
|
||||
The source code is dual-licensed under MIT or the Apache License (Version 2.0). This excludes the `blog` directory.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
layout: page
|
||||
title: Cross Compile Binutils
|
||||
---
|
||||
+++
|
||||
title = "Cross Compile Binutils"
|
||||
+++
|
||||
|
||||
The [GNU Binutils] are a collection of various binary tools such as `ld`, `as`, `objdump`, or `readelf`. These tools are platform-specific, so you need to compile them again if your host system and target system are different. In our case, we need `ld` and `objdump` for the x86_64 architecture.
|
||||
[GNU Binutils]: https://www.gnu.org/software/binutils/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
layout: page
|
||||
title: "Cross Compiling: libcore"
|
||||
---
|
||||
+++
|
||||
title = "Cross Compiling: libcore"
|
||||
+++
|
||||
|
||||
So you're getting an ``error: can't find crate for `core` [E0463]`` when using `--target x86_64-unknown-linux-gnu`. That means that you're not running Linux or not using using a x86_64 processor.
|
||||
|
||||
**If you have an x86_64 processor and want a quick fix**, try it with `x86_64-pc-windows-gnu` or `x86_64-apple-darwin` (or simply omit the explicit `--target`).
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
layout: page
|
||||
title: "Set Up GDB"
|
||||
---
|
||||
+++
|
||||
title = "Set Up GDB"
|
||||
+++
|
||||
|
||||
There are a lot of things that can go wrong when developing an OS. So it's a good idea to add a debugger to our toolset, which allows us to set breakpoints and examine variables. We will use [GDB](https://www.gnu.org/software/gdb/) as QEMU supports it out of the box.
|
||||
|
||||
### QEMU parameters
|
||||
@@ -71,4 +71,4 @@ After connecting to QEMU, you can use various gdb commands to control execution
|
||||
Of course there are many more commands. Feel free to send a PR if you think this list is missing something important. For a more complete GDB overview, check out [Beej's Quick Guide][bggdb] or the [website for Harvard's CS161 course][CS161].
|
||||
|
||||
[bggdb]: http://beej.us/guide/bggdb/
|
||||
[CS161]: http://www.eecs.harvard.edu/~margo/cs161/resources/gdb.html
|
||||
[CS161]: http://www.eecs.harvard.edu/~cs161/resources/gdb.html
|
||||
@@ -1,13 +1,21 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'A minimal x86 kernel'
|
||||
redirect_from: '/2015/08/18/multiboot-kernel/'
|
||||
---
|
||||
+++
|
||||
title = "A minimal x86 kernel"
|
||||
slug = "multiboot-kernel"
|
||||
date = "2015-08-18"
|
||||
aliases = [
|
||||
"/2015/08/18/multiboot-kernel/",
|
||||
"/rust-os/multiboot-kernel.html",
|
||||
]
|
||||
+++
|
||||
|
||||
This post explains how to create a minimal x86 operating system kernel. In fact, it will just boot and print `OK` to the screen. The following blog posts we will extend it using the [Rust] programming language.
|
||||
|
||||
[Rust]: http://www.rust-lang.org/
|
||||
|
||||
<!--more-->
|
||||
|
||||
I tried to explain everything in detail and to keep the code as simple as possible. If you have any questions, suggestions or other issues, please leave a comment or [create an issue] on Github. The source code is available in a [repository][source code], too.
|
||||
|
||||
[Rust]: http://www.rust-lang.org/
|
||||
[create an issue]: https://github.com/phil-opp/blog_os/issues
|
||||
[source code]: https://github.com/phil-opp/blog_os/tree/multiboot_bootstrap/src/arch/x86_64
|
||||
|
||||
@@ -183,7 +191,7 @@ Idx Name Size VMA LMA File off Algn
|
||||
CONTENTS, ALLOC, LOAD, READONLY, CODE
|
||||
```
|
||||
_Note_: The `ld` and `objdump` commands are platform specific. If you're _not_ working on x86_64 architecture, you will need to [cross compile binutils]. Then use `x86_64‑elf‑ld` and `x86_64‑elf‑objdump` instead of `ld` and `objdump`.
|
||||
[cross compile binutils]: /cross-compile-binutils.html
|
||||
[cross compile binutils]: {{% relref "cross-compile-binutils.md" %}}
|
||||
|
||||
## Creating the ISO
|
||||
The last step is to create a bootable ISO image with GRUB. We need to create the following directory structure and copy the `kernel.bin` to the right place:
|
||||
@@ -258,7 +266,7 @@ Right now we need to execute 4 commands in the right order everytime we change a
|
||||
├── linker.ld
|
||||
└── grub.cfg
|
||||
```
|
||||
The Makefile looks like this (but indented with tabs instead of spaces):
|
||||
The Makefile looks like this (indented with tabs instead of spaces):
|
||||
|
||||
```Makefile
|
||||
arch ?= x86_64
|
||||
@@ -313,5 +321,5 @@ Now we can invoke `make` and all updated assembly files are compiled and linked.
|
||||
|
||||
In the [next post] we will create a page table and do some CPU configuration to switch to the 64-bit [long mode].
|
||||
|
||||
[next post]: {{ page.next.url }}
|
||||
[next post]: {{% relref "2015-08-25-entering-longmode.md" %}}
|
||||
[long mode]: https://en.wikipedia.org/wiki/Long_mode
|
||||
@@ -1,17 +1,25 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'Entering Long Mode'
|
||||
redirect_from: "/2015/08/25/entering-longmode/"
|
||||
updated: 2015-10-29 00:00:00 +0000
|
||||
---
|
||||
+++
|
||||
title = "Entering Long Mode"
|
||||
slug = "entering-longmode"
|
||||
date = "2015-08-25"
|
||||
updated = "2015-10-29"
|
||||
aliases = [
|
||||
"/2015/08/25/entering-longmode/",
|
||||
"/rust-os/entering-longmode.html",
|
||||
]
|
||||
+++
|
||||
|
||||
In the [previous post] we created a minimal multiboot kernel. It just prints `OK` and hangs. The goal is to extend it and call 64-bit [Rust] code. But the CPU is currently in [protected mode] and allows only 32-bit instructions and up to 4GiB memory. So we need to set up _Paging_ and switch to the 64-bit [long mode] first.
|
||||
|
||||
I tried to explain everything in detail and to keep the code as simple as possible. If you have any questions, suggestions, or issues, please leave a comment or [create an issue] on Github. The source code is available in a [repository][source code], too.
|
||||
|
||||
[previous post]: {{ page.previous.url }}
|
||||
[previous post]: {{% relref "2015-08-18-multiboot-kernel.md" %}}
|
||||
[Rust]: http://www.rust-lang.org/
|
||||
[protected mode]: https://en.wikipedia.org/wiki/Protected_mode
|
||||
[long mode]: https://en.wikipedia.org/wiki/Long_mode
|
||||
|
||||
<!--more-->
|
||||
|
||||
I tried to explain everything in detail and to keep the code as simple as possible. If you have any questions, suggestions, or issues, please leave a comment or [create an issue] on Github. The source code is available in a [repository][source code], too.
|
||||
|
||||
[create an issue]: https://github.com/phil-opp/blog_os/issues
|
||||
[source code]: https://github.com/phil-opp/blog_os/tree/entering_longmode/src/arch/x86_64
|
||||
|
||||
@@ -34,7 +42,7 @@ error:
|
||||
At address `0xb8000` begins the so-called [VGA text buffer]. It's an array of screen characters that are displayed by the graphics card. A [future post] will cover the VGA buffer in detail and create a Rust interface to it. But for now, manual bit-fiddling is the easiest option.
|
||||
|
||||
[VGA text buffer]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode
|
||||
[future post]: {{ page.next.next.url }}
|
||||
[future post]: {{% relref "2015-10-23-printing-to-screen.md" %}}
|
||||
|
||||
A screen character consists of a 8 bit color code and a 8 bit [ASCII] character. We used the color code `4f` for all characters, which means white text on red background. `0x52` is an ASCII `R`, `0x45` is an `E`, `0x3a` is a `:`, and `0x20` is a space. The second space is overwritten by the given ASCII byte. Finally the CPU is stopped with the `hlt` instruction.
|
||||
|
||||
@@ -102,8 +110,8 @@ In `no_multiboot`, we use the `jmp` (“jump”) instruction to jump to our erro
|
||||
|
||||
```nasm
|
||||
check_cpuid:
|
||||
; Check if CPUID is supported by attempting to flip the ID bit (bit 21) in
|
||||
; the FLAGS register. If we can flip it, CPUID is available.
|
||||
; Check if CPUID is supported by attempting to flip the ID bit (bit 21)
|
||||
; in the FLAGS register. If we can flip it, CPUID is available.
|
||||
|
||||
; Copy FLAGS in to EAX via stack
|
||||
pushfd
|
||||
@@ -123,13 +131,13 @@ check_cpuid:
|
||||
pushfd
|
||||
pop eax
|
||||
|
||||
; Restore FLAGS from the old version stored in ECX (i.e. flipping the ID bit
|
||||
; back if it was ever flipped).
|
||||
; Restore FLAGS from the old version stored in ECX (i.e. flipping the
|
||||
; ID bit back if it was ever flipped).
|
||||
push ecx
|
||||
popfd
|
||||
|
||||
; Compare EAX and ECX. If they are equal then that means the bit wasn't
|
||||
; flipped, and CPUID isn't supported.
|
||||
; Compare EAX and ECX. If they are equal then that means the bit
|
||||
; wasn't flipped, and CPUID isn't supported.
|
||||
cmp eax, ecx
|
||||
je .no_cpuid
|
||||
ret
|
||||
@@ -526,4 +534,4 @@ It's time to finally leave assembly behind[^leave_assembly_behind] and switch to
|
||||
[^leave_assembly_behind]: Actually we will still need some assembly in the future, but I'll try to minimize it.
|
||||
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[next post]: {{ page.next.url }}
|
||||
[next post]: {{% relref "2015-09-02-set-up-rust.md" %}}
|
||||
@@ -1,16 +1,23 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'Set Up Rust'
|
||||
redirect_from: "/2015/09/02/setup-rust/"
|
||||
redirect_from: "/setup-rust.html"
|
||||
---
|
||||
+++
|
||||
title = "Set Up Rust"
|
||||
date = "2015-09-02"
|
||||
aliases = [
|
||||
"/2015/09/02/setup-rust/",
|
||||
"/setup-rust.html",
|
||||
"/rust-os/setup-rust.html",
|
||||
]
|
||||
+++
|
||||
|
||||
In the previous posts we created a [minimal Multiboot kernel][multiboot post] and [switched to Long Mode][long mode post]. Now we can finally switch to [Rust] code. Rust is a high-level language without runtime. It allows us to not link the standard library and write bare metal code. Unfortunately the setup is not quite hassle-free yet.
|
||||
|
||||
[multiboot post]: {{% relref "2015-08-18-multiboot-kernel.md" %}}
|
||||
[long mode post]: {{% relref "2015-08-25-entering-longmode.md" %}}
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
|
||||
<!--more-->
|
||||
|
||||
This blog post tries to set up Rust step-by-step and point out the different problems. If you have any questions, problems, or suggestions please [file an issue] or create a comment at the bottom. The code from this post is in a [Github repository], too.
|
||||
|
||||
[multiboot post]: {{ page.previous.previous.url }}
|
||||
[long mode post]: {{ page.previous.url }}
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[file an issue]: https://github.com/phil-opp/blog_os/issues
|
||||
[Github repository]: https://github.com/phil-opp/blog_os/tree/set_up_rust
|
||||
|
||||
@@ -76,7 +83,7 @@ We can now build it using `cargo build`. To make sure, we are building it for th
|
||||
cargo build --target=x86_64-unknown-linux-gnu
|
||||
```
|
||||
It creates a static library at `target/x86_64-unknown-linux-gnu/debug/libblog_os.a`, which can be linked with our assembly kernel. If you're getting an error about a missing `core` crate, [look here][cross compile libcore].
|
||||
[cross compile libcore]: /cross-compile-libcore.html
|
||||
[cross compile libcore]: {{% relref "cross-compile-libcore.md" %}}
|
||||
|
||||
To build and link the rust library on `make`, we extend our `Makefile`([full file][github makefile]):
|
||||
|
||||
@@ -86,7 +93,8 @@ target ?= $(arch)-unknown-linux-gnu
|
||||
rust_os := target/$(target)/debug/libblog_os.a
|
||||
# ...
|
||||
$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script)
|
||||
@ld -n -T $(linker_script) -o $(kernel) $(assembly_object_files) $(rust_os)
|
||||
@ld -n -T $(linker_script) -o $(kernel) \
|
||||
$(assembly_object_files) $(rust_os)
|
||||
|
||||
cargo:
|
||||
@cargo build --target $(target)
|
||||
@@ -149,10 +157,14 @@ pub extern fn rust_main() {
|
||||
Now `make run` doesn't complain about `memcpy` anymore. Instead it will show a pile of new errors:
|
||||
|
||||
```
|
||||
target/debug/libblog_os.a(core-35017696.0.o): In function `ops::f32.Rem::rem::hfcbbcbe5711a6e6emxm':
|
||||
core.0.rs:(.text._ZN3ops7f32.Rem3rem20hfcbbcbe5711a6e6emxmE+0x1): undefined reference to `fmodf'
|
||||
target/debug/libblog_os.a(core-35017696.0.o): In function `ops::f64.Rem::rem::hbf225030671c7a35Txm':
|
||||
core.0.rs:(.text._ZN3ops7f64.Rem3rem20hbf225030671c7a35TxmE+0x1): undefined reference to `fmod'
|
||||
target/debug/libblog_os.a(core-35017696.0.o):
|
||||
In function `ops::f32.Rem::rem::hfcbbcbe5711a6e6emxm':
|
||||
core.0.rs:(.text._ZN3ops7f32.Rem3rem20hfcbbcbe5711a6e6emxmE+0x1):
|
||||
undefined reference to `fmodf'
|
||||
target/debug/libblog_os.a(core-35017696.0.o):
|
||||
In function `ops::f64.Rem::rem::hbf225030671c7a35Txm':
|
||||
core.0.rs:(.text._ZN3ops7f64.Rem3rem20hbf225030671c7a35TxmE+0x1):
|
||||
undefined reference to `fmod'
|
||||
...
|
||||
```
|
||||
|
||||
@@ -170,7 +182,8 @@ So how do we fix this problem? We don't use any floating point operations, so we
|
||||
|
||||
```make
|
||||
$(kernel): cargo $(rust_os) $(assembly_object_files) $(linker_script)
|
||||
@ld -n --gc-sections -T $(linker_script) -o $(kernel) $(assembly_object_files) $(rust_os)
|
||||
@ld -n --gc-sections -T $(linker_script) -o $(kernel) \
|
||||
$(assembly_object_files) $(rust_os)
|
||||
```
|
||||
Now we can do a `make run` again and… it doesn't boot anymore:
|
||||
|
||||
@@ -199,8 +212,12 @@ The following snippet still fails:
|
||||
The error is a linker error again (hence the ugly error message):
|
||||
|
||||
```
|
||||
target/debug/libblog_os.a(blog_os.0.o): In function `blog_os::iter::Iterator::zip<core::iter::FlatMap<core::ops::Range<i32>, core::ops::Range<i32>, closure>,core::ops::RangeFrom<i32>>':
|
||||
/home/.../src/libcore/iter.rs:654: undefined reference to `_Unwind_Resume'
|
||||
target/debug/libblog_os.a(blog_os.0.o):
|
||||
In function `blog_os::iter::Iterator::zip<core::iter::FlatMap<
|
||||
core::ops::Range<i32>, core::ops::Range<i32>, closure>,
|
||||
core::ops::RangeFrom<i32>>':
|
||||
/home/.../src/libcore/iter.rs:654:
|
||||
undefined reference to `_Unwind_Resume'
|
||||
```
|
||||
So the linker can't find a function named `_Unwind_Resume` that is referenced in `iter.rs:654` in libcore. This reference is not really there at [line 654 of libcore's `iter.rs`][iter.rs:654]. Instead, it is a compiler inserted _landing pad_, which is used for exception handling.
|
||||
|
||||
@@ -225,7 +242,7 @@ a.1 += 1;
|
||||
```
|
||||
When we add that code to `rust_main` and test it using `make run`, the OS will constantly reboot itself. Let's try to debug it.
|
||||
|
||||
[iter.rs:654]: https://doc.rust-lang.org/nightly/src/core/iter.rs.html#654
|
||||
[iter.rs:654]: https://github.com/rust-lang/rust/blob/b0ca03923359afc8df92a802b7cc1476a72fb2d0/src/libcore/iter.rs#L654
|
||||
|
||||
### Debugging
|
||||
Such a boot loop is most likely caused by some [CPU exception][exception table]. When these exceptions aren't handled, a [Triple Fault] occurs and the processor resets itself. We can look at generated CPU interrupts/exceptions using QEMU:
|
||||
@@ -267,13 +284,14 @@ So let's look at the first exception: `old:0xffffffff` means that the CPU wasn't
|
||||
> objdump -D build/kernel-x86_64.bin | grep "1001d3:"
|
||||
1001d3: 0f 10 05 16 01 00 00 movups 0x116(%rip),%xmm0 ...
|
||||
```
|
||||
Through `objdump -D` we disassemble our whole kernel and `grep` picks the relevant line. The instruction at `0x1001d3` seems to be a valid `movaps` instruction. It's a [SSE] instruction that moves 128 bit between memory and SSE-registers (e.g. `xmm0`). But why the `Invalid Opcode` exception? The answer is hidden behind the [movaps documentation][movaps]: The section _Protected Mode Exceptions_ lists the conditions for the various exceptions. The short code of the `Invalid Opcode` is `#UD`, so the exception occurs
|
||||
Through `objdump -D` we disassemble our whole kernel and `grep` picks the relevant line. The instruction at `0x1001d3` seems to be a valid `movaps` instruction. It's a [SSE] instruction that moves 128 bit between memory and SSE-registers (e.g. `xmm0`). But why the `Invalid Opcode` exception? The answer is hidden behind the [movaps documentation][movaps]: The section _Protected Mode Exceptions_ lists the conditions for the various exceptions. The short code of the `Invalid Opcode` is `#UD`. An `#UD` exception occurs:
|
||||
|
||||
> For an unmasked Streaming SIMD Extensions 2 instructions numeric exception (CR4.OSXMMEXCPT =0). If EM in CR0 is set. If OSFXSR in CR4 is 0. If CPUID feature flag SSE2 is 0.
|
||||
|
||||
[SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
|
||||
[movaps]: http://www.c3se.chalmers.se/Common/VTUNE-9.1/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc181.htm
|
||||
|
||||
The rough translation of this cryptic definition is: _If SSE isn't enabled_. So apparently Rust uses SSE instructions by default and we didn't enable SSE before. So the fix for this bug is enabling SSE.
|
||||
The rough translation of this cryptic definition is: _If SSE isn't enabled_. So apparently Rust uses SSE instructions by default and we didn't enable SSE before. To fix this, we can either disable SSE instructions in the compiler or enable SSE in our kernel. We do the latter, as it's easier.
|
||||
|
||||
### Enabling SSE
|
||||
To enable SSE, assembly code is needed again. We want to add a function that tests if SSE is available and enables it then. Else we want to print an error message.
|
||||
@@ -307,7 +325,6 @@ The code is from the great [OSDev Wiki][osdev sse] again. Notice that it sets/un
|
||||
|
||||
When we insert a `call set_up_SSE` somewhere in the `start` function (for example after `call enable_paging`), our Rust code will finally work.
|
||||
|
||||
[32-bit error function]: {{ page.previous.url }}#some-tests
|
||||
[osdev sse]: http://wiki.osdev.org/SSE#Checking_for_SSE
|
||||
|
||||
### “OS returned!”
|
||||
@@ -365,10 +382,10 @@ Some notes:
|
||||
### Stack Overflows
|
||||
Since we still use the small 64 byte [stack from the last post], we must be careful not to [overflow] it. Normally, Rust tries to avoid stack overflows through _guard pages_: The page below the stack isn't mapped and such a stack overflow triggers a page fault (instead of silently overwriting random memory). But we can't unmap the page below our stack right now since we currently use only a single big page. Fortunately the stack is located just above the page tables. So some important page table entry would probably get overwritten on stack overflow and then a page fault occurs, too.
|
||||
|
||||
[stack from the last post]: {{ page.previous.url }}#creating-a-stack
|
||||
[stack from the last post]: {{% relref "2015-08-25-entering-longmode.md#creating-a-stack" %}}
|
||||
[overflow]: https://en.wikipedia.org/wiki/Stack_overflow
|
||||
|
||||
## What's next?
|
||||
Until now we write magic bits to some memory location when we want to print something to screen. In the [next post] we create a abstraction for the VGA text buffer that allows us to print strings in different colors and provides a simple interface.
|
||||
|
||||
[next post]: {{ page.next.url }}
|
||||
[next post]: {{% relref "2015-10-23-printing-to-screen.md" %}}
|
||||
@@ -1,19 +1,25 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'Printing to Screen'
|
||||
redirect_from: "/2015/10/23/printing-to-screen/"
|
||||
---
|
||||
+++
|
||||
title = "Printing to Screen"
|
||||
date = "2015-10-23"
|
||||
aliases = [
|
||||
"/2015/10/23/printing-to-screen/",
|
||||
"/rust-os/printing-to-screen.html",
|
||||
]
|
||||
+++
|
||||
|
||||
In the [previous post] we switched from assembly to [Rust], a systems programming language that provides great safety. But so far we are using unsafe features like [raw pointers] whenever we want to print to screen. In this post we will create a Rust module that provides a safe and easy-to-use interface for the VGA text buffer. It will support Rust's [formatting macros], too.
|
||||
|
||||
[previous post]: {{ page.previous.url }}
|
||||
[previous post]: {{% relref "2015-09-02-set-up-rust.md" %}}
|
||||
[Rust]: https://www.rust-lang.org/
|
||||
[raw pointers]: https://doc.rust-lang.org/book/raw-pointers.html
|
||||
[formatting macros]: https://doc.rust-lang.org/std/fmt/#related-macros
|
||||
|
||||
<!--more-->
|
||||
|
||||
This post uses recent unstable features, so you need an up-to-date nighly compiler. If you have any questions, problems, or suggestions please [file an issue] or create a comment at the bottom. The code from this post is also available on [Github][code repository].
|
||||
|
||||
[file an issue]: https://github.com/phil-opp/blog_os/issues
|
||||
[code repository]: https://github.com/phil-opp/blog_os/tree/printing_to_screen/src
|
||||
[code repository]: https://github.com/phil-opp/blog_os/tree/printing_to_screen
|
||||
|
||||
## The VGA Text Buffer
|
||||
The text buffer starts at physical address `0xb8000` and contains the characters displayed on screen. It has 25 rows and 80 columns. Each screen character has the following format:
|
||||
@@ -379,7 +385,7 @@ Rust's [macro syntax] is a bit strange, so we won't try to write a macro from sc
|
||||
[macro syntax]: https://doc.rust-lang.org/nightly/book/macros.html
|
||||
[`println!` macro]: https://doc.rust-lang.org/nightly/std/macro.println!.html
|
||||
|
||||
```
|
||||
```rust
|
||||
macro_rules! println {
|
||||
($fmt:expr) => (print!(concat!($fmt, "\n")));
|
||||
($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
|
||||
@@ -389,7 +395,7 @@ It just adds a `\n` and then invokes the [`print!` macro], which is defined as:
|
||||
|
||||
[`print!` macro]: https://doc.rust-lang.org/nightly/std/macro.print!.html
|
||||
|
||||
```
|
||||
```rust
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
|
||||
}
|
||||
@@ -400,12 +406,13 @@ It calls the `_print` method in the `io` module of the current crate (`$crate`),
|
||||
|
||||
To print to the VGA buffer, we just copy the `println!` macro and modify the `print!` macro to use our static `WRITER` instead of `_print`:
|
||||
|
||||
```
|
||||
```rust
|
||||
// in src/vga_buffer.rs
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => ({
|
||||
use core::fmt::Write;
|
||||
$crate::vga_buffer::WRITER.lock().write_fmt(format_args!($($arg)*)).unwrap();
|
||||
let writer = $crate::vga_buffer::WRITER.lock();
|
||||
writer.write_fmt(format_args!($($arg)*)).unwrap();
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -449,7 +456,7 @@ In the next posts we will map the kernel pages correctly so that accessing `0x0`
|
||||
|
||||
The [next post] describes the Multiboot information structure and creates a frame allocator using the information about memory areas.
|
||||
|
||||
[next post]: {{ page.next.url }}
|
||||
[next post]: {{% relref "2015-11-15-allocating-frames.md" %}}
|
||||
|
||||
## Other Rust OS Projects
|
||||
Now that you know the very basics of OS development in Rust, you should also check out the following projects:
|
||||
@@ -458,7 +465,7 @@ Now that you know the very basics of OS development in Rust, you should also che
|
||||
_Note_: You need to [cross compile binutils] to build it (or you create some symbolic links[^fn-symlink] if you're on x86_64).
|
||||
[Rust Bare-Bones Kernel]: https://github.com/thepowersgang/rust-barebones-kernel
|
||||
[higher half]: http://wiki.osdev.org/Higher_Half_Kernel
|
||||
[cross compile binutils]: /cross-compile-binutils.html
|
||||
[cross compile binutils]: {{% relref "cross-compile-binutils.md" %}}
|
||||
[^fn-symlink]: You will need to symlink `x86_64-none_elf-XXX` to `/usr/bin/XXX` where `XXX` is in {`as`, `ld`, `objcopy`, `objdump`, `strip`}. The `x86_64-none_elf-XXX` files must be in some folder that is in your `$PATH`. But then you can only build for your x86_64 host architecture, so use this hack only for testing.
|
||||
|
||||
- [RustOS]: More advanced kernel that supports allocation, keyboard inputs, and threads. It also has a scheduler and a basic network driver.
|
||||
@@ -1,10 +1,12 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'Allocating Frames'
|
||||
---
|
||||
+++
|
||||
title = "Allocating Frames"
|
||||
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.
|
||||
|
||||
<!--more-->
|
||||
|
||||
The full source code is available on [Github][source repo]. Feel free to open issues there if you have any problems or improvements. You can also leave a comment at the bottom.
|
||||
|
||||
[source repo]: https://github.com/phil-opp/blog_os/tree/allocating_frames
|
||||
@@ -47,15 +49,20 @@ Instead of writing an own Multiboot module, we use the [multiboot2-elf64] crate.
|
||||
[multiboot2-elf64]: https://github.com/phil-opp/multiboot2-elf64
|
||||
[^fn-multiboot-crate]: All contributions are welcome! If you want to maintain it, please contact me!
|
||||
|
||||
So let's add a dependency on the git repository in the `Cargo.toml`:
|
||||
So let's add a dependency on the git repository:
|
||||
|
||||
```toml
|
||||
...
|
||||
# in Cargo.toml
|
||||
[dependencies.multiboot2]
|
||||
git = "https://github.com/phil-opp/multiboot2-elf64"
|
||||
```
|
||||
|
||||
Now we can add `extern crate multiboot2` and use it to print available memory areas.
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
extern crate multiboot2;
|
||||
```
|
||||
|
||||
Now we can use it to print available memory areas.
|
||||
|
||||
### Available Memory
|
||||
The boot information structure consists of various _tags_. See section 3.4 of the Multiboot specification ([PDF][multiboot specification]) for a complete list. The _memory map_ tag contains a list of all available RAM areas. Special areas such as the VGA text buffer at `0xb8000` are not available. Note that some of the available memory is already used by our kernel and by the multiboot information structure itself.
|
||||
@@ -66,11 +73,13 @@ To print all available memory areas, we can use the `multiboot2` crate in our `r
|
||||
|
||||
```rust
|
||||
let boot_info = unsafe{ multiboot2::load(multiboot_information_address) };
|
||||
let memory_map_tag = boot_info.memory_map_tag().expect("Memory map tag required");
|
||||
let memory_map_tag = boot_info.memory_map_tag()
|
||||
.expect("Memory map tag required");
|
||||
|
||||
println!("memory areas:");
|
||||
for area in memory_map_tag.memory_areas() {
|
||||
println!(" start: 0x{:x}, length: 0x{:x}", area.base_addr, area.length);
|
||||
println!(" start: 0x{:x}, length: 0x{:x}",
|
||||
area.base_addr, area.length);
|
||||
}
|
||||
```
|
||||
The `load` function is `unsafe` because it relies on a valid address. Since the memory tag is not required by the Multiboot specification, the `memory_map_tag()` function returns an `Option`. The `memory_areas()` function returns the desired memory area iterator.
|
||||
@@ -260,12 +269,26 @@ pub struct AreaFrameAllocator {
|
||||
```
|
||||
The `next_free_frame` field is a simple counter that is increased every time we return a frame. It's initialized to `0` and every frame below it counts as used. The `current_area` field holds the memory area that contains `next_free_frame`. If `next_free_frame` leaves this area, we will look for the next one in `areas`. When there are no areas left, all frames are used and `current_area` becomes `None`. The `{kernel, multiboot}_{start, end}` fields are used to avoid returning already used fields.
|
||||
|
||||
To implement the `FrameAllocator` trait, we need to implement the `allocate_frame` and the `deallocate_frame` methods. The former looks like this:
|
||||
To implement the `FrameAllocator` trait, we need to implement the allocation and deallocation methods:
|
||||
|
||||
```rust
|
||||
impl FrameAllocator for AreaFrameAllocator {
|
||||
fn allocate_frame(&mut self) -> Option<Frame> {
|
||||
// TODO (see below)
|
||||
}
|
||||
|
||||
fn deallocate_frame(&mut self, frame: Frame) {
|
||||
// TODO (see below)
|
||||
}
|
||||
}
|
||||
```
|
||||
The `allocate_frame` method looks like this:
|
||||
|
||||
```rust
|
||||
// in `allocate_frame` in `impl FrameAllocator for AreaFrameAllocator`
|
||||
|
||||
if let Some(area) = self.current_area {
|
||||
// "clone" the frame to return it if it's free. Frame doesn't
|
||||
// "Clone" the frame to return it if it's free. Frame doesn't
|
||||
// implement Clone, but we can construct an identical frame.
|
||||
let frame = Frame{ number: self.next_free_frame.number };
|
||||
|
||||
@@ -298,11 +321,12 @@ fn allocate_frame(&mut self) -> Option<Frame> {
|
||||
} else {
|
||||
None // no free frames left
|
||||
}
|
||||
}
|
||||
```
|
||||
The `choose_next_area` method isn't part of the trait and thus goes into an `impl AreaFrameAllocator` block:
|
||||
The `choose_next_area` method isn't part of the trait and thus goes into a new `impl AreaFrameAllocator` block:
|
||||
|
||||
```rust
|
||||
// in `impl AreaFrameAllocator`
|
||||
|
||||
fn choose_next_area(&mut self) {
|
||||
self.current_area = self.areas.clone().filter(|area| {
|
||||
let address = area.base_addr + area.length - 1;
|
||||
@@ -326,9 +350,15 @@ If the `next_free_frame` is below the new `current_area`, it needs to be updated
|
||||
We don't have a data structure to store free frames, so we can't implement `deallocate_frame` reasonably. Thus we use the `unimplemented` macro, which just panics when the method is called:
|
||||
|
||||
```rust
|
||||
impl FrameAllocator for AreaFrameAllocator {
|
||||
fn allocate_frame(&mut self) -> Option<Frame> {
|
||||
// described above
|
||||
}
|
||||
|
||||
fn deallocate_frame(&mut self, _frame: Frame) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we only need a constructor function to make the allocator usable:
|
||||
@@ -392,10 +422,10 @@ Now we have a working frame allocator. It is a bit rudimentary and cannot free f
|
||||
## What's next?
|
||||
The [next post] will be about paging again. We will use the frame allocator to create a safe module that allows us to switch page tables and map pages. Then we will use this module and the information from the Elf-sections tag to remap the kernel correctly.
|
||||
|
||||
[next post]: {{ page.next.url }}
|
||||
[next post]: {{% relref "2015-12-09-modifying-page-tables.md" %}}
|
||||
|
||||
## Recommended Posts
|
||||
Eric Kidd started the [Bare Metal Rust] series last week. Like this post, it builds upon the code from [Printing to Screen], but tries to support keyboard input instead of wrestling through memory management details ;).
|
||||
Eric Kidd started the [Bare Metal Rust] series last week. Like this post, it builds upon the code from [Printing to Screen], but tries to support keyboard input instead of wrestling through memory management details.
|
||||
|
||||
[Bare Metal Rust]: http://www.randomhacks.net/bare-metal-rust/
|
||||
[Printing to Screen]: {% post_url 2015-10-23-printing-to-screen %}
|
||||
[Printing to Screen]: {{% relref "2015-10-23-printing-to-screen.md" %}}
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'Page Tables'
|
||||
---
|
||||
+++
|
||||
title = "Page Tables"
|
||||
slug = "modifying-page-tables"
|
||||
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.
|
||||
|
||||
<!--more-->
|
||||
|
||||
You can find the source code and this post itself on [Github][source repository]. Please file an issue there if you have any problems or improvement suggestions. There is also a comment section at the end of this page. Note that this post requires a current Rust nightly.
|
||||
|
||||
[source repository]: https://github.com/phil-opp/blog_os/tree/page_tables
|
||||
@@ -46,7 +49,7 @@ pub struct Page {
|
||||
```
|
||||
We import the `PAGE_SIZE` and define a constant for the number of entries per table. To make future function signatures more expressive, we can use the type aliases `PhysicalAddress` and `VirtualAddress`. The `Page` struct is similar to the `Frame` struct in the [previous post], but represents a virtual page instead of a physical frame.
|
||||
|
||||
[previous post]: {{ page.previous.url }}#a-memory-module
|
||||
[previous post]: {{% relref "2015-11-15-allocating-frames.md#a-memory-module" %}}
|
||||
|
||||
### Page Table Entries
|
||||
To model page table entries, we create a new `entry` submodule:
|
||||
@@ -98,7 +101,13 @@ features = ["no_std"]
|
||||
The `no_std` feature is needed because `bitflags` depends on the standard library by default. But it has a [cargo feature] to use the core library instead. It will become the default as soon as `no_std` is stable in a stable Rust release.
|
||||
[cargo feature]: http://doc.crates.io/manifest.html#the-[features]-section
|
||||
|
||||
Note that you need a `#[macro_use]` above the `extern crate` definition.
|
||||
To import the macro, we need to use `#[macro_use]` above the `extern crate` definition:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
```
|
||||
|
||||
Now we can model the various flags:
|
||||
|
||||
@@ -134,7 +143,9 @@ To extract the physical address, we add a `pointed_frame` method:
|
||||
```rust
|
||||
pub fn pointed_frame(&self) -> Option<Frame> {
|
||||
if self.flags().contains(PRESENT) {
|
||||
Some(Frame::containing_address(self.0 as usize & 0x000fffff_fffff000))
|
||||
Some(Frame::containing_address(
|
||||
self.0 as usize & 0x000fffff_fffff000
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -316,7 +327,9 @@ Note that `self` stays borrowed as long as the returned reference is valid. This
|
||||
```rust
|
||||
pub fn next_table<'a>(&'a self, index: usize) -> Option<&'a Table> {...}
|
||||
|
||||
pub fn next_table_mut<'a>(&'a mut self, index: usize) -> Option<&'a mut Table> {...}
|
||||
pub fn next_table_mut<'a>(&'a mut self, index: usize)
|
||||
-> Option<&'a mut Table>
|
||||
{...}
|
||||
```
|
||||
|
||||
Note the additional lifetime parameters, which are identical for input and output references. That's exactly what we want. It ensures that we can't modify tables as long as we have references to lower tables. For example, it would be very bad if we could unmap a P3 table if we still write to one of its P2 tables.
|
||||
@@ -394,7 +407,8 @@ impl<L> Table<L> where L: HierarchicalLevel
|
||||
{
|
||||
pub fn next_table(&self, index: usize) -> Option<&Table<???>> {...}
|
||||
|
||||
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<???>> {...}
|
||||
pub fn next_table_mut(&mut self, index: usize) -> Option<&mut Table<???>>
|
||||
{...}
|
||||
|
||||
fn next_table_address(&self, index: usize) -> Option<usize> {...}
|
||||
}
|
||||
@@ -451,7 +465,9 @@ Remember that this is bare metal kernel code. We just used type system magic to
|
||||
Now let's do something useful with our new module. We will create a function that translates a virtual address to the corresponding physical address. We add it to the `paging/mod.rs` module:
|
||||
|
||||
```rust
|
||||
pub fn translate(virtual_address: VirtualAddress) -> Option<PhysicalAddress> {
|
||||
pub fn translate(virtual_address: VirtualAddress)
|
||||
-> Option<PhysicalAddress>
|
||||
{
|
||||
let offset = virtual_address % PAGE_SIZE;
|
||||
translate_page(Page::containing_address(virtual_address))
|
||||
.map(|frame| frame.number * PAGE_SIZE + offset)
|
||||
@@ -461,7 +477,8 @@ It uses two functions we haven't defined yet: `translate_page` and `Page::contai
|
||||
|
||||
```rust
|
||||
pub fn containing_address(address: VirtualAddress) -> Page {
|
||||
assert!(address < 0x0000_8000_0000_0000 || address >= 0xffff_8000_0000_0000,
|
||||
assert!(address < 0x0000_8000_0000_0000 ||
|
||||
address >= 0xffff_8000_0000_0000,
|
||||
"invalid address: 0x{:x}", address);
|
||||
Page { number: address / PAGE_SIZE }
|
||||
}
|
||||
@@ -540,8 +557,8 @@ p3.and_then(|p3| {
|
||||
// address must be 1GiB aligned
|
||||
assert!(start_frame.number % (ENTRY_COUNT * ENTRY_COUNT) == 0);
|
||||
return Some(Frame {
|
||||
number: start_frame.number + page.p2_index() * ENTRY_COUNT +
|
||||
page.p1_index(),
|
||||
number: start_frame.number + page.p2_index() *
|
||||
ENTRY_COUNT + page.p1_index(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -570,7 +587,8 @@ Let's add a function that modifies the page tables to map a `Page` to a `Frame`:
|
||||
pub use self::entry::*;
|
||||
use memory::FrameAllocator;
|
||||
|
||||
pub fn map_to<A>(page: Page, frame: Frame, flags: EntryFlags, allocator: &mut A)
|
||||
pub fn map_to<A>(page: Page, frame: Frame, flags: EntryFlags,
|
||||
allocator: &mut A)
|
||||
where A: FrameAllocator
|
||||
{
|
||||
let p4 = unsafe { &mut *P4 };
|
||||
@@ -635,7 +653,7 @@ pub struct ActivePageTable {
|
||||
```
|
||||
We can't store the `Table<Level4>` directly because it needs to be at a special memory location (like the [VGA text buffer]). We could use a raw pointer or `&mut` instead of [Unique], but Unique indicates ownership better.
|
||||
|
||||
[VGA text buffer]: http://os.phil-opp.com/printing-to-screen.html#the-text-buffer
|
||||
[VGA text buffer]: {{% relref "2015-10-23-printing-to-screen.md#the-text-buffer" %}}
|
||||
[Unique]: https://doc.rust-lang.org/nightly/core/ptr/struct.Unique.html
|
||||
|
||||
Because the `ActivePageTable` owns the unique recursive mapped P4 table, there must be only one `ActivePageTable` instance. Thus we make the constructor function unsafe:
|
||||
@@ -920,7 +938,9 @@ This post has become pretty long. So let's summarize what we've done:
|
||||
- and we fixed stack overflow and TLB related bugs
|
||||
|
||||
## What's next?
|
||||
In the next post we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment.
|
||||
In the [next post] we will extend this module and add a function to modify inactive page tables. Through that function, we will create a new page table hierarchy that maps the kernel correctly using 4KiB pages. Then we will switch to the new table to get a safer kernel environment.
|
||||
|
||||
[next post]: {{% relref "2016-01-01-remap-the-kernel.md" %}}
|
||||
|
||||
Afterwards, we will use this paging module to build a heap allocator. This will allow us to use allocation and collection types such as `Box` and `Vec`.
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'Remap the Kernel'
|
||||
updated: 2016-03-06 00:00:00 +0000
|
||||
---
|
||||
+++
|
||||
title = "Remap the Kernel"
|
||||
date = "2016-01-01"
|
||||
updated = "2016-03-06"
|
||||
+++
|
||||
|
||||
In this post we will create a new page table to map the kernel sections correctly. Therefor we will extend the paging module to support modifications of _inactive_ page tables as well. Then we will switch to the new table and secure our kernel stack by creating a guard page.
|
||||
|
||||
<!--more-->
|
||||
|
||||
As always, you can find the source code on [Github]. Don't hesitate to file issues there if you have any problems or improvement suggestions. There is also a comment section at the end of this page. Note that this post requires a current Rust nightly.
|
||||
|
||||
[Github]: https://github.com/phil-opp/blog_os/tree/remap_the_kernel
|
||||
@@ -26,16 +29,16 @@ _Updates_:
|
||||
## Motivation
|
||||
|
||||
In the [previous post], we had a strange bug in the `unmap` function. Its reason was a silent stack overflow, which corrupted the page tables. Fortunately, our kernel stack is right above the page tables so that we noticed the overflow relatively quickly. This won't be the case when we add threads with new stacks in the future. Then a silent stack overflow could overwrite some data without us noticing. But eventually some completely unrelated function fails because a variable changed its value.
|
||||
[previous post]: {{ page.previous.url }}
|
||||
[previous post]: {{% relref "2015-12-09-modifying-page-tables.md" %}}
|
||||
|
||||
As you can imagine, these kinds of bugs are horrendous to debug. For that reason we will create a new hierarchical page table in this post, which has _guard page_ below the stack. A guard page is basically an unmapped page that causes a page fault when accessed. Thus we can catch stack overflows right when they happen.
|
||||
|
||||
Also, we will use the [information about kernel sections] to map the various sections individually instead of blindly mapping the first gigabyte. To improve safety even further, we will set the correct page table flags for the various sections. Thus it won't be possible to modify the contents of `.text` or to execute code from `.data` anymore.
|
||||
[information about kernel sections]: {% post_url 2015-11-15-allocating-frames %}#kernel-elf-sections
|
||||
[information about kernel sections]: {{% relref "2015-11-15-allocating-frames.md#kernel-elf-sections" %}}
|
||||
|
||||
## Preparation
|
||||
There are many things that can go wrong when we switch to a new table. Therefore it's a good idea to [set up a debugger][set up gdb]. You should not need it when you follow this post, but it's good to know how to debug a problem when it occurs[^fn-debug-notes].
|
||||
[set up gdb]: /set-up-gdb.html
|
||||
[set up gdb]: {{% relref "set-up-gdb.md" %}}
|
||||
[^fn-debug-notes]: For this post the most useful GDB command is probably `p/x *((long int*)0xfffffffffffff000)@512`. It prints all entries of the recursively mapped P4 table by interpreting it as an array of 512 long ints (the `@512` is GDB's array syntax). Of course you can also print other tables by adjusting the address.
|
||||
|
||||
We also update the `Page` and `Frame` types to make our lives easier. The `Page` struct gets some derived traits:
|
||||
@@ -274,8 +277,8 @@ Now our `TemporaryPage` type is nearly complete. We only add one more method for
|
||||
```rust
|
||||
use super::table::{Table, Level1};
|
||||
|
||||
/// Maps the temporary page to the given page table frame in the active table.
|
||||
/// Returns a reference to the now mapped table.
|
||||
/// Maps the temporary page to the given page table frame in the active
|
||||
/// table. Returns a reference to the now mapped table.
|
||||
pub fn map_table_frame(&mut self,
|
||||
frame: Frame,
|
||||
active_table: &mut ActivePageTable)
|
||||
@@ -284,7 +287,7 @@ pub fn map_table_frame(&mut self,
|
||||
}
|
||||
```
|
||||
This function interprets the given frame as a page table frame and returns a `Table` reference. We return a table of level 1 because it [forbids calling the `next_table` methods][some clever solution]. Calling `next_table` must not be possible since it's not a page of the recursive mapping. To be able to return a `Table<Level1>`, we need to make the `Level1` enum in `memory/paging/table.rs` public.
|
||||
[some clever solution]: {% post_url 2015-12-09-modifying-page-tables %}#some-clever-solution
|
||||
[some clever solution]: {{% relref "2015-12-09-modifying-page-tables.md#some-clever-solution" %}}
|
||||
|
||||
|
||||
The `unsafe` block is safe since the `VirtualAddress` returned by the `map` function is always valid and the type cast just reinterprets the frame's content.
|
||||
@@ -316,7 +319,8 @@ impl InactivePageTable {
|
||||
temporary_page: &mut TemporaryPage)
|
||||
-> InactivePageTable {
|
||||
{
|
||||
let table = temporary_page.map_table_frame(frame.clone(), active_table);
|
||||
let table = temporary_page.map_table_frame(frame.clone(),
|
||||
active_table);
|
||||
// now we are able to zero the table
|
||||
table.zero();
|
||||
// set up recursive mapping for the table
|
||||
@@ -460,7 +464,9 @@ To backup the physical P4 frame of the active table, we can either read it from
|
||||
|
||||
```rust
|
||||
use x86::controlregs;
|
||||
let backup = Frame::containing_address(unsafe { controlregs::cr3() } as usize);
|
||||
let backup = Frame::containing_address(
|
||||
unsafe { controlregs::cr3() } as usize
|
||||
);
|
||||
```
|
||||
Why is it unsafe? Because reading the CR3 register leads to a CPU exception if the processor is not running in kernel mode ([Ring 0]). But this code will always run in kernel mode, so the `unsafe` block is completely safe here.
|
||||
|
||||
@@ -551,7 +557,7 @@ pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
|
||||
First, we create a temporary page at page number `0xcafebabe`. We could use `0xdeadbeaf` or `0x123456789` as well, as long as the page is unused. The `active_table` and the `new_table` are created using their constructor functions.
|
||||
|
||||
Then we use the `with` function to temporary change the recursive mapping and execute the closure as if the `new_table` were active. This allows us to map the sections in the new table without changing the active mapping. To get the kernel sections, we use the [Multiboot information structure].
|
||||
[Multiboot information structure]: {% post_url 2015-11-15-allocating-frames %}#the-multiboot-information-structure
|
||||
[Multiboot information structure]: {{% relref "2015-11-15-allocating-frames.md#the-multiboot-information-structure" %}}
|
||||
|
||||
Let's resolve the above `TODO` by identity mapping the sections:
|
||||
|
||||
@@ -692,7 +698,9 @@ Time to test it! We reexport the `remap_the_kernel` function from the memory mod
|
||||
```rust
|
||||
// in src/memory/mod.rs
|
||||
pub use self::paging::remap_the_kernel;
|
||||
```
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
||||
@@ -702,15 +710,18 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
||||
vga_buffer::clear_screen();
|
||||
println!("Hello World{}", "!");
|
||||
|
||||
let boot_info = unsafe { multiboot2::load(multiboot_information_address) };
|
||||
let boot_info = unsafe {
|
||||
multiboot2::load(multiboot_information_address)
|
||||
};
|
||||
let memory_map_tag = boot_info.memory_map_tag()
|
||||
.expect("Memory map tag required");
|
||||
let elf_sections_tag = boot_info.elf_sections_tag()
|
||||
.expect("Elf sections tag required");
|
||||
|
||||
let kernel_start = elf_sections_tag.sections().map(|s| s.addr).min().unwrap();
|
||||
let kernel_end = elf_sections_tag.sections().map(|s| s.addr + s.size).max()
|
||||
.unwrap();
|
||||
let kernel_start = elf_sections_tag.sections().map(|s| s.addr)
|
||||
.min().unwrap();
|
||||
let kernel_end = elf_sections_tag.sections().map(|s| s.addr + s.size)
|
||||
.max().unwrap();
|
||||
|
||||
let multiboot_start = multiboot_information_address;
|
||||
let multiboot_end = multiboot_start + (boot_info.total_size as usize);
|
||||
@@ -747,7 +758,9 @@ pub fn switch(&mut self, new_table: InactivePageTable) -> InactivePageTable {
|
||||
use x86::controlregs;
|
||||
|
||||
let old_table = InactivePageTable {
|
||||
p4_frame: Frame::containing_address(unsafe { controlregs::cr3() } as usize),
|
||||
p4_frame: Frame::containing_address(
|
||||
unsafe { controlregs::cr3() } as usize
|
||||
),
|
||||
};
|
||||
unsafe {
|
||||
controlregs::cr3_write(new_table.p4_frame.start_address() as u64);
|
||||
@@ -777,7 +790,7 @@ Let's cross our fingers and run it…
|
||||
|
||||
### Debugging
|
||||
A QEMU boot load indicates that some CPU exception occured. We can see all thrown CPU exception by starting QEMU with `-d int` (as described [here][qemu debugging]):
|
||||
[qemu debugging]: {% post_url 2015-09-02-set-up-rust %}#debugging
|
||||
[qemu debugging]: {{% relref "2015-09-02-set-up-rust.md#debugging" %}}
|
||||
|
||||
```bash
|
||||
> qemu-system-x86_64 -d int -no-reboot -cdrom build/os-x86_64.iso
|
||||
@@ -797,12 +810,12 @@ These lines are the important ones. We can read many useful information from the
|
||||
[page fault error code]: http://wiki.osdev.org/Exceptions#Error_code
|
||||
|
||||
- `IP=0008:000000000010ab97` or `pc=000000000010ab97`: The program counter register tells us that the exception occurred when the CPU tried to execute the instruction at `0x10ab97`. We can disassemble this address to see the corresponding function. The `0008:` prefix in `IP` indicates the code [GDT segment].
|
||||
[GDT segment]: {% post_url 2015-08-25-entering-longmode %}#loading-the-gdt
|
||||
[GDT segment]: {{% relref "2015-08-25-entering-longmode.md#loading-the-gdt" %}}
|
||||
|
||||
- `SP=0010:00000000001182d0`: The stack pointer was `0x1182d0` (the `0010:` prefix indicates the data [GDT segment]). This tells us if it the stack overflowed.
|
||||
|
||||
- `CR2=00000000000b8f00`: Finally the most useful register. It tells us which virtual address caused the page fault. In our case it's `0xb8f00`, which is part of the [VGA text buffer].
|
||||
[VGA text buffer]: {% post_url 2015-10-23-printing-to-screen %}#the-vga-text-buffer
|
||||
[VGA text buffer]: {{% relref "2015-10-23-printing-to-screen.md#the-vga-text-buffer" %}}
|
||||
|
||||
So let's find out which function caused the exception:
|
||||
|
||||
@@ -997,7 +1010,7 @@ If we haven't forgotten to set the `WRITABLE` flag somewhere, it should still wo
|
||||
The final step is to create a guard page for our kernel stack.
|
||||
|
||||
The decision to place the kernel stack right above the page tables was already useful to detect a silent stack overflow in the [previous post][silent stack overflow]. Now we profit from it again. Let's look at our assembly `.bss` section again to understand why:
|
||||
[silent stack overflow]: {% post_url 2015-12-09-modifying-page-tables %}#translate
|
||||
[silent stack overflow]: {{% relref "2015-12-09-modifying-page-tables.md#translate" %}}
|
||||
|
||||
```nasm
|
||||
; in src/arch/x86_64/boot.asm
|
||||
@@ -1031,7 +1044,9 @@ pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
|
||||
// below is the new part
|
||||
|
||||
// turn the old p4 page into a guard page
|
||||
let old_p4_page = Page::containing_address(old_table.p4_frame.start_address());
|
||||
let old_p4_page = Page::containing_address(
|
||||
old_table.p4_frame.start_address()
|
||||
);
|
||||
active_table.unmap(old_p4_page, allocator);
|
||||
println!("guard page at {:#x}", old_p4_page.start_address());
|
||||
}
|
||||
@@ -1055,7 +1070,7 @@ Unfortunately stack probes require compiler support. They already work on Window
|
||||
|
||||
## What's next?
|
||||
Now that we have a (mostly) safe kernel stack and a working page table module, we can add a virtual memory allocator. The [next post] will explore Rust's allocator API and create a very basic allocator. At the end of that post, we will be able to use Rust's allocation and collections types such as [Box], [Vec], or even [BTreeMap].
|
||||
[next post]: {{ page.next.url }}
|
||||
[next post]: {{% relref "2016-04-11-kernel-heap.md" %}}
|
||||
[Box]: https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html
|
||||
[Vec]: https://doc.rust-lang.org/nightly/collections/vec/struct.Vec.html
|
||||
[BTreeMap]: https://doc.rust-lang.org/nightly/collections/struct.BTreeMap.html
|
||||
@@ -1,15 +1,17 @@
|
||||
---
|
||||
layout: post
|
||||
title: 'Kernel Heap'
|
||||
---
|
||||
+++
|
||||
title = "Kernel Heap"
|
||||
date = "2016-04-11"
|
||||
+++
|
||||
|
||||
In the previous posts we have created a [frame allocator] and a [page table module]. Now we are ready to create a kernel heap and a memory allocator. Thus, we will unlock `Box`, `Vec`, `BTreeMap`, and the rest of the [alloc] and [collections] crates.
|
||||
|
||||
[frame allocator]: {% post_url 2015-11-15-allocating-frames %}
|
||||
[page table module]: {% post_url 2015-12-09-modifying-page-tables %}
|
||||
[frame allocator]: {{% relref "2015-11-15-allocating-frames.md" %}}
|
||||
[page table module]: {{% relref "2015-12-09-modifying-page-tables.md" %}}
|
||||
[alloc]: https://doc.rust-lang.org/nightly/alloc/index.html
|
||||
[collections]: https://doc.rust-lang.org/nightly/collections/index.html
|
||||
|
||||
<!--more-->
|
||||
|
||||
As always, you can find the complete source code on [Github]. Please file [issues] for any problems, questions, or improvement suggestions. There is also a comment section at the end of this page.
|
||||
|
||||
[Github]: https://github.com/phil-opp/blog_os/tree/kernel_heap
|
||||
@@ -318,7 +320,7 @@ let heap_test = Box::new(42);
|
||||
When we try to compile it using `make run`, we get several linker errors about a function named `_Unwind_Resume`:
|
||||
|
||||
```
|
||||
target/x86_64-unknown-linux-gnu/debug/libblog_os.a(bump_allocator-947b648f2a584929.0.o):
|
||||
target/x86_64-unknown-linux-gnu/debug/libblog_os.a(bump_allocator-[…].0.o):
|
||||
In function `bump_allocator::__rust_allocate':
|
||||
/home/…/blog_os/libs/bump_allocator/src/lib.rs:19:
|
||||
undefined reference to `_Unwind_Resume'
|
||||
@@ -459,8 +461,8 @@ The crate provides an [assert_has_not_been_called!] macro (sorry for the long na
|
||||
pub fn init(boot_info: &BootInformation) {
|
||||
assert_has_not_been_called!("memory::init must be called only once");
|
||||
|
||||
let memory_map_tag = …
|
||||
…
|
||||
let memory_map_tag = ...
|
||||
...
|
||||
}
|
||||
```
|
||||
That's it. Now our `memory::init` function can only be called once. The macro works by creating a static [AtomicBool] named `CALLED`, which is initialized to `false`. When the macro is invoked, it checks the value of `CALLED` and sets it to `true`. If the value was already `true` before, the macro panics.
|
||||
@@ -479,7 +481,7 @@ pub fn remap_the_kernel<A>(allocator: &mut A, boot_info: &BootInformation)
|
||||
-> ActivePageTable // new
|
||||
where A: FrameAllocator
|
||||
{
|
||||
…
|
||||
...
|
||||
println!("guard page at {:#x}", old_p4_page.start_address());
|
||||
|
||||
active_table // new
|
||||
@@ -492,9 +494,9 @@ Now we have full page table access in the `memory::init` function. This allows u
|
||||
// in src/memory/mod.rs
|
||||
|
||||
pub fn init(boot_info: &BootInformation) {
|
||||
…
|
||||
...
|
||||
|
||||
let mut frame_allocator = …;
|
||||
let mut frame_allocator = ...;
|
||||
|
||||
// below is the new part
|
||||
|
||||
@@ -518,10 +520,10 @@ The `Page::range_inclusive` function is just a copy of the `Frame::range_inclusi
|
||||
// in src/memory/paging/mod.rs
|
||||
|
||||
#[derive(…, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Page {…}
|
||||
pub struct Page {...}
|
||||
|
||||
impl Page {
|
||||
…
|
||||
...
|
||||
pub fn range_inclusive(start: Page, end: Page) -> PageIter {
|
||||
PageIter {
|
||||
start: start,
|
||||
Reference in New Issue
Block a user