page tableの構造
https://os.phil-opp.com/paging-introduction/#example-translation
x86_64だと4-level page tableが採用されている
Page Tableの主要な要素は、Index, frame, flagsの3つ
frameにはアドレス値が入っている
4-level page tableはこのPage Tableが4階層で構成されている.
構造としては以下の通り
48-39の部分がlevel 4のindexにそのまま当てはまる.
例: 0000000000000000 000001111 110000000 100000001 000110000 000000000000
1ブロック9bitなので、2^9=512
で512entryで、1つのentryのサイズが8バイトであるため、1つのpage tableのサイズは512 * 8B = 4KiB
である. なので、Page Tableが必要になる度に4KiBのメモリが必要になる.
CR3
<= lv4 page tableの仮想アドレス値が格納されている
lv4[0xb000001111]
<= frameに格納されている数値をlv3 page tableの仮想アドレス値として扱う
lv3[110000000]
<= frameに格納されている数値をlv2 page tableの仮想アドレス値として扱う
lv2[100000001]
<= frameに格納されている数値をlv1 page tableの仮想アドレス値として扱う
lv1[000110000]
<= frameに格納されている数値を物理アドレス値をして扱う (Identity Mappingだとこの値がそのまま解決された物理アドレスとして扱う. Map at a Fixed Offsetだと上の図のPage Offset分をこの物理アドレスに加算した値を解決された物理アドレスとして扱う
仮想アドレスと物理アドレスのmapping方法
Identity Mapping
仮想アドレスが物理アドレスと同じ値でmapされる
例: Identity Mappingで仮想アドレス0xb8000
を物理アドレスに翻訳すると0xb8000
になる
Map at a Fixed Offset
Identity Mappingの次にパフォーマンスが良い
linuxはこれを使ってるらしい
仮想アドレスが仮想アドレス+offset値でmapされる
例: Identity Mappingで仮想アドレス0xb8000
を物理アドレスに翻訳すると0xb8000
になる
- 仮想アドレスにoffsetを足して使われる
その他
- Map the Complete Physical Memory
- Temporary Mapping
- Recursive Page Tables
- etc...
boot後
bootloaderがkernelを起動した時既にIdentity Mappingが施されている.
詳しくはここを参照: https://os.phil-opp.com/paging-introduction/#implementation
kernelが実装するpagingの用途
初めに私は勘違いしていました
- kernelが動作するにはpagingが必要
- pagingはkernelが実装する
あれ???循環依存してるので、これは何かおかしいぞ と。
Frontend Architecture@2x (44).png
実際にはこうでした
- kernelが動作するにはpagingが必要 <= bootloaderが設定してある
- pagingはkernelが実装する <= user landのpagingはkernelが実装する
つまり、kernelのmemoryのmapping方法とuser landのmemoryのmapping方法は基本的に違います. (user landにIdentity Mappingを採用したら同じだけど
以下がその図です
kernelでpage tableをmapしてみる
// in src/memory.rs
use x86_64::{
PhysAddr,
structures::paging::{Page, PhysFrame, Mapper, Size4KiB, FrameAllocator}
};
/// Creates an example mapping for the given page to frame `0xb8000`.
pub fn create_example_mapping(
page: Page,
mapper: &mut OffsetPageTable,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) {
use x86_64::structures::paging::PageTableFlags as Flags;
let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000));
let flags = Flags::PRESENT | Flags::WRITABLE;
let map_to_result = unsafe {
// FIXME: this is not safe, we do it only for testing
mapper.map_to(page, frame, flags, frame_allocator)
};
map_to_result.expect("map_to failed").flush();
}
// in src/main.rs
fn kernel_main(boot_info: &'static BootInfo) -> ! {
use blog_os::memory;
use x86_64::{structures::paging::Page, VirtAddr}; // new import
[…] // hello world and blog_os::init
let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset);
let mut mapper = unsafe { memory::init(phys_mem_offset) };
let mut frame_allocator = memory::EmptyFrameAllocator;
// map an unused page
let page = Page::containing_address(VirtAddr::new(0));
memory::create_example_mapping(page, &mut mapper, &mut frame_allocator);
// write the string `New!` to the screen through the new mapping
let page_ptr: *mut u64 = page.start_address().as_mut_ptr();
unsafe { page_ptr.offset(400).write_volatile(0x_f021_f077_f065_f04e)};
[…] // test_main(), "it did not crash" printing, and hlt_loop()
}
ここのPage::containing_address(VirtAddr::new(0));
で取得したPageは未解決な仮想アドレスである.
このアドレスを上だとcreate_example_mappingに通すと、仮想アドレス0xb0
は物理アドレス0xb8000
にmapされる.
なので、下の2行でvga text bufferに書き込める.
let page_ptr: *mut u64 = page.start_address().as_mut_ptr();
unsafe { page_ptr.offset(400).write_volatile(0x_f021_f077_f065_f04e)};
補足
vga text bufferがbootloaderがkernelの為に設定したIdentity Mappingのtableで仮想アドレス0xb8000
にアクセスすると、物理アドレス0xb8000
にアクセスできる.
kernelがサンプルで用意したcreate_example_mappingを通したPageは物理アドレス0xb8000
にmappingされる
その際に、複数のPage Tableのframeが同じ物理アドレスを指すことになるが、これは問題ない