mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
Continue improving post
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
let map_to_result = unsafe {
|
||||||
recursive_page_table.map_to(page, frame, flags, frame_allocator)
|
recursive_page_table.map_to(page, frame, flags, frame_allocator)
|
||||||
.expect("map_to failed").flush();
|
};
|
||||||
|
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:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.9 KiB |
Reference in New Issue
Block a user