From 8c3b9e6508e677024296db3ff7caeb17ab0ee9b7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 26 Jan 2019 19:01:21 +0100 Subject: [PATCH] Continue improving post --- .../posts/10-advanced-paging/index.md | 48 +++++++++++------- .../10-advanced-paging/qemu-new-mapping.png | Bin 7792 -> 8136 bytes 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/blog/content/second-edition/posts/10-advanced-paging/index.md b/blog/content/second-edition/posts/10-advanced-paging/index.md index 07edca3a..57a41674 100644 --- a/blog/content/second-edition/posts/10-advanced-paging/index.md +++ b/blog/content/second-edition/posts/10-advanced-paging/index.md @@ -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 -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 @@ -399,7 +399,7 @@ use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB}; pub fn create_example_mapping( recursive_page_table: &mut RecursivePageTable, - frame_allocator: &mut impl FrameAllocator, + frame_allocator: &mut impl FrameAllocator, ) { 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 flags = Flags::PRESENT | Flags::WRITABLE; - recursive_page_table.map_to(page, frame, flags, frame_allocator) - .expect("map_to failed").flush(); + let map_to_result = unsafe { + 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 [`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 [`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 [`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 [`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 [`#[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 // in src/memory.rs @@ -442,7 +446,7 @@ The `EmptyFrameAllocator` looks like this: pub struct EmptyFrameAllocator; impl FrameAllocator for EmptyFrameAllocator { - fn alloc(&mut self) -> Option { + fn allocate_frame(&mut self) -> Option { None } } @@ -460,10 +464,11 @@ pub extern "C" fn _start() -> ! { 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); - unsafe { (0x1c00 as *mut u64).write_volatile(0xffffffffffffffff)}; + unsafe { (0x1900 as *mut u64).write_volatile(0xf021f077f065f04e)}; println!("It did not crash!"); 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. -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 [`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) -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`: ```rust // 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 -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: @@ -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. -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 @@ -601,7 +615,7 @@ use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; /// Create a FrameAllocator from the passed memory map pub fn init_frame_allocator(memory_map: &'static MemoryMap) -> BootInfoFrameAllocator> -{ +{ // get usable regions from memory map let regions = memory_map.iter().filter(|r| { r.region_type == MemoryRegionType::Usable diff --git a/blog/content/second-edition/posts/10-advanced-paging/qemu-new-mapping.png b/blog/content/second-edition/posts/10-advanced-paging/qemu-new-mapping.png index 8169af59ed86600d9b87122acf8401891b440a4b..5e4a6c5e0280aa6bf90c307496df56e9934058a6 100644 GIT binary patch delta 6298 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 HB{Ts5xTeaj literal 7792 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