Introduce boot info later; continue post

This commit is contained in:
Philipp Oppermann
2019-01-14 21:51:52 +01:00
parent 41b21914be
commit e46f8c5187
3 changed files with 336 additions and 107 deletions

View File

@@ -144,82 +144,22 @@ Whereas `AAA` is the level 4 index, `BBB` the level 3 index, `CCC` the level 2 i
## Implementation ## Implementation
After all this theory we can finally start our implementation. We already mentioned that the bootloader created page tables for our kernel, but it also created a recursive mapping in the last entry of the level 4 table for us. 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. After all this theory we can finally start our implementation. Conveniently, the bootloader not only created page tables for our kernel, 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 [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: with `RRR` = `0o777` = 511, `AAAA` = 0, and the sign extension bits set to `1` each: 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 with `RRR` = `0o777`, `AAAA` = 0, and the sign extension bits set to `1` each:
``` ```
structure: 0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA structure: 0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA
address: 0o_177777_777_777_777_777_0000 address: 0o_177777_777_777_777_777_0000
``` ```
Using hardcoded addresses is rarely a good idea, since they might become outdated. For example, our code would break if a future bootloader version uses a different entry for the recursive mapping. Fortunately the bootloader tells us the recursive entry by passing a _boot information structure_ to our kernel. With our knowlege about recursive page tables we can now create virtual addresess to access all active page tables. This allows us to create a translation function in software.
### Boot Information
To communicate the index of the recursive entry and other information to our kernel, the bootloader passes a reference to a boot information structure as an argument when calling our `_start` function. Right now we don't have this argument declared in our function, so we just ignore it. Let's add the argument to the signature of our `_start` function:
```rust
// in src/main.rs
use bootloader::bootinfo::BootInfo;
#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! {
println!("boot_info: {:x?}", boot_info);
[]
}
```
The [`BootInfo`] struct is still in an early stage, so expect some breakage when updating to future [semver-incompatible] bootloader versions. When we print it, we see that it currently has the three fields `p4_table_addr`, `memory_map`, and `package`:
[`BootInfo`]: https://docs.rs/bootloader/0.3.11/bootloader/bootinfo/struct.BootInfo.html
[semver-incompatible]: https://doc.rust-lang.org/stable/cargo/reference/specifying-dependencies.html#caret-requirements
![QEMU printing a `BootInfo` struct: "boot_info: Bootlnfo { p4_table_addr: fffffffffffff000. memory_map: […]. package: […]"](qemu-bootinfo-print.png)
The most interesting field for us right now is `p4_table_addr`, as it contains a virtual address that is mapped to the physical frame of the level 4 page table. As we see this address is `0xfffffffffffff000`, which is the same as the hardcoded address we used before.
The `memory_map` field will become relevant later in this post. The `package` field is an in-progress feature to bundle additional data with the bootloader. The implementation is not finished, so we can ignore this field for now.
#### The `entry_point` Macro
Since our `_start` function is called externally from the bootloader, no checking of our function signature occurs. This means that we could let it take arbitrary arguments without any compilation errors, but it would fail or cause undefined behavior at runtime.
To make sure that the entry point function has always the correct signature that the bootloader expects, the `bootloader` crate provides an [`entry_point`] macro that provides a type-checked way to define a Rust function as entry point. Let's rewrite our entry point function to use this macro:
[`entry_point`]: https://docs.rs/bootloader/0.3.12/bootloader/macro.entry_point.html
```rust
// in src/main.rs
use bootloader::entry_point;
entry_point!(kernel_main);
#[cfg(not(test))]
fn kernel_main(boot_info: &'static BootInfo) -> ! {
use blog_os::interrupts::PICS;
blog_os::gdt::init();
blog_os::interrupts::init_idt();
unsafe { PICS.lock().initialize() };
x86_64::instructions::interrupts::enable();
println!("It did not crash!");
blog_os::hlt_loop();
}
```
Note that we no longer need to use `extern "C"` or `no_mangle` for our entry point, since the macro does this for us. We can also use an arbitrary name for our function. When we now try to modify the function signature in any way, for example adding an argument or changing the argument type, a compilation error occurs.
### Translating Addresses ### Translating Addresses
Now we have a clean way to retrieve the virtual address of the recursively mapped level 4 table. which allows us to derive the virtual addreses of all other page tables. As a first step, let's try to create a function that translates a virtual address to a physical address: 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 ```rust
// in src/lib.rs // in src/lib.rs
@@ -235,7 +175,7 @@ use x86_64::structures::paging::PageTable;
/// Returns the physical address for the given virtual address, or `None` if the /// Returns the physical address for the given virtual address, or `None` if the
/// virtual address is not mapped. /// virtual address is not mapped.
pub fn translate_addr(addr: usize, level_4_table_addr: usize) -> Option<PhysAddr> { pub fn translate_addr(addr: usize, level_4_table_addr) -> Option<PhysAddr> {
// retrieve the page table indices of the address that we want to translate // retrieve the page table indices of the address that we want to translate
let level_4_index = (addr >> 39) & 0o777; let level_4_index = (addr >> 39) & 0o777;
let level_3_index = (addr >> 30) & 0o777; let level_3_index = (addr >> 30) & 0o777;
@@ -275,34 +215,43 @@ pub fn translate_addr(addr: usize, level_4_table_addr: usize) -> Option<PhysAddr
} }
``` ```
First, we calculate the page table indices and the page offset from the address: First, we calculate the page table indices and the page offset from the address through bitwise operations as specified in the graphic:
![Bits 012 are the page offset, bits 1221 the level 1 index, bits 2130 the level 2 index, bits 3039 the level 3 index, and bits 3948 the level 4 index](../paging-introduction/x86_64-table-indices-from-address.svg) ![Bits 012 are the page offset, bits 1221 the level 1 index, bits 2130 the level 2 index, bits 3039 the level 3 index, and bits 3948 the level 4 index](../paging-introduction/x86_64-table-indices-from-address.svg)
Then we check the whether the entries in the four tables are empty and return `None` in that case. It's important that we do this because the address of the next table is only valid if the entry is mapped, and we don't want to risk that our translation function causes a page fault. Then we transform the `level_4_table_addr` to a `&PageTable`, which is an `unsafe` operation since the compiler can't know that the address is valid. We use the indexing operator to look at the entry with `level_4_index`. 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`.
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. Finally, we add the page offset to that address and return it. If the entry is not `None`, we know that a level 3 table exist. We calculate the virtual address of it by shifting the level 4 address 9 bits to the left and setting the address bits 1221 to the level 4 index (see the section about [address calculation]). We can do that because the recursive index is `0o777`, so that it is also a valid sign extension. We then do the same cast and entry-checking as with the level 4 table.
Now we can use this function to translate some virtual addresses in our `kernel_main` function: [address calculation]: #address-calculation
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 a 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, so we have to check whether the level 1 table exists first, otherwise we 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 ```rust
// in src/main.rs // in src/main.rs
#[cfg(not(test))] #[cfg(not(test))]
fn kernel_main(boot_info: &'static BootInfo) -> ! { #[no_mangle]
pub extern "C" fn _start() -> ! {
[] // initialize GDT, IDT, PICS [] // initialize GDT, IDT, PICS
use blog_os::memory::translate_addr; use blog_os::memory::translate_addr;
let level_4_table_addr = boot_info.p4_table_addr as usize; const LEVEL_4_TABLE_ADDR: usize = 0o_177777_777_777_777_777_0000;
// the identity-mapped vga buffer page // the identity-mapped vga buffer page
println!("0xb8000 -> {:?}", translate_addr(0xb8000, level_4_table_addr)); println!("0xb8000 -> {:?}", translate_addr(0xb8000, LEVEL_4_TABLE_ADDR));
// some code page // some code page
println!("0x20010a -> {:?}", translate_addr(0x20010a, level_4_table_addr)); println!("0x20010a -> {:?}", translate_addr(0x20010a, LEVEL_4_TABLE_ADDR));
// some stack page // some stack page
println!("0x57ac001ffe48 -> {:?}", translate_addr(0x57ac001ffe48, println!("0x57ac001ffe48 -> {:?}", translate_addr(0x57ac001ffe48,
level_4_table_addr)); LEVEL_4_TABLE_ADDR));
println!("It did not crash!"); println!("It did not crash!");
blog_os::hlt_loop(); blog_os::hlt_loop();
@@ -327,14 +276,19 @@ The `x86_64` provides a [`RecursivePageTable`] type that implements safe abstrac
use x86_64::{VirtAddr, PhysAddr}; use x86_64::{VirtAddr, PhysAddr};
use x86_64::structures::paging::{Mapper, Page, PageTable, RecursivePageTable}; use x86_64::structures::paging::{Mapper, Page, PageTable, RecursivePageTable};
/// Returns the physical address for the given virtual address, or /// Create a RecursivePageTable instance from the level 4 address
/// `None` if the virtual address is not mapped. // TODO call only once
pub fn translate_addr(addr: u64, level_4_table_addr: usize) -> Option<PhysAddr> { pub fn init(level_4_table_addr: usize) -> RecursivePageTable {
// create a RecursivePageTable instance from the level 4 address
let level_4_table_ptr = level_4_table_addr as *mut PageTable; let level_4_table_ptr = level_4_table_addr as *mut PageTable;
let level_4_table = unsafe { &mut *level_4_table_ptr }; let level_4_table = unsafe { &mut *level_4_table_ptr };
let recursive_page_table = RecursivePageTable::new(level_4_table).unwrap(); 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 addr = VirtAddr::new(addr);
let page: Page = Page::containing_address(addr); let page: Page = Page::containing_address(addr);
@@ -344,51 +298,325 @@ pub fn translate_addr(addr: u64, level_4_table_addr: usize) -> Option<PhysAddr>
} }
``` ```
The `RecursivePageTable` type encapsulates the unsafety of the page table walk completely. We only need a single instance of `unsafe` to create a `&mut PageTable` from the level 4 page table address. Also, we no longer need to perform any bitwise operations. The `RecursivePageTable` type encapsulates the unsafety of the page table walk completely. We only need a single instance of `unsafe` in the `init` function to create a `&mut PageTable` from the level 4 page table address. Also, we no longer need to perform any bitwise operations.
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 = 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.
After reading the page tables and creating a translation function, the next step is to create a new mapping in the page table hierarchy.
### Creating a new Mapping ### Creating a new Mapping
Let's try to create a new mapping in the page tables. The `RecursivePageTable` type implements the [`Mapper`] trait, which has a [`map_to`] method with the following signature: The difficulty of creating a new mapping depends on 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.
[`Mapper`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.Mapper.html For creating a new page table we need to find an unused physical frame where the page table will be stored. We initialize it to zero and create a mapping for that frame in the higher level page table. At this point, we can access the new page table through the recursive page table and continue with the next level.
[`map_to`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.Mapper.html#tymethod.map_to
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 ```rust
pub trait Mapper<S: PageSize> { // in src/memory/mod.rs
fn map_to<A>(
&mut self,
page: Page<S>,
frame: PhysFrame<S>,
flags: PageTableFlags,
allocator: &mut A
) -> Result<MapperFlush<S>, MapToError>
where
A: FrameAllocator<Size4KiB>;
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;
recursive_page_table.map_to(page, frame, flags, frame_allocator)
.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.
[`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`. 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
[`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 ignore 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
[`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
TODO
```rust
// in src/memory/mod.rs
/// A FrameAllocator that always returns `None`.
pub struct EmptyFrameAllocator;
impl FrameAllocator<Size4KiB> for EmptyFrameAllocator {
fn alloc(&mut self) -> Option<PhysFrame> {
None
}
}
```
TODO
We can 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};
let mut recursive_page_table = memory::init(boot_info.p4_table_addr as usize);
create_example_mapping(&mut recursive_page_table, &mut EmptyFrameAllocator);
unsafe { (0x1c00 as *mut u64).write_volatile(0xffffffffffffffff)};
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. We don't write directly to `0x1000` since the top line is directly shifted off the screen by the next `println`. As we learned [in the _“VGA Text Mode”_ post], writes to the VGA buffer should be volatile.
[in the _“VGA Text Mode”_ post]: ./second-edition/posts/03-vga-text-buffer/index.md#volatile
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 white block in the middle of the screen is by our write to `0x1c00`, 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`. If we try to map a page for that no level 1 table exists yet, the `map_to` function tries to allocate frames from the `EmptyFrameAllocator` to create new page tables, which fails. We can see that happen when we try to map for example page `0xdeadbeaf000` instead of `0x1000`:
```rust
// in src/memory/mod.rs
TODO: update create_example_mapping
// in src/main.rs
TODO: update accessed address
```
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?
### Boot Information
The amount of physical memory and physical memory reserved by devices like the VGA hardware varies between different machines. Only the BIOS or UEFI firmware knows exactly which memory regions can be used by the operating system and which regions are reserved. Both firmaware standards provide functions to retrieve the memory map, but they can only be called very early in the boot process. For this reason, the bootloader already queries this and other information from the firmaware.
To communicate this information to our kernel, the bootloader passes a reference to a boot information structure as an argument when calling our `_start` function. Right now we don't have this argument declared in our function, so we just ignore it. Let's add it:
```rust
// in src/main.rs
use bootloader::bootinfo::BootInfo;
#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! {
[] []
} }
``` ```
The method creates a mapping in the page table that maps the given [`Page`] to the given [`PhysFrame`] with the given [`PageTableFlags`]. The last parameter is [generic] and expects some type that implements the [`FrameAllocator`] trait. This parameter is needed because the method might need to create new page tables to create the mapping, so it requires empty frames for this. <-- TODO The [`BootInfo`] struct is still in an early stage, so expect some breakage when updating to future [semver-incompatible] bootloader versions. It currently has the three fields `p4_table_addr`, `memory_map`, and `package`:
[`Page`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.Page.html [`BootInfo`]: https://docs.rs/bootloader/0.3.11/bootloader/bootinfo/struct.BootInfo.html
[`PhysFrame`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.PhysFrame.html [semver-incompatible]: https://doc.rust-lang.org/stable/cargo/reference/specifying-dependencies.html#caret-requirements
[`PageTableFlags`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/struct.PageTableFlags.html
[generic]: https://doc.rust-lang.org/book/ch10-00-generics.html
[`FrameAllocator`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.FrameAllocator.html
There are two [generic parameters]: `S: PageSize` and `A: FrameAllocator`. The [`PageSize`] trait makes it possible to generate generic code that works with normal 4KiB pages and huge 2MiB and 1GiB pages at the same time. We only use default 4KiB for now, so we can ignore this parameter for now. - The `p4_table_addr` field contains the recursive virtual address of the level 4 page table. By using this field we can avoid hardcoding the address `0o_177777_777_777_777_777_0000`.
- The `memory_map` field is most interesting to us, since it contains a list of all memory regions and their type (i.e. unused, reserved, or other).
- The `package` field is an in-progress feature to bundle additional data with the bootloader. The implementation is not finished, so we can ignore this field for now.
[generic parameters]: https://doc.rust-lang.org/book/ch10-00-generics.html Before we use the `memory_map` field to create a proper `FrameAllocator`, we want to ensure that we can't use a `boot_info` argument of the wrong type.
[`PageSize`]: https://docs.rs/x86_64/0.3.5/x86_64/structures/paging/trait.PageSize.html
#### The `entry_point` Macro
TODO: Since our `_start` function is called externally from the bootloader, no checking of our function signature occurs. This means that we could let it take arbitrary arguments without any compilation errors, but it would fail or cause undefined behavior at runtime.
- Map adddress 0 to the vga buffer To make sure that the entry point function has always the correct signature that the bootloader expects, the `bootloader` crate provides an [`entry_point`] macro that provides a type-checked way to define a Rust function as entry point. Let's rewrite our entry point function to use this macro:
- We need free physical frames for creating new page tables -> memory map
## A Physical Memory Map [`entry_point`]: https://docs.rs/bootloader/0.3.12/bootloader/macro.entry_point.html
```rust
// in src/main.rs
use bootloader::entry_point;
entry_point!(kernel_main);
#[cfg(not(test))]
fn kernel_main(boot_info: &'static BootInfo) -> ! {
[] // initialize GDT, IDT, PICS
let mut recursive_page_table = memory::init(boot_info.p4_table_addr as usize);
[] // create and test example mapping
println!("It did not crash!");
blog_os::hlt_loop();
}
```
We no longer need to use `extern "C"` or `no_mangle` for our entry point, as the macro defines the real lowet 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 now, 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.
### 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/mod.rs
pub struct BootInfoFrameAllocator<I> where I: Iterator<Item = PhysFrame> {
frames: I,
}
impl<I> FrameAllocator<Size4KiB> for BootInfoFrameAllocator<I>
where I: Iterator<Item = PhysFrame>
{
fn alloc(&mut self) -> Option<PhysFrame> {
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/mod.rs
use bootloader::bootinfo::{MemoryMap, MemoryRegionType};
/// Create a FrameAllocator from the passed memory map
pub fn init_frame_allocator(memory_map: &'static MemoryMap)
-> BootInfoFrameAllocator<impl Iterator<Item = PhysFrame>>
{
// 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 in use 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`]. 4096 bytes (= 4 KiB) is the page size, so by doing this 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<Item = u64>` instead of an `Iterator<Item = Iterator<Item = u64>>`.
- In the final step, we convert the start addresses to `PhysFrame` types to construct the desired `Iterator<Item = PhysFrame>`. 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 = memory::init(boot_info.p4_table_addr as usize);
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 { (0xdeadbeafc00 as *mut u64).write_volatile(0xffffffffffffffff)};
println!("It did not crash!");
blog_os::hlt_loop();
}
```
Now the mapping succeeds and we see the white block on the screen again:
![]() TODO
The `map_to` method was now able to create the missing page tables on the frames allocated from our `BootInfoFrameAllocator`. You can insert a `println` message into the `BootInfoFrameAllocator::alloc` method to see how it's called.
We're now able to map arbitrary pages and to allocate new physical frames when we need them.
# TODO
## Allocating Stacks ## Allocating Stacks
@@ -397,4 +625,5 @@ TODO:
## What's next? ## What's next?
--- ---
TODO update post date TODO spellcheck
TODO update post date

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB