PageTableの簡略メモ

## page tableの構造
https://os.phil-opp.com/paging-introduction/#example-translation
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141711/5111c278-ff5a-540c-d684-a62b70242b2d.png)
[x86_64だと4-level page tableが採用されている](https://os.phil-opp.com/paging-introduction/#paging-on-x86-64)


Page Tableの主要な要素は、Index, frame, flagsの3つ
frameにはアドレス値が入っている

4-level page tableはこのPage Tableが4階層で構成されている.
構造としては以下の通り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141711/889d46bb-d07a-a4f3-14ed-f3d8cf2d3f80.png)
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
https://wiki.osdev.org/Identity_Paging

仮想アドレスが物理アドレスと同じ値で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が実装する

あれ？？？循環依存してるので、これは何かおかしいぞ :thinking: と。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141711/6631d4b6-5abf-d275-9d33-173af3775066.png)
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を採用したら同じだけど
以下がその図です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141711/e4e48f3b-3acc-e3a0-63a3-132a18ed88e6.png)

## kernelでpage tableをmapしてみる

```rust:create_example_mapping
// 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();
}
```

https://os.phil-opp.com/paging-implementation/#creating-the-mapping

```rust
// 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に書き込める.

```rust
    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が同じ物理アドレスを指すことになるが、これは問題ない

### 参考文献

* https://os.phil-opp.com/paging-introduction
* https://os.phil-opp.com/paging-implementation
* https://www.youtube.com/watch?v=WKuNyf_lLtY