Continue improving post

This commit is contained in:
Philipp Oppermann
2019-01-26 19:01:21 +01:00
parent 5e756c9fd3
commit 8c3b9e6508
2 changed files with 31 additions and 17 deletions

View File

@@ -25,7 +25,7 @@ However, it also causes a problem when we try to access the page tables from our
[at the end of the previous post]: ./second-edition/posts/09-paging-introduction/index.md#accessing-the-page-tables [at the end of the previous post]: ./second-edition/posts/09-paging-introduction/index.md#accessing-the-page-tables
The next section discusses the problem in detail and provides different approaches to a solution. Afterwards, we will implement a function to translate virtual to physical addresses and learn how to create new mappings in the page tables. The next section discusses the problem in detail and provides different approaches to a solution. Afterwards, we will implement a function to translate virtual to physical addresses and learn how to create new mappings in the page tables.
## Accessing Page Tables ## Accessing Page Tables
@@ -399,7 +399,7 @@ use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB};
pub fn create_example_mapping( pub fn create_example_mapping(
recursive_page_table: &mut RecursivePageTable, recursive_page_table: &mut RecursivePageTable,
frame_allocator: &mut impl FrameAllocator, frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) { ) {
use x86_64::structures::paging::PageTableFlags as Flags; use x86_64::structures::paging::PageTableFlags as Flags;
@@ -407,17 +407,21 @@ pub fn create_example_mapping(
let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000));
let flags = Flags::PRESENT | Flags::WRITABLE; let flags = Flags::PRESENT | Flags::WRITABLE;
recursive_page_table.map_to(page, frame, flags, frame_allocator) let map_to_result = unsafe {
.expect("map_to failed").flush(); recursive_page_table.map_to(page, frame, flags, frame_allocator)
};
map_to_result.expect("map_to failed").flush();
} }
``` ```
The function takes a mutable reference to the `RecursivePageTable` because it needs to modify it. It then uses the [`map_to`] function of the [`Mapper`] trait to map the page at `0x1000` to the physical frame at address `0xb8000`. The `PRESENT` flags is required for all valid entries and the `WRITABLE` flag makes the mapping writable. 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 `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 [`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 [`Mapper`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.Mapper.html
The 4th 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. Since we know that no new page tables are required for the address `0x1000`, we pass an `EmptyFrameAllocator` type that always returns `None` (see below). The `Size4KiB` argument in the trait implementation is needed because the [`Page`] and [`PhysFrame`] types are [generic] over the [`PageSize`] to work with both standard 4KiB pages and huge 2MiB/1GiB pages. 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 mapping 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`] 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 [`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 [`Page`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.Page.html
@@ -425,7 +429,7 @@ The 4th argument needs to be some structure that implements the [`FrameAllocator
[generic]: https://doc.rust-lang.org/book/ch10-00-generics.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.3.5/x86_64/structures/paging/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 the return type. 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 the return type.
[`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html [`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html
[`expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect [`expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect
@@ -433,7 +437,7 @@ The [`map_to`] function can fail, so it returns a [`Result`]. Since this is just
[`flush`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.MapperFlush.html#method.flush [`flush`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.MapperFlush.html#method.flush
[`#[must_use]`]: https://doc.rust-lang.org/std/result/#results-must-be-used [`#[must_use]`]: https://doc.rust-lang.org/std/result/#results-must-be-used
The `EmptyFrameAllocator` looks like this: 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 `EmptyFrameAllocator` for testing our mapping function:
```rust ```rust
// in src/memory.rs // in src/memory.rs
@@ -442,7 +446,7 @@ The `EmptyFrameAllocator` looks like this:
pub struct EmptyFrameAllocator; pub struct EmptyFrameAllocator;
impl FrameAllocator<Size4KiB> for EmptyFrameAllocator { impl FrameAllocator<Size4KiB> for EmptyFrameAllocator {
fn alloc(&mut self) -> Option<PhysFrame> { fn allocate_frame(&mut self) -> Option<PhysFrame> {
None None
} }
} }
@@ -460,10 +464,11 @@ pub extern "C" fn _start() -> ! {
use blog_os::memory::{create_example_mapping, EmptyFrameAllocator}; use blog_os::memory::{create_example_mapping, EmptyFrameAllocator};
let mut recursive_page_table = memory::init(boot_info.p4_table_addr as usize); const LEVEL_4_TABLE_ADDR: usize = 0o_177777_777_777_777_777_0000;
let mut recursive_page_table = unsafe { memory::init(LEVEL_4_TABLE_ADDR) };
create_example_mapping(&mut recursive_page_table, &mut EmptyFrameAllocator); create_example_mapping(&mut recursive_page_table, &mut EmptyFrameAllocator);
unsafe { (0x1c00 as *mut u64).write_volatile(0xffffffffffffffff)}; unsafe { (0x1900 as *mut u64).write_volatile(0xf021f077f065f04e)};
println!("It did not crash!"); println!("It did not crash!");
blog_os::hlt_loop(); blog_os::hlt_loop();
@@ -472,7 +477,7 @@ pub extern "C" fn _start() -> ! {
We first create the mapping for the page at `0x1000` by calling our `create_example_mapping` function with a mutable reference to the `RecursivePageTable` instance. This maps the page `0x1000` to the VGA text buffer, so we should see any write to it on the screen. We first create the mapping for the page at `0x1000` by calling our `create_example_mapping` function with a mutable reference to the `RecursivePageTable` instance. This maps the page `0x1000` to the VGA text buffer, so we should see any write to it on the screen.
Then we write the value `0xffffffffffffffff` to this page, which represents the string "New!" on white background. We don't write directly to `0x1000` since the top line is directly shifted off the screen by the next `println`. Instead we write to offset `0xc00`, which is in the lower screen half. As we learned [in the _“VGA Text Mode”_ post], writes to the VGA buffer should be volatile, so we use the [`write_volatile`] method. Then we write the value `0xf021f077f065f04e` to this page, which represents the string "New!" on white background. We don't write directly to `0x1000` since the top line is directly shifted off the screen by the next `println`. Instead we write to offset `0x900`, which is about in the middle of the screen. As we learned [in the _“VGA Text Mode”_ post], writes to the VGA buffer should be volatile, so we use the [`write_volatile`] method.
[in the _“VGA Text Mode”_ post]: ./second-edition/posts/03-vga-text-buffer/index.md#volatile [in the _“VGA Text Mode”_ post]: ./second-edition/posts/03-vga-text-buffer/index.md#volatile
[`write_volatile`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.write_volatile [`write_volatile`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.write_volatile
@@ -481,18 +486,27 @@ When we run it in QEMU, we see the following output:
![QEMU printing "It did not crash!" with four completely white cells in middle of the screen](qemu-new-mapping.png) ![QEMU printing "It did not crash!" with four completely white cells in middle of the screen](qemu-new-mapping.png)
The "New!" on the screen is by our write to `0x1c00`, which means that we successfully created a new mapping in the page tables. The "New!" on the screen is by our write to `0x1900`, which means that we successfully created a new mapping in the page tables.
This only worked because there was already a level 1 table for mapping page `0x1000`. When we try to map a page for that no level 1 table exists yet, the `map_to` function fails because it tries to allocate frames from the `EmptyFrameAllocator` for creating new page tables. We can see that happen when we try to map page `0xdeadbeaf000` instead of `0x1000`: This only worked because there was already a level 1 table for mapping page `0x1000`. When we try to map a page for that no level 1 table exists yet, the `map_to` function fails because it tries to allocate frames from the `EmptyFrameAllocator` for creating new page tables. We can see that happen when we try to map page `0xdeadbeaf000` instead of `0x1000`:
```rust ```rust
// in src/memory.rs // in src/memory.rs
TODO: update create_example_mapping pub fn create_example_mapping() {
[]
let page: Page = Page::containing_address(VirtAddr::new(0xdeadbeaf000));
[]
}
// in src/main.rs // in src/main.rs
TODO: update accessed address #[no_mangle]
pub extern "C" fn _start() -> ! {
[]
unsafe { (0xdeadbeaf900 as *mut u64).write_volatile(0xf021f077f065f04e)};
[]
}
``` ```
When we run it, a panic with the following error message occurs: When we run it, a panic with the following error message occurs:
@@ -564,7 +578,7 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
We no longer need to use `extern "C"` or `no_mangle` for our entry point, as the macro defines the real lower level `_start` entry point for us. The `kernel_main` function is now a completely normal Rust function, so we can choose an arbitrary name for it. The important thing is that it is type-checked, so that a compilation error occurs when we now try to modify the function signature in any way, for example adding an argument or changing the argument type. We no longer need to use `extern "C"` or `no_mangle` for our entry point, as the macro defines the real lower level `_start` entry point for us. The `kernel_main` function is now a completely normal Rust function, so we can choose an arbitrary name for it. The important thing is that it is type-checked, so that a compilation error occurs when we now try to modify the function signature in any way, for example adding an argument or changing the argument type.
Note that we now pass `boot_info.p4_table_addr` instead of a hardcoded address to our `memory::init`. Thus our code continues to work even if a future version of the bootloader chooses a different entry of the level 4 page table for the recursive mapping. Note that we now pass `boot_info.p4_table_addr` instead of a hardcoded address to our `memory::init`. Thus our code continues to work even if a future version of the bootloader chooses a different entry of the level 4 page table for the recursive mapping.
### Allocating Frames ### Allocating Frames
@@ -601,7 +615,7 @@ use bootloader::bootinfo::{MemoryMap, MemoryRegionType};
/// Create a FrameAllocator from the passed memory map /// Create a FrameAllocator from the passed memory map
pub fn init_frame_allocator(memory_map: &'static MemoryMap) pub fn init_frame_allocator(memory_map: &'static MemoryMap)
-> BootInfoFrameAllocator<impl Iterator<Item = PhysFrame>> -> BootInfoFrameAllocator<impl Iterator<Item = PhysFrame>>
{ {
// get usable regions from memory map // get usable regions from memory map
let regions = memory_map.iter().filter(|r| { let regions = memory_map.iter().filter(|r| {
r.region_type == MemoryRegionType::Usable r.region_type == MemoryRegionType::Usable

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB