From 8efa53761e1f73acac6be413fdf54f1202952f7c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 12 Mar 2019 13:57:23 +0100 Subject: [PATCH] Cleanup: remove replaced content from Advanced Paging post --- .../posts/10-paging-implementation/index.md | 590 +----------------- 1 file changed, 3 insertions(+), 587 deletions(-) diff --git a/blog/content/second-edition/posts/10-paging-implementation/index.md b/blog/content/second-edition/posts/10-paging-implementation/index.md index 95fd1996..651d118a 100644 --- a/blog/content/second-edition/posts/10-paging-implementation/index.md +++ b/blog/content/second-edition/posts/10-paging-implementation/index.md @@ -68,7 +68,7 @@ The important thing here is that each page entry stores the _physical_ address o The problem for us is that we can't directly access physical addresses from our kernel since our kernel also runs on top of virtual addresses. For example when we access address `4 KiB`, we access the _virtual_ address `4 KiB`, not the _physical_ address `4 KiB` where the level 4 page table is stored. When we want to access the physical address `4 KiB`, we can only do so through some virtual address that maps to it. -So in order to access page table frames, we need to map some virtual pages to them. There are different ways to create these mappings that all allow us to access arbitrary page table frames: +So in order to access page table frames, we need to map some virtual pages to them. There are different ways to create these mappings that all allow us to access arbitrary page table frames. ### Identity Mapping @@ -240,7 +240,7 @@ The above code assumes that the last level 4 entry with index `0o777` (511) is r Alternatively to performing the bitwise operations by hand, you can use the [`RecursivePageTable`] type of the `x86_64` crate, which provides safe abstractions for various page table operations. For example, the code below shows how to translate a virtual address to its mapped physical address: -[`RecursivePageTable`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.RecursivePageTable.html +[`RecursivePageTable`]: https://docs.rs/x86_64/0.5.2/x86_64/structures/paging/struct.RecursivePageTable.html ```rust // in src/memory.rs @@ -910,590 +910,6 @@ The next post will create a heap memory region for our kernel, which will allow [allocate memory]: https://doc.rust-lang.org/alloc/boxed/struct.Box.html [collection types]: https://doc.rust-lang.org/alloc/collections/index.html - use x86_64::structures::paging::PageTable; - - let level_4_table_ptr = 0xffff_ffff_ffff_f000 as *const PageTable; - let level_4_table = unsafe {&*level_4_table_ptr}; - for i in 0..10 { - println!("Entry {}: {:?}", i, level_4_table[i]); - } - - println!("It did not crash!"); - blog_os::hlt_loop(); -} -``` - -Here we cast the `0xffff_ffff_ffff_f000` pointer first to a raw pointer and then transform it to a Rust reference. This operation still needs `unsafe`, because the compiler can't know that this accessing this address is valid. But after this conversion we have a safe `&PageTable` type, which allows us to access the individual entries through safe, bounds checked [indexing operations]. - -[indexing operations]: https://doc.rust-lang.org/core/ops/trait.Index.html - -The crate also provides some abstractions for the individual entries so that we directly see which flags are set when we print them: - -![ -Entry 0: PageTableEntry { addr: PhysAddr(0x2000), flags: PRESENT | WRITABLE | ACCCESSED } -Entry 1: PageTableEntry { addr: PhysAddr(0x6e5000), flags: PRESENT | WRITABLE | ACCESSED | DIRTY } -Entry 2: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -Entry 3: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -Entry 4: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -Entry 5: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -Entry 6: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -Entry 7: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -Entry 8: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -Entry 9: PageTableEntry { addr: PhysAddr(0x0), flags: (empty)} -](qemu-print-p4-entries-abstraction.png) - -The next step would be to follow the pointers in entry 0 or entry 1 to a level 3 page table. But we now again have the problem that `0x2000` and `0x6e5000` are physical addresses, so we can't access them directly. This problem will be solved in the next post. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -## Implementation - -After all this theory we can finally start our implementation. Conveniently, the bootloader not only created page tables for our kernel, but it also created a recursive mapping in the last entry of the level 4 table. The bootloader did this because otherwise there would be a [chicken or egg problem]: We need to access the level 4 table to create a recursive mapping, but we can't access it without some kind of mapping. - -[chicken or egg problem]: https://en.wikipedia.org/wiki/Chicken_or_the_egg - -We already used this recursive mapping [at the end of the previous post] to access the level 4 table. We did this through the hardcoded address `0xffff_ffff_ffff_f000`. When we convert this address to [octal] and compare it with the above table, we can see that it exactly follows the structure of a level 4 table entry with `RRR` = `0o777`, `AAAA` = 0, and the sign extension bits set to `1` each: - -``` -structure: 0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA -address: 0o_177777_777_777_777_777_0000 -``` - -With our knowledge about recursive page tables we can now create virtual addresses to access all active page tables. This allows us to create a translation function in software. - -### Translating Addresses - -As a first step, let's create a function that translates a virtual address to a physical address by walking the page table hierarchy: - -```rust -// in src/lib.rs - -pub mod memory; -``` - -```rust -// in src/memory.rs - -use x86_64::PhysAddr; -use x86_64::structures::paging::PageTable; - -/// Returns the physical address for the given virtual address, or `None` if the -/// virtual address is not mapped. -pub fn translate_addr(addr: usize) -> Option { - // introduce variables for the recursive index and the sign extension bits - // TODO: Don't hardcode these values - let r = 0o777; // recursive index - let sign = 0o177777 << 48; // sign extension - - // retrieve the page table indices of the address that we want to translate - let l4_idx = (addr >> 39) & 0o777; // level 4 index - let l3_idx = (addr >> 30) & 0o777; // level 3 index - let l2_idx = (addr >> 21) & 0o777; // level 2 index - let l1_idx = (addr >> 12) & 0o777; // level 1 index - let page_offset = addr & 0o7777; - - // calculate the table addresses - let level_4_table_addr = - sign | (r << 39) | (r << 30) | (r << 21) | (r << 12); - let level_3_table_addr = - sign | (r << 39) | (r << 30) | (r << 21) | (l4_idx << 12); - let level_2_table_addr = - sign | (r << 39) | (r << 30) | (l4_idx << 21) | (l3_idx << 12); - let level_1_table_addr = - sign | (r << 39) | (l4_idx << 30) | (l3_idx << 21) | (l2_idx << 12); - - // check that level 4 entry is mapped - let level_4_table = unsafe { &*(level_4_table_addr as *const PageTable) }; - if level_4_table[l4_idx].addr().is_null() { - return None; - } - - // check that level 3 entry is mapped - let level_3_table = unsafe { &*(level_3_table_addr as *const PageTable) }; - if level_3_table[l3_idx].addr().is_null() { - return None; - } - - // check that level 2 entry is mapped - let level_2_table = unsafe { &*(level_2_table_addr as *const PageTable) }; - if level_2_table[l2_idx].addr().is_null() { - return None; - } - - // check that level 1 entry is mapped and retrieve physical address from it - let level_1_table = unsafe { &*(level_1_table_addr as *const PageTable) }; - let phys_addr = level_1_table[l1_idx].addr(); - if phys_addr.is_null() { - return None; - } - - Some(phys_addr + page_offset) -} -``` - -First, we introduce variables for the recursive index (511 = `0o777`) and the sign extension bits (which are 1 each). Then we calculate the page table indices and the page offset from the address through bitwise operations as specified in the graphic: - -![Bits 0–12 are the page offset, bits 12–21 the level 1 index, bits 21–30 the level 2 index, bits 30–39 the level 3 index, and bits 39–48 the level 4 index](../paging-introduction/x86_64-table-indices-from-address.svg) - -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 - -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. - -After we checked the three higher level pages, we can finally read the entry of the level 1 table that tells us the physical frame that the address is mapped to. As the last step, we add the page offset to that address and return it. - -If we knew that the address is mapped, we could directly access the level 1 table without looking at the higher level pages first. But since we don't know this, we have to check whether the level 1 table exists first, otherwise our function would cause a page fault for unmapped addresses. - -#### Try it out - -We can use our new translation function to translate some virtual addresses in our `_start` function: - -```rust -// in src/main.rs - -#[cfg(not(test))] -#[no_mangle] -pub extern "C" fn _start() -> ! { - […] // initialize GDT, IDT, PICS - - 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!("0x57ac001ffe48 -> {:?}", translate_addr(0x57ac001ffe48)); - - - println!("It did not crash!"); - blog_os::hlt_loop(); -} -``` - -When we run it, we see the following output: - -![0xb8000 -> 0xb8000, 0x20010a -> 0x40010a, 0x57ac001ffe48 -> 0x27be48](qemu-translate-addr.png) - -As expected, the identity-mapped address `0xb8000` translates to the same physical address. The code page and the stack page translate to some arbitrary physical addresses, which depend on how the bootloader created the initial mapping for our kernel. - -#### 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: - -[`RecursivePageTable`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.RecursivePageTable.html - -```rust -// in src/memory.rs - -use x86_64::structures::paging::{Mapper, Page, PageTable, RecursivePageTable}; -use x86_64::{VirtAddr, PhysAddr}; - -/// Creates a RecursivePageTable instance from the level 4 address. -/// -/// This function is unsafe because it can break memory safety if an invalid -/// address is passed. -pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> { - let level_4_table_ptr = level_4_table_addr as *mut PageTable; - 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 -{ - 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. - -Our `_start` function needs to be updated for the new function signature in the following way: - -```rust -// in src/main.rs - -#[cfg(not(test))] -#[no_mangle] -pub extern "C" fn _start() -> ! { - […] // initialize GDT, IDT, PICS - - use blog_os::memory::{self, translate_addr}; - - 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!("0x57ac001ffe48 -> {:?}", translate_addr(0x57ac001ffe48, - &recursive_page_table)); - - 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. - -When we run it, we see the same result as with our handcrafted translation function. - -#### Making Unsafe Functions Safer - -Our `memory::init` function is an [unsafe function], which means that an `unsafe` block is required for calling it because the caller has to guarantee that certain requirements are met. In our case, the requirement is that the passed address is mapped to the physical frame of the level 4 page table. - -[unsafe function]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#calling-an-unsafe-function-or-method - -The second property of unsafe functions is that their complete body is treated as an `unsafe` block, which means that it can perform all kinds of unsafe operations without additional unsafe blocks. This is the reason that we didn't need an `unsafe` block for dereferencing the raw `level_4_table_ptr`: - -```rust -pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> { - let level_4_table_ptr = level_4_table_addr as *mut PageTable; - let level_4_table = &mut *level_4_table_ptr; // <- this operation is unsafe - RecursivePageTable::new(level_4_table).unwrap() -} -``` - -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 - -To avoid this problem, we can add a safe inner function: - -```rust -// in src/memory.rs - -pub unsafe fn init(level_4_table_addr: usize) -> RecursivePageTable<'static> { - /// Rust currently treats the whole body of unsafe functions as an unsafe - /// block, which makes it difficult to see which operations are unsafe. To - /// limit the scope of unsafe we use a safe inner function. - fn init_inner(level_4_table_addr: usize) -> RecursivePageTable<'static> { - let level_4_table_ptr = level_4_table_addr as *mut PageTable; - let level_4_table = unsafe { &mut *level_4_table_ptr }; - RecursivePageTable::new(level_4_table).unwrap() - } - - init_inner(level_4_table_addr) -} -``` - -Now an `unsafe` block is required again for dereferencing the `level_4_table_ptr` and we immediately see that this is the only unsafe operations in the function. There is currently an open [RFC][unsafe-fn-rfc] to change this unfortunate property of unsafe functions that would allow us to avoid the above boilerplate. - -[unsafe-fn-rfc]: https://github.com/rust-lang/rfcs/pull/2585 - -### Creating a new Mapping -After reading the page tables and creating a translation function, the next step is to create a new mapping in the page table hierarchy. - -The difficulty of creating a new mapping depends on the virtual page that we want to map. In the easiest case, the level 1 page table for the page already exists and we just need to write a single entry. In the most difficult case, the page is in a memory region for that no level 3 exists yet so that we need to create new level 3, level 2 and level 1 page tables first. - -Let's start with the simple case and assume that we don't need to create new page tables. The bootloader loads itself in the first megabyte of the virtual address space, so we know that a valid level 1 table exists for this region. We can choose any unused page in this memory region for our example mapping, for example, the page at address `0x1000`. As the target frame we use `0xb8000`, the frame of the VGA text buffer. This way we can easily test whether our mapping worked. - -We implement it in a new `create_mapping` function in our `memory` module: - -```rust -// in src/memory.rs - -use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB}; - -pub fn create_example_mapping( - recursive_page_table: &mut RecursivePageTable, - frame_allocator: &mut impl FrameAllocator, -) { - use x86_64::structures::paging::PageTableFlags as Flags; - - let page: Page = Page::containing_address(VirtAddr::new(0x1000)); - let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); - let flags = Flags::PRESENT | Flags::WRITABLE; - - 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 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 - -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 -[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 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 -[`#[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: - -```rust -// in src/memory.rs - -/// A FrameAllocator that always returns `None`. -pub struct EmptyFrameAllocator; - -impl FrameAllocator for EmptyFrameAllocator { - fn allocate_frame(&mut self) -> Option { - None - } -} -``` - -(If you're getting a 'method `allocate_frame` is not a member of trait `FrameAllocator`' error, you need to update `x86_64` to version 0.4.0.) - -We can now test the new mapping function in our `main.rs`: - -```rust -// in src/main.rs - -#[cfg(not(test))] -#[no_mangle] -pub extern "C" fn _start() -> ! { - […] // initialize GDT, IDT, PICS - - use blog_os::memory::{create_example_mapping, EmptyFrameAllocator}; - - 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 { (0x1900 as *mut u64).write_volatile(0xf021f077f065f04e)}; - - println!("It did not crash!"); - blog_os::hlt_loop(); -} -``` - -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 `0xf021f077f065f04e` to this page, which represents the string _"New!"_ on white background. We don't write directly to the beginning of the page at `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 - -When we run it in QEMU, we see the following output: - -![QEMU printing "It did not crash!" with four completely white cells in the middle of the screen](qemu-new-mapping.png) - -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 - -pub fn create_example_mapping(…) { - […] - let page: Page = Page::containing_address(VirtAddr::new(0xdeadbeaf000)); - […] -} - -// in src/main.rs - -#[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: - -``` -panicked at 'map_to failed: FrameAllocationFailed', /…/result.rs:999:5 -``` - -To map pages that don't have a level 1 page table yet we need to create a proper `FrameAllocator`. But how do we know which frames are unused and how much physical memory is available? - - - - - - - - --- - - - - - - - - - - - - - - - - - - -### Allocating Frames - -Now that we have access to the memory map through the boot information we can create a proper frame allocator on top. We start with a generic skeleton: - -```rust -// in src/memory.rs - -pub struct BootInfoFrameAllocator where I: Iterator { - frames: I, -} - -impl FrameAllocator for BootInfoFrameAllocator - where I: Iterator -{ - fn allocate_frame(&mut self) -> Option { - self.frames.next() - } -} -``` - -The `frames` field can be initialized with an arbitrary [`Iterator`] of frames. This allows us to just delegate `alloc` calls to the [`Iterator::next`] method. - -[`Iterator`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html -[`Iterator::next`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#tymethod.next - -The initialization of the `BootInfoFrameAllocator` happens in a new `init_frame_allocator` function: - -```rust -// in src/memory.rs - -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); - // map each region to its address range - let addr_ranges = regions.map(|r| r.range.start_addr()..r.range.end_addr()); - // transform to an iterator of frame start addresses - let frame_addresses = addr_ranges.flat_map(|r| r.into_iter().step_by(4096)); - // create `PhysFrame` types from the start addresses - let frames = frame_addresses.map(|addr| { - PhysFrame::containing_address(PhysAddr::new(addr)) - }); - - BootInfoFrameAllocator { frames } -} -``` - -This function uses iterator combinator methods to transform the initial `MemoryMap` into an iterator of usable physical frames: - -- First, we call the `iter` method to convert the memory map to an iterator of [`MemoryRegion`]s. Then we use the [`filter`] method to skip any reserved or otherwise unavailable regions. The bootloader updates the memory map for all the mappings it creates, so frames that are used by our kernel (code, data or stack) or to store the boot information are already marked as `InUse` or similar. Thus we can be sure that `Usable` frames are not used somewhere else. -- In the second step, we use the [`map`] combinator and Rust's [range syntax] to transform our iterator of memory regions to an iterator of address ranges. -- The third step is the most complicated: We convert each range to an iterator through the `into_iter` method and then choose every 4096th address using [`step_by`]. Since 4096 bytes (= 4 KiB) is the page size, we get the start address of each frame. The bootloader page aligns all usable memory areas so that we don't need any alignment or rounding code here. By using [`flat_map`] instead of `map`, we get an `Iterator` instead of an `Iterator>`. -- In the final step, we convert the start addresses to `PhysFrame` types to construct the desired `Iterator`. We then use this iterator to create and return a new `BootInfoFrameAllocator`. - -[`MemoryRegion`]: https://docs.rs/bootloader/0.3.12/bootloader/bootinfo/struct.MemoryRegion.html -[`filter`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.filter -[`map`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.map -[range syntax]: https://doc.rust-lang.org/core/ops/struct.Range.html -[`step_by`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.step_by -[`flat_map`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.flat_map - -We can now modify our `kernel_main` function to pass a `BootInfoFrameAllocator` instance instead of an `EmptyFrameAllocator`: - -```rust -// in src/main.rs - -#[cfg(not(test))] -fn kernel_main(boot_info: &'static BootInfo) -> ! { - […] // initialize GDT, IDT, PICS - - use x86_64::structures::paging::{PageTable, RecursivePageTable}; - - let mut recursive_page_table = unsafe { - memory::init(boot_info.p4_table_addr as usize) - }; - // new - let mut frame_allocator = memory::init_frame_allocator(&boot_info.memory_map); - - blog_os::memory::create_mapping(&mut recursive_page_table, &mut frame_allocator); - unsafe { (0xdeadbeaf900 as *mut u64).write_volatile(0xf021f077f065f04e)}; - - println!("It did not crash!"); - blog_os::hlt_loop(); -} -``` - -Now the mapping succeeds and we see the black-on-white _"New!"_ on the screen again. Behind the scenes, the `map_to` method creates the missing page tables in the following way: - -- Allocate an unused frame from the passed `frame_allocator`. -- Map the entry of the higher level table to that frame. Now the frame is accessible through the recursive page table. -- Zero the frame to create a new, empty page table. -- Continue with the next table level. - -While our `create_mapping` function is just some example code, we are now able to create new mappings for arbitrary pages. This will be essential for allocating memory or implementing multithreading in future posts. - -## Summary - -In this post we learned how a recursive level 4 table entry can be used to map all page table frames to calculatable virtual addresses. We used this technique to implement an address translation function and to create a new mapping in the page tables. - -We saw that the creation of new mappings requires unused frames for creating new page tables. Such a frame allocator can be implemented on top of the boot information structure that the bootloader passes to our kernel. - -## What's next? - -The next post will create a heap memory region for our kernel, which will allow us to [allocate memory] and use various [collection types]. - -[allocate memory]: https://doc.rust-lang.org/alloc/boxed/struct.Box.html -[collection types]: https://doc.rust-lang.org/alloc/collections/index.html +TODO: update date