Merge pull request #566 from phil-opp/x86_64-0.5.0

Update to version 0.5.0 of x86_64
This commit is contained in:
Philipp Oppermann
2019-03-09 13:51:39 +01:00
committed by GitHub
6 changed files with 93 additions and 92 deletions

View File

@@ -193,13 +193,13 @@ The `iobase` specifies on which port address the device should live (`0xf4` is a
To write to the I/O port, we use the [`x86_64`] crate:
[`x86_64`]: https://docs.rs/x86_64
[`x86_64`]: https://docs.rs/x86_64/0.5.0/x86_64/
```toml
# in Cargo.toml
[dependencies]
x86_64 = "0.4.0"
x86_64 = "0.5.0"
```
```rust

View File

@@ -80,7 +80,7 @@ Don't worry about steps 4 and 5 for now, we will learn about the global descript
## An IDT Type
Instead of creating our own IDT type, we will use the [`InterruptDescriptorTable` struct] of the `x86_64` crate, which looks like this:
[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.InterruptDescriptorTable.html
[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/struct.InterruptDescriptorTable.html
``` rust
#[repr(C)]
@@ -111,15 +111,15 @@ pub struct InterruptDescriptorTable {
The fields have the type [`idt::Entry<F>`], which is a struct that represents the fields of an IDT entry (see the table above). The type parameter `F` defines the expected handler function type. We see that some entries require a [`HandlerFunc`] and some entries require a [`HandlerFuncWithErrCode`]. The page fault even has its own special type: [`PageFaultHandlerFunc`].
[`idt::Entry<F>`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.Entry.html
[`HandlerFunc`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/type.HandlerFunc.html
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/type.PageFaultHandlerFunc.html
[`idt::Entry<F>`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/struct.Entry.html
[`HandlerFunc`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/type.HandlerFunc.html
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/type.PageFaultHandlerFunc.html
Let's look at the `HandlerFunc` type first:
```rust
type HandlerFunc = extern "x86-interrupt" fn(_: &mut ExceptionStackFrame);
type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame);
```
It's a [type alias] for an `extern "x86-interrupt" fn` type. The `extern` keyword defines a function with a [foreign calling convention] and is often used to communicate with C code (`extern "C" fn`). But what is the `x86-interrupt` calling convention?
@@ -166,7 +166,7 @@ In contrast to function calls, exceptions can occur on _any_ instruction. In mos
Since we don't know when an exception occurs, we can't backup any registers before. This means that we can't use a calling convention that relies on caller-saved registers for exception handlers. Instead, we need a calling convention means that preserves _all registers_. The `x86-interrupt` calling convention is such a calling convention, so it guarantees that all register values are restored to their original values on function return.
### The Exception Stack Frame
### The Interrupt Stack Frame
On a normal function call (using the `call` instruction), the CPU pushes the return address before jumping to the target function. On function return (using the `ret` instruction), the CPU pops this return address and jumps to it. So the stack frame of a normal function call looks like this:
![function stack frame](function-stack-frame.svg)
@@ -183,13 +183,13 @@ For exception and interrupt handlers, however, pushing a return address would no
[`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register
So the _exception stack frame_ looks like this:
So the _interrupt stack frame_ looks like this:
![exception stack frame](exception-stack-frame.svg)
![interrupt stack frame](exception-stack-frame.svg)
In the `x86_64` crate, the exception stack frame is represented by the [`ExceptionStackFrame`] struct. It is passed to interrupt handlers as `&mut` and can be used to retrieve additional information about the exception's cause. The struct contains no error code field, since only some few exceptions push an error code. These exceptions use the separate [`HandlerFuncWithErrCode`] function type, which has an additional `error_code` argument.
In the `x86_64` crate, the interrupt stack frame is represented by the [`InterruptStackFrame`] struct. It is passed to interrupt handlers as `&mut` and can be used to retrieve additional information about the exception's cause. The struct contains no error code field, since only some few exceptions push an error code. These exceptions use the separate [`HandlerFuncWithErrCode`] function type, which has an additional `error_code` argument.
[`ExceptionStackFrame`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.ExceptionStackFrame.html
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/struct.InterruptStackFrame.html
Note that there is currently [a bug in LLVM] that leads to wrong error code arguments. The cause of the issue is already known and a solution is [being worked on].
@@ -201,7 +201,7 @@ Note that there is currently [a bug in LLVM] that leads to wrong error code argu
The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of:
- **Retrieving the arguments**: Most calling conventions expect that the arguments are passed in registers. This is not possible for exception handlers, since we must not overwrite any register values before backing them up on the stack. Instead, the `x86-interrupt` calling convention is aware that the arguments already lie on the stack at a specific offset.
- **Returning using `iretq`**: Since the exception stack frame completely differs from stack frames of normal function calls, we can't return from handlers functions through the normal `ret` instruction. Instead, the `iretq` instruction must be used.
- **Returning using `iretq`**: Since the interrupt stack frame completely differs from stack frames of normal function calls, we can't return from handlers functions through the normal `ret` instruction. Instead, the `iretq` instruction must be used.
- **Handling the error code**: The error code, which is pushed for some exceptions, makes things much more complex. It changes the stack alignment (see the next point) and needs to be popped off the stack before returning. The `x86-interrupt` calling convention handles all that complexity. However, it doesn't know which handler function is used for which exception, so it needs to deduce that information from the number of function arguments. That means that the programmer is still responsible to use the correct function type for each exception. Luckily, the `InterruptDescriptorTable` type defined by the `x86_64` crate ensures that the correct function types are used.
- **Aligning the stack**: There are some instructions (especially SSE instructions) that require a 16-byte stack alignment. The CPU ensures this alignment whenever an exception occurs, but for some exceptions it destroys it again later when it pushes an error code. The `x86-interrupt` calling convention takes care of this by realigning the stack in this case.
@@ -240,7 +240,7 @@ For our use case, we don't need to overwrite any instructions. Instead, we just
```rust
// in src/interrupts.rs
use x86_64::structures::idt::{InterruptDescriptorTable, ExceptionStackFrame};
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
use crate::println;
pub fn init_idt() {
@@ -249,13 +249,13 @@ pub fn init_idt() {
}
extern "x86-interrupt" fn breakpoint_handler(
stack_frame: &mut ExceptionStackFrame)
stack_frame: &mut InterruptStackFrame)
{
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}
```
Our handler just outputs a message and pretty-prints the exception stack frame.
Our handler just outputs a message and pretty-prints the interrupt stack frame.
When we try to compile it, the following error occurs:
@@ -263,7 +263,7 @@ When we try to compile it, the following error occurs:
error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue #40180)
--> src/main.rs:53:1
|
53 | / extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) {
53 | / extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) {
54 | | println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
55 | | }
| |_^
@@ -277,7 +277,7 @@ This error occurs because the `x86-interrupt` calling convention is still unstab
In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it:
[`lidt`]: http://x86.renejeschke.de/html/file_module_x86_id_156.html
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
```rust
// in src/interrupts.rs
@@ -383,7 +383,7 @@ pub extern "C" fn _start() -> ! {
blog_os::interrupts::init_idt();
// invoke a breakpoint exception
x86_64::instructions::int3();
x86_64::instructions::interrupts::int3();
println!("It did not crash!");
loop {}
@@ -392,11 +392,11 @@ pub extern "C" fn _start() -> ! {
When we run it in QEMU now (using `bootimage run`), we see the following:
![QEMU printing `EXCEPTION: BREAKPOINT` and the exception stack frame](qemu-breakpoint-exception.png)
![QEMU printing `EXCEPTION: BREAKPOINT` and the interrupt stack frame](qemu-breakpoint-exception.png)
It works! The CPU successfully invokes our breakpoint handler, which prints the message, and then returns back to the `_start` function, where the `It did not crash!` message is printed.
We see that the exception stack frame tells us the instruction and stack pointers at the time when the exception occured. This information is very useful when debugging unexpected exceptions.
We see that the interrupt stack frame tells us the instruction and stack pointers at the time when the exception occured. This information is very useful when debugging unexpected exceptions.
### Adding a Test
@@ -417,7 +417,7 @@ use blog_os::{exit_qemu, serial_println};
pub extern "C" fn _start() -> ! {
blog_os::interrupts::init_idt();
x86_64::instructions::int3();
x86_64::instructions::interrupts::int3();
serial_println!("ok");
@@ -476,7 +476,7 @@ Both variants have the exact same effects, so it comes down to personal preferen
The `x86-interrupt` calling convention and the [`InterruptDescriptorTable`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own IDT type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed. Note that these posts are based on the [first edition] of this blog and might be out of date.
[“Handling Exceptions with Naked Functions”]: ./first-edition/extra/naked-exceptions/_index.md
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.InterruptDescriptorTable.html
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/struct.InterruptDescriptorTable.html
[first edition]: ./first-edition/_index.md
## What's next?

View File

@@ -74,7 +74,7 @@ lazy_static! {
// new
extern "x86-interrupt" fn double_fault_handler(
stack_frame: &mut ExceptionStackFrame, _error_code: u64)
stack_frame: &mut InterruptStackFrame, _error_code: u64)
{
println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
loop {}
@@ -223,7 +223,7 @@ The _Privilege Stack Table_ is used by the CPU when the privilege level changes.
### Creating a TSS
Let's create a new TSS that contains a separate double fault stack in its interrupt stack table. For that we need a TSS struct. Fortunately, the `x86_64` crate already contains a [`TaskStateSegment` struct] that we can use.
[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.2.8/x86_64/structures/tss/struct.TaskStateSegment.html
[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.5.0/x86_64/structures/tss/struct.TaskStateSegment.html
We create the TSS in a new `gdt` module (the name will make sense later):
@@ -376,8 +376,8 @@ pub fn init() {
We reload the code segment register using [`set_cs`] and to load the TSS using [`load_tss`]. The functions are marked as `unsafe`, so we need an `unsafe` block to invoke them. The reason is that it might be possible to break memory safety by loading invalid selectors.
[`set_cs`]: https://docs.rs/x86_64/0.2.8/x86_64/instructions/segmentation/fn.set_cs.html
[`load_tss`]: https://docs.rs/x86_64/0.2.8/x86_64/instructions/tables/fn.load_tss.html
[`set_cs`]: https://docs.rs/x86_64/0.5.0/x86_64/instructions/segmentation/fn.set_cs.html
[`load_tss`]: https://docs.rs/x86_64/0.5.0/x86_64/instructions/tables/fn.load_tss.html
Now that we loaded a valid TSS and interrupt stack table, we can set the stack index for our double fault handler in the IDT:

View File

@@ -214,7 +214,7 @@ lazy_static! {
}
extern "x86-interrupt" fn timer_interrupt_handler(
_stack_frame: &mut ExceptionStackFrame)
_stack_frame: &mut InterruptStackFrame)
{
print!(".");
}
@@ -222,7 +222,7 @@ extern "x86-interrupt" fn timer_interrupt_handler(
Our `timer_interrupt_handler` has the same signature as our exception handlers, because the CPU reacts identically to exceptions and external interrupts (the only difference is that some exceptions push an error code). The [`InterruptDescriptorTable`] struct implements the [`IndexMut`] trait, so we can access individual entries through array indexing syntax.
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.2.11/x86_64/structures/idt/struct.InterruptDescriptorTable.html
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/struct.InterruptDescriptorTable.html
[`IndexMut`]: https://doc.rust-lang.org/core/ops/trait.IndexMut.html
In our timer interrupt handler, we print a dot to the screen. As the timer interrupt happens periodically, we would expect to see a dot appearing on each timer tick. However, when we run it we see that only a single dot is printed:
@@ -239,7 +239,7 @@ To send the EOI, we use our static `PICS` struct again:
// in src/interrupts.rs
extern "x86-interrupt" fn timer_interrupt_handler(
_stack_frame: &mut ExceptionStackFrame)
_stack_frame: &mut InterruptStackFrame)
{
print!(".");
@@ -348,7 +348,7 @@ pub fn _print(args: fmt::Arguments) {
The [`without_interrupts`] function takes a [closure] and executes it in an interrupt-free environment. We use it to ensure that no interrupt can occur as long as the `Mutex` is locked. When we run our kernel now we see that it keeps running without hanging. (We still don't notice any dots, but this is because they're scrolling by too fast. Try to slow down the printing, e.g. by putting a `for _ in 0..10000 {}` inside the loop.)
[`without_interrupts`]: https://docs.rs/x86_64/0.2.10/x86_64/instructions/interrupts/fn.without_interrupts.html
[`without_interrupts`]: https://docs.rs/x86_64/0.5.0/x86_64/instructions/interrupts/fn.without_interrupts.html
[closure]: https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html
We can apply the same change to our serial printing function to ensure that no deadlocks occur with it either:
@@ -426,7 +426,7 @@ We can also use `hlt_loop` in our double fault exception handler as well:
use crate::hlt_loop; // new
extern "x86-interrupt" fn double_fault_handler(
stack_frame: &mut ExceptionStackFrame,
stack_frame: &mut InterruptStackFrame,
_error_code: u64,
) {
println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
@@ -476,7 +476,7 @@ lazy_static! {
}
extern "x86-interrupt" fn keyboard_interrupt_handler(
_stack_frame: &mut ExceptionStackFrame)
_stack_frame: &mut InterruptStackFrame)
{
print!("k");
@@ -501,7 +501,7 @@ To find out _which_ key was pressed, we need to query the keyboard controller. W
// in src/interrupts.rs
extern "x86-interrupt" fn keyboard_interrupt_handler(
_stack_frame: &mut ExceptionStackFrame)
_stack_frame: &mut InterruptStackFrame)
{
use x86_64::instructions::port::Port;
@@ -518,7 +518,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(
We use the [`Port`] type of the `x86_64` crate to read a byte from the keyboard's data port. This byte is called the [_scancode_] and is a number that represents the key press/release. We don't do anything with the scancode yet, we just print it to the screen:
[`Port`]: https://docs.rs/x86_64/0.2.11/x86_64/instructions/port/struct.Port.html
[`Port`]: https://docs.rs/x86_64/0.5.0/x86_64/instructions/port/struct.Port.html
[_scancode_]: https://en.wikipedia.org/wiki/Scancode
![QEMU printing scancodes to the screen when keys are pressed](qemu-printing-scancodes.gif)
@@ -542,7 +542,7 @@ To translate the scancodes to keys, we can use a match statement:
// in src/interrupts.rs
extern "x86-interrupt" fn keyboard_interrupt_handler(
_stack_frame: &mut ExceptionStackFrame)
_stack_frame: &mut InterruptStackFrame)
{
use x86_64::instructions::port::Port;
@@ -595,7 +595,7 @@ Now we can use this crate to rewrite our `keyboard_interrupt_handler`:
// in/src/interrupts.rs
extern "x86-interrupt" fn keyboard_interrupt_handler(
_stack_frame: &mut ExceptionStackFrame)
_stack_frame: &mut InterruptStackFrame)
{
use x86_64::instructions::port::Port;
use pc_keyboard::{Keyboard, ScancodeSet1, DecodedKey, layouts};

View File

@@ -227,8 +227,8 @@ Let's take a closer look at the available flags:
The `x86_64` crate provides types for [page tables] and their [entries], so we don't need to create these structures ourselves.
[page tables]: https://docs.rs/x86_64/0.3.4/x86_64/structures/paging/struct.PageTable.html
[entries]: https://docs.rs/x86_64/0.3.4/x86_64/structures/paging/struct.PageTableEntry.html
[page tables]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/struct.PageTable.html
[entries]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/page_table/struct.PageTableEntry.html
### The Translation Lookaside Buffer
@@ -237,7 +237,7 @@ A 4-level page table makes the translation of virtual addresses expensive, becau
Unlike the other CPU caches, the TLB is not fully transparent and does not update or remove translations when the contents of page tables change. This means that the kernel must manually update the TLB whenever it modifies a page table. To do this, there is a special CPU instruction called [`invlpg`] (“invalidate page”) that removes the translation for the specified page from the TLB, so that it is loaded again from the page table on the next access. The TLB can also be flushed completely by reloading the `CR3` register, which simulates an address space switch. The `x86_64` crate provides Rust functions for both variants in the [`tlb` module].
[`invlpg`]: https://www.felixcloutier.com/x86/INVLPG.html
[`tlb` module]: https://docs.rs/x86_64/0.3.4/x86_64/instructions/tlb/index.html
[`tlb` module]: https://docs.rs/x86_64/0.5.0/x86_64/instructions/tlb/index.html
It is important to remember flushing the TLB on each page table modification because otherwise the CPU might keep using the old translation, which can lead to non-deterministic bugs that are very hard to debug.
@@ -275,7 +275,7 @@ lazy_static! {
use x86_64::structures::idt::PageFaultErrorCode;
extern "x86-interrupt" fn page_fault_handler(
stack_frame: &mut ExceptionStackFrame,
stack_frame: &mut InterruptStackFrame,
_error_code: PageFaultErrorCode,
) {
use crate::hlt_loop;
@@ -291,8 +291,8 @@ extern "x86-interrupt" fn page_fault_handler(
The [`CR2`] register is automatically set by the CPU on a page fault and contains the accessed virtual address that caused the page fault. We use the [`Cr2::read`] function of the `x86_64` crate to read and print it. Normally the [`PageFaultErrorCode`] type would provide more information about the type of memory access that caused the page fault, but there is currently an [LLVM bug] that passes an invalid error code, so we ignore it for now. We can't continue execution without resolving the page fault, so we enter a [`hlt_loop`] at the end.
[`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2
[`Cr2::read`]: https://docs.rs/x86_64/0.3.5/x86_64/registers/control/struct.Cr2.html#method.read
[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.3.4/x86_64/structures/idt/struct.PageFaultErrorCode.html
[`Cr2::read`]: https://docs.rs/x86_64/0.5.0/x86_64/registers/control/struct.Cr2.html#method.read
[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/idt/struct.PageFaultErrorCode.html
[LLVM bug]: https://github.com/rust-lang/rust/issues/57270
[`hlt_loop`]: ./second-edition/posts/08-hardware-interrupts/index.md#the
@@ -326,7 +326,7 @@ pub extern "C" fn _start() -> ! {
When we run it, we see that our page fault handler is called:
![EXCEPTION: Page Fault, Accessed Address: VirtAddr(0xdeadbeaf), ExceptionStackFrame: {…}](qemu-page-fault.png)
![EXCEPTION: Page Fault, Accessed Address: VirtAddr(0xdeadbeaf), InterruptStackFrame: {…}](qemu-page-fault.png)
The `CR2` register indeed contains `0xdeadbeaf`, the address that we tried to access.
@@ -368,9 +368,9 @@ pub extern "C" fn _start() -> ! {
The [`Cr3::read`] function of the `x86_64` returns the currently active level 4 page table from the `CR3` register. It returns a tuple of a [`PhysFrame`] and a [`Cr3Flags`] type. We are only interested in the frame, so we ignore the second element of the tuple.
[`Cr3::read`]: https://docs.rs/x86_64/0.3.4/x86_64/registers/control/struct.Cr3.html#method.read
[`PhysFrame`]: https://docs.rs/x86_64/0.3.4/x86_64/structures/paging/struct.PhysFrame.html
[`Cr3Flags`]: https://docs.rs/x86_64/0.3.4/x86_64/registers/control/struct.Cr3Flags.html
[`Cr3::read`]: https://docs.rs/x86_64/0.5.0/x86_64/registers/control/struct.Cr3.html#method.read
[`PhysFrame`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/frame/struct.PhysFrame.html
[`Cr3Flags`]: https://docs.rs/x86_64/0.5.0/x86_64/registers/control/struct.Cr3Flags.html
When we run it, we see the following output:
@@ -380,7 +380,7 @@ Level 4 page table at: PhysAddr(0x1000)
So the currently active level 4 page table is stored at address `0x1000` in _physical_ memory, as indicated by the [`PhysAddr`] wrapper type. The question now is: how can we access this table from our kernel?
[`PhysAddr`]: https://docs.rs/x86_64/0.3.4/x86_64/struct.PhysAddr.html
[`PhysAddr`]: https://docs.rs/x86_64/0.5.0/x86_64/struct.PhysAddr.html
Accessing physical memory directly is not possible when paging is active, since programs could easily circumvent memory protection and access memory of other programs otherwise. So the only way to access the table is through some virtual page that is mapped to the physical frame at address `0x1000`. This problem of creating mappings for page table frames is a general problem, since the kernel needs to access the page tables regularly, for example when allocating a stack for a new thread.
@@ -418,7 +418,7 @@ When we look at the [format of page table entries][page table format], we see th
Instead of working with unsafe raw pointers we can use the [`PageTable`] type of the `x86_64` crate:
[`PageTable`]: https://docs.rs/x86_64/0.3.4/x86_64/structures/paging/struct.PageTable.html
[`PageTable`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/page_table/struct.PageTable.html
```rust
// in src/main.rs

View File

@@ -248,7 +248,7 @@ First, we introduce variables for the recursive index (511 = `0o777`) and the si
In the next step we calculate the virtual addresses of the four page tables as descripbed in the [address calculation] section. We transform each of these addresses to [`PageTable`] references later in the function. These transformations are `unsafe` operations since the compiler can't know that these addresses are valid.
[address calculation]: #address-calculation
[`PageTable`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.PageTable.html
[`PageTable`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/page_table/struct.PageTable.html
After the address calculation, we use the indexing operator to look at the entry in the level 4 table. If that entry is null, there is no level 3 table for this level 4 entry, which means that the `addr` is not mapped to any physical memory, so we return `None`. If the entry is not `None`, we know that a level 3 table exists. We then do the same cast and entry-checking as with the level 4 table.
@@ -270,13 +270,18 @@ pub extern "C" fn _start() -> ! {
use blog_os::memory::translate_addr;
// the identity-mapped vga buffer page
println!("0xb8000 -> {:?}", translate_addr(0xb8000));
// some code page
println!("0x20010a -> {:?}", translate_addr(0x20010a));
// some stack page
println!("0x57ac_001f_fe48 -> {:?}", translate_addr(0x57ac_001f_fe48));
let addresses = [
// the identity-mapped vga buffer page
0xb8000,
// some code page
0x20010a,
// some stack page
0x57ac_001f_fe48,
];
for &address in &addresses {
println!("{:?} -> {:?}", address, translate_addr(address));
}
println!("It did not crash!");
blog_os::hlt_loop();
@@ -291,9 +296,10 @@ As expected, the identity-mapped address `0xb8000` translates to the same physic
#### The `RecursivePageTable` Type
The `x86_64` provides a [`RecursivePageTable`] type that implements safe abstractions for various page table operations. By using this type, we can reimplement our `translate_addr` function in a much cleaner way:
The `x86_64` provides a [`RecursivePageTable`] type that implements safe abstractions for various page table operations. The type implements the [`MapperAllSizes`] trait, which already contains a `translate_addr` function that we can use instead of hand-rolling our own. To create a new `RecursivePageTable`, we create a `memory::init` function:
[`RecursivePageTable`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.RecursivePageTable.html
[`RecursivePageTable`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/struct.RecursivePageTable.html
[`MapperAllSizes`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/mapper/trait.MapperAllSizes.html
```rust
// in src/memory.rs
@@ -310,24 +316,11 @@ pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> {
let level_4_table = &mut *level_4_table_ptr;
RecursivePageTable::new(level_4_table).unwrap()
}
/// Returns the physical address for the given virtual address, or `None` if
/// the virtual address is not mapped.
pub fn translate_addr(addr: u64, recursive_page_table: &RecursivePageTable)
-> Option<PhysAddr>
{
let addr = VirtAddr::new(addr);
let page: Page = Page::containing_address(addr);
// perform the translation
let frame = recursive_page_table.translate_page(page);
frame.map(|frame| frame.start_address() + u64::from(addr.page_offset()))
}
```
The `RecursivePageTable` type encapsulates the unsafety of the page table walk completely so that we no longer need `unsafe` in our `translate_addr` function. The `init` function needs to be unsafe because the caller has to guarantee that the passed `level_4_table_addr` is valid.
The `RecursivePageTable` type encapsulates the unsafety of the page table walk completely so that we no longer need `unsafe` to implement our own `translate_addr` function. The `init` function needs to be unsafe because the caller has to guarantee that the passed `level_4_table_addr` is valid.
Our `_start` function needs to be updated for the new function signature in the following way:
We can now use the `MapperAllSizes::translate_addr` function in our `_start` function:
```rust
// in src/main.rs
@@ -337,25 +330,33 @@ Our `_start` function needs to be updated for the new function signature in the
pub extern "C" fn _start() -> ! {
[] // initialize GDT, IDT, PICS
use blog_os::memory::{self, translate_addr};
use blog_os::memory;
use x86_64::{
structures::paging::MapperAllSizes,
VirtAddr,
};
const LEVEL_4_TABLE_ADDR: usize = 0o_177777_777_777_777_777_0000;
let recursive_page_table = unsafe { memory::init(LEVEL_4_TABLE_ADDR) };
// the identity-mapped vga buffer page
println!("0xb8000 -> {:?}", translate_addr(0xb8000, &recursive_page_table));
// some code page
println!("0x20010a -> {:?}", translate_addr(0x20010a, &recursive_page_table));
// some stack page
println!("0x57ac_001f_fe48 -> {:?}", translate_addr(0x57ac_001f_fe48,
&recursive_page_table));
let addresses = []; // as before
for &address in &addresses {
let virt_addr = VirtAddr::new(address);
let phys_addr = recursive_page_table.translate_addr(virt_addr);
println!("{:?} -> {:?}", virt_addr, phys_addr);
}
println!("It did not crash!");
blog_os::hlt_loop();
}
```
Instead of passing the `LEVEL_4_TABLE_ADDR` to `translate_addr` and accessing the page tables through unsafe raw pointers, we now pass references to a `RecursivePageTable` type. By doing this, we now have a safe abstraction and clear ownership semantics. This ensures that we can't accidentally modify the page table concurrently, because an exclusive borrow of the `RecursivePageTable` is needed in order to modify it.
Instead of using `u64` for all addresses we now use the [`VirtAddr`] and [`PhysAddr`] wrapper types to differentiate the two kinds of addresses. In order to be able to call the `translate_addr` method, we need to import the `MapperAllSizes` trait.
[`VirtAddr`]: https://docs.rs/x86_64/0.5.0/x86_64/struct.VirtAddr.html
[`PhysAddr`]: https://docs.rs/x86_64/0.5.0/x86_64/struct.PhysAddr.html
By using the `RecursivePageTable` type, we now have a safe abstraction and clear ownership semantics. This ensures that we can't accidentally modify the page table concurrently, because an exclusive borrow of the `RecursivePageTable` is needed in order to modify it.
When we run it, we see the same result as with our handcrafted translation function.
@@ -377,7 +378,7 @@ pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> {
The problem with this is that we don't immediately see which parts are unsafe. For example, we don't know whether the `RecursivePageTable::new` function is unsafe or not without looking at [its definition][RecursivePageTable::new]. This makes it very easy to accidentally do something unsafe without noticing.
[RecursivePageTable::new]: https://docs.rs/x86_64/0.3.6/x86_64/structures/paging/struct.RecursivePageTable.html#method.new
[RecursivePageTable::new]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/struct.RecursivePageTable.html#method.new
To avoid this problem, we can add a safe inner function:
@@ -435,25 +436,25 @@ pub fn create_example_mapping(
The function takes a mutable reference to the `RecursivePageTable` because it needs to modify it and a `FrameAllocator` that is explained below. It then uses the [`map_to`] function of the [`Mapper`] trait to map the page at address `0x1000` to the physical frame at address `0xb8000`. The function is unsafe because it's possible to break memory safety with invalid arguments.
[`map_to`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.Mapper.html#tymethod.map_to
[`Mapper`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.Mapper.html
[`map_to`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/mapper/trait.Mapper.html#tymethod.map_to
[`Mapper`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/mapper/trait.Mapper.html
Apart from the `page` and `frame` arguments, the [`map_to`] function takes two more arguments. The third argument is a set of flags for the page table entry. We set the `PRESENT` flag because it is required for all valid entries and the `WRITABLE` flag to make the mapped page writable.
The fourth argument needs to be some structure that implements the [`FrameAllocator`] trait. The `map_to` method needs this argument because it might need unused frames for creating new page tables. The `Size4KiB` argument in the trait implementation is needed because the [`Page`] and [`PhysFrame`] types are [generic] over the [`PageSize`] trait to work with both standard 4KiB pages and huge 2MiB/1GiB pages.
[`FrameAllocator`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.FrameAllocator.html
[`Page`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.Page.html
[`PhysFrame`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.PhysFrame.html
[`FrameAllocator`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/trait.FrameAllocator.html
[`Page`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/page/struct.Page.html
[`PhysFrame`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/frame/struct.PhysFrame.html
[generic]: https://doc.rust-lang.org/book/ch10-00-generics.html
[`PageSize`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.PageSize.html
[`PageSize`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/page/trait.PageSize.html
The `map_to` function can fail, so it returns a [`Result`]. Since this is just some example code that does not need to be robust, we just use [`expect`] to panic when an error occurs. On success, the function returns a [`MapperFlush`] type that provides an easy way to flush the newly mapped page from the translation lookaside buffer (TLB) with its [`flush`] method. Like `Result`, the type uses the [`#[must_use]`] attribute to emit a warning when we accidentally forget to use it.
[`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html
[`expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect
[`MapperFlush`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.MapperFlush.html
[`flush`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.MapperFlush.html#method.flush
[`MapperFlush`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/mapper/struct.MapperFlush.html
[`flush`]: https://docs.rs/x86_64/0.5.0/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush
[`#[must_use]`]: https://doc.rust-lang.org/std/result/#results-must-be-used
Since we know that no new page tables are required for the address `0x1000`, a frame allocator that always returns `None` suffices. We create such an `EmptyFrameAllocator` for testing our mapping function: