diff --git a/blog/content/edition-2/posts/09-paging-implementation/index.ja.md b/blog/content/edition-2/posts/09-paging-implementation/index.ja.md index 58f62a57..34540189 100644 --- a/blog/content/edition-2/posts/09-paging-implementation/index.ja.md +++ b/blog/content/edition-2/posts/09-paging-implementation/index.ja.md @@ -10,7 +10,7 @@ translation_based_on_commit = "27ab4518acbb132e327ed4f4f0508393e9d4d684" translators = ["woodyZootopia"] +++ -この記事では私達のカーネルをページングに対応させる方法についてお伝えします。まず物理ページテーブルのフレームにカーネルがアクセスできるようにする様々な方法を示し、それらの利点と欠点について議論します。次にアドレス変換関数を、ついで新しいマッピングを作るための関数を実装します。 +この記事では私達のカーネルをページングに対応させる方法についてお伝えします。まずページテーブルの物理フレームにカーネルがアクセスできるようにする様々な方法を示し、それらの利点と欠点について議論します。次にアドレス変換関数を、ついで新しい対応付けを作るための関数を実装します。 @@ -24,6 +24,8 @@ translators = ["woodyZootopia"] ## 導入 +## 記事へのリンクを日本語のものに直す + [1つ前の記事][previous post]ではページングの概念を説明しました。セグメンテーションと比較することによってページングのメリットを示し、ページングとページテーブルの仕組みを説明し、そして`x86_64`における4層ページテーブルの設計を導入しました。ブートローダはすでにページテーブルの階層構造を設定してしまっているので、私達のカーネルは既に仮想アドレス上で動いているということを学びました。これにより、不正なメモリアクセスは、任意の物理メモリを書き換えてしまう代わりにページフォルト例外を発生させるので、安全性が向上しています。 [previous post]: @/edition-2/posts/08-paging-introduction/index.ja.md @@ -52,24 +54,24 @@ translators = ["woodyZootopia"] ![A virtual and a physical address space with various virtual pages mapped to the physical frame with the same address](identity-mapped-page-tables.svg) -この例では、いくつかの恒等対応したページテーブルのフレームが見てとれます。こうすることで、ページテーブルの物理アドレスは仮想アドレスとしても正当になり、よってCR3レジスタから始めることで全てのレベルのページテーブルに簡単にアクセスできます。 +この例では、いくつかの恒等対応したページテーブルのフレームが見てとれます。こうすることで、ページテーブルの物理アドレスは仮想アドレスと同じ値になり、よってCR3レジスタから始めることで全ての階層のページテーブルに簡単にアクセスできます。 -しかし、この方法では仮想アドレス空間が散らかってしまい、より大きいサイズの連続したメモリを見つけることが難しくなります。例えば、上の図において、[ファイルをメモリにマップする][memory-mapping a file]ために1000KiBの大きさの仮想メモリ領域を作りたいとします。`28KiB`を始点として領域を作ろうとすると、`1004KiB`のところで既存のページと衝突してしまうのでうまくいきません。そのため、`1008KiB`のような、十分な広さで対応付けのない領域が見つかるまで更に探さないといけません。これは[セグメンテーション][segmentation]の時に見た断片化の問題に似ています。 +しかし、この方法では仮想アドレス空間が散らかってしまい、大きいサイズの連続したメモリを見つけることが難しくなります。例えば、上の図において、[ファイルをメモリにマップする][memory-mapping a file]ために1000KiBの大きさの仮想メモリ領域を作りたいとします。`28KiB`を始点として領域を作ろうとすると、`1004KiB`のところで既存のページと衝突してしまうのでうまくいきません。そのため、`1008KiB`のような、十分な広さで対応付けのない領域が見つかるまで更に探さないといけません。これは[セグメンテーション][segmentation]の時に見た断片化の問題に似ています。 [memory-mapping a file]: https://en.wikipedia.org/wiki/Memory-mapped_file [segmentation]: @/edition-2/posts/08-paging-introduction/index.md#fragmentation -同様に、新しいページテーブルを作ることもずっと難しくなります。なぜなら、対応するページがまだ使われていない物理フレームを見つけないといけないからです。例えば、メモリマップト (に対応づけられた) ファイルのために`1008KiB`から1000KiBにわたって仮想メモリを占有したとしましょう。すると、すると、物理アドレス`1000KiB`から`2008KiB`までのフレームは、もう恒等対応を作ることができないので、使用することができません。 +同様に、新しいページテーブルを作ることもずっと難しくなります。なぜなら、対応するページがまだ使われていない物理フレームを見つけないといけないからです。例えば、メモリマップト (に対応づけられた) ファイルのために`1008KiB`から1000KiBにわたって仮想メモリを占有したとしましょう。すると、すると、物理アドレス`1000KiB`から`2008KiB`までのフレームは、もう恒等対応を作ることができないので使用することができません。 ### 固定オフセットの対応づけ -仮想アドレス空間を散らかしてしまうという問題を回避するために、**ページテーブルの対応づけのために別のメモリ領域を使う**ことができます。ページテーブルを恒等対応させる代わりに、仮想アドレス空間で一定のオフセットをおいて対応づけてみましょう。例えば、オフセットを10TiBにしてみましょう: +仮想アドレス空間を散らかしてしまうという問題を回避するために、**ページテーブルの対応づけのために別のメモリ領域を使う**ことができます。ページテーブルを恒等対応させる代わりに、仮想アドレス空間で一定の補正値 (オフセット) をおいて対応づけてみましょう。例えば、オフセットを10TiBにしてみましょう: ![The same figure as for the identity mapping, but each mapped virtual page is offset by 10 TiB.](page-tables-mapped-at-offset.svg) `10TiB`から`10TiB+物理メモリ全体の大きさ`の範囲の仮想メモリをページテーブルの対応付け専用に使うことで、恒等対応のときに存在していた衝突問題を回避しています。このように巨大な領域を仮想アドレス空間内に用意するのは、仮想アドレス空間が物理メモリの大きさより遥かに大きい場合にのみ可能です。x86_64で用いられている48bit(仮想)アドレス空間は256TiBもの大きさがあるので、これは問題ではありません。 -この方法では、新しいページテーブルを作るたびに新しい対応付けを作る必要があるという欠点があります。また、他のアドレス空間のページテーブルにアクセスすることができると、新しいプロセスを作るときに便利なのですがこれも不可能です。 +この方法では、新しいページテーブルを作るたびに新しい対応付けを作る必要があるという欠点があります。また、他のアドレス空間のページテーブルにアクセスすることができると新しいプロセスを作るときに便利なのですが、これも不可能です。 ### 物理メモリ全体を対応付ける @@ -81,7 +83,7 @@ translators = ["woodyZootopia"] この方法の欠点は、物理メモリへの対応付けを格納するために、追加でページテーブルが必要になるところです。これらのページテーブルもどこかに格納されなければならず、したがって物理メモリの一部を占有することになります。これはメモリの量が少ないデバイスにおいては問題となりえます。 -しかし、x86_64においては、通常の4KiBサイズのページに代わって、大きさ2MiBの[huge pages]を対応付けに使うことができます。こうすれば、例えば32GiBの物理メモリを対応付けるのに、レベル3テーブル1個とレベル2テーブル32個があればいいので、たったの132KiBしか必要ではありません。huge pagesは、トランスレーション・ルックアサイド・バッファ (TLB) のエントリをあまり使わないので、キャッシュ的にも効率が良いです。 +しかし、x86_64においては、通常の4KiBサイズのページに代わって、大きさ2MiBの[huge page][huge pages]を対応付けに使うことができます。こうすれば、例えば32GiBの物理メモリを対応付けるのに、レベル3テーブル1個とレベル2テーブル32個があればいいので、たったの132KiBしか必要ではありません。huge pagesは、トランスレーション・ルックアサイド・バッファ (TLB) のエントリをあまり使わないので、キャッシュ的にも効率が良いです。 [huge pages]: https://en.wikipedia.org/wiki/Page_%28computer_memory%29#Multiple_page_sizes @@ -123,7 +125,7 @@ translators = ["woodyZootopia"] CPUにこのエントリを辿らせるようにすると、レベル3テーブルではなく、そのレベル4テーブルに再び到達します。これは再帰的関数(自らを呼び出す関数)に似ているので、**再帰的ページテーブル**と呼ばれます。レベル4テーブルのすべてのエントリはレベル3テーブルを指しているとCPUは思っているので、CPUはいまレベル4テーブルをレベル3テーブルとして扱っているということに注目してください。これがうまく行くのは、x86_64においてはすべてのレベルのテーブルが全く同じレイアウトを持っているためです。 -実際に変換を始める前に、この再帰エントリを1回以上たどることで、CPUのたどる階層の数を短くできます。例えば、一度再帰的エントリを辿ったあとでレベル3テーブルに進むと、CPUはレベル3テーブルをレベル2テーブルだと思い込みます。同様に、レベル2テーブルをレベル1テーブルだと、レベル1テーブルを対応付けられた(物理)フレームだと思います。CPUがこれを物理フレームだと思っているということは、レベル1ページテーブルを読み書きできるということを意味します。下の図はこの5回の変換ステップを示しています: +実際に変換を始める前に、この再帰エントリを1回以上たどることで、CPUのたどる階層の数を短くできます。例えば、一度再帰エントリを辿ったあとでレベル3テーブルに進むと、CPUはレベル3テーブルをレベル2テーブルだと思い込みます。同様に、レベル2テーブルをレベル1テーブルだと、レベル1テーブルを対応付けられた(物理)フレームだと思います。CPUがこれを物理フレームだと思っているということは、レベル1ページテーブルを読み書きできるということを意味します。下の図はこの5回の変換ステップを示しています: ![The above example 4-level page hierarchy with 5 arrows: "Step 0" from CR4 to level 4 table, "Step 1" from level 4 table to level 4 table, "Step 2" from level 4 table to level 3 table, "Step 3" from level 3 table to level 2 table, and "Step 4" from level 2 table to level 1 table.](recursive-page-table-access-level-1.png) @@ -139,74 +141,75 @@ CPUにこのエントリを辿らせるようにすると、レベル3テーブ この概念を理解するのは難しいかもしれませんが、実際これは非常にうまく行くのです。 -下のセクションでは、再帰エントリをたどるための仮想アドレスを構成する方法について説明します。私達の(カーネルの)実装には再帰的ページングは使わないので、これを読まずに記事の続きを読み進めても構いません。もし興味がおありでしたら、下の「アドレス計算」をクリックして展開してください。 +下のセクションでは、再帰エントリをたどるための仮想アドレスを構成する方法について説明します。私達の(OSの)実装には再帰的ページングは使わないので、これを読まずに記事の続きを読み進めても構いません。もし興味がおありでしたら、下の「アドレス計算」をクリックして展開してください。 ---

アドレス計算

-We saw that we can access tables of all levels by following the recursive entry once or multiple times before the actual translation. Since the indexes into the tables of the four levels are derived directly from the virtual address, we need to construct special virtual addresses for this technique. Remember, the page table indexes are derived from the address in the following way: +実際の変換の前に再帰的移動を1回または複数回行うことですべての階層のテーブルにアクセスできるということを見てきました。4つのテーブルそれぞれのどのインデクスが使われるかは仮想アドレスから直接計算されていましたから、再帰エントリを使うためには特別な仮想アドレスを作り出す必要があります。ページテーブルのインデクスは仮想アドレスから以下のように計算されていたことを思い出してください: ![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) -Let's assume that we want to access the level 1 page table that maps a specific page. As we learned above, this means that we have to follow the recursive entry one time before continuing with the level 4, level 3, and level 2 indexes. To do that we move each block of the address one block to the right and set the original level 4 index to the index of the recursive entry: +あるページを対応付けているレベル1テーブルにアクセスしたいとします。上で学んだように、このためには再帰エントリを1度辿ってからレベル4,3,2のインデクスへと続けていく必要があります。これをするために、それぞれのアドレスブロックを一つ右にずらし、レベル4のインデクスがあったところに再帰エントリのインデクスをセットします: ![Bits 0–12 are the offset into the level 1 table frame, bits 12–21 the level 2 index, bits 21–30 the level 3 index, bits 30–39 the level 4 index, and bits 39–48 the index of the recursive entry](table-indices-from-address-recursive-level-1.svg) -For accessing the level 2 table of that page, we move each index block two blocks to the right and set both the blocks of the original level 4 index and the original level 3 index to the index of the recursive entry: +そのページのレベル2テーブルにアクセスしたい場合、それぞれのブロックを2つ右にずらし、レベル4と3のインデクスがあったところに再帰エントリのインデクスをセットします: ![Bits 0–12 are the offset into the level 2 table frame, bits 12–21 the level 3 index, bits 21–30 the level 4 index, and bits 30–39 and bits 39–48 are the index of the recursive entry](table-indices-from-address-recursive-level-2.svg) -Accessing the level 3 table works by moving each block three blocks to the right and using the recursive index for the original level 4, level 3, and level 2 address blocks: +レベル3テーブルにアクセスする場合、それぞれのブロックを3つ右にずらし、レベル4,3,2のインデクスがあったところに再帰インデクスを使います: ![Bits 0–12 are the offset into the level 3 table frame, bits 12–21 the level 4 index, and bits 21–30, bits 30–39 and bits 39–48 are the index of the recursive entry](table-indices-from-address-recursive-level-3.svg) -Finally, we can access the level 4 table by moving each block four blocks to the right and using the recursive index for all address blocks except for the offset: +最後に、レベル4テーブルにはそれぞれのブロックを4ブロックずらし、オフセットを除いてすべてのアドレスブロックに再帰インデクスを使うことでアクセスできます: ![Bits 0–12 are the offset into the level l table frame and bits 12–21, bits 21–30, bits 30–39 and bits 39–48 are the index of the recursive entry](table-indices-from-address-recursive-level-4.svg) We can now calculate virtual addresses for the page tables of all four levels. We can even calculate an address that points exactly to a specific page table entry by multiplying its index by 8, the size of a page table entry. +これで、4つの階層すべてのページテーブルの仮想アドレスを計算できます。 -The table below summarizes the address structure for accessing the different kinds of frames: +下の表は、それぞれの種類のフレームにアクセスするためのアドレス構造をまとめたものです: -Virtual Address for | Address Structure ([octal]) -------------------- | ------------------------------- -Page | `0o_SSSSSS_AAA_BBB_CCC_DDD_EEEE` -Level 1 Table Entry | `0o_SSSSSS_RRR_AAA_BBB_CCC_DDDD` -Level 2 Table Entry | `0o_SSSSSS_RRR_RRR_AAA_BBB_CCCC` -Level 3 Table Entry | `0o_SSSSSS_RRR_RRR_RRR_AAA_BBBB` -Level 4 Table Entry | `0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA` +……の仮想アドレス | アドレス構造([8進][octal]) +------------------- | ------------------------------- +ページ | `0o_SSSSSS_AAA_BBB_CCC_DDD_EEEE` +レベル1テーブルエントリ | `0o_SSSSSS_RRR_AAA_BBB_CCC_DDDD` +レベル2テーブルエントリ | `0o_SSSSSS_RRR_RRR_AAA_BBB_CCCC` +レベル3テーブルエントリ | `0o_SSSSSS_RRR_RRR_RRR_AAA_BBBB` +レベル4テーブルエントリ | `0o_SSSSSS_RRR_RRR_RRR_RRR_AAAA` [octal]: https://en.wikipedia.org/wiki/Octal -Whereas `AAA` is the level 4 index, `BBB` the level 3 index, `CCC` the level 2 index, and `DDD` the level 1 index of the mapped frame, and `EEEE` the offset into it. `RRR` is the index of the recursive entry. When an index (three digits) is transformed to an offset (four digits), it is done by multiplying it by 8 (the size of a page table entry). With this offset, the resulting address directly points to the respective page table entry. +ただし、`AAA`がレベル4インデクス、`BBB`がレベル3インデクス、`CCC`がレベル2インデクス、`DDD`が対応付けられたフレームのレベル1インデクス、`EEE`がオフセットです。`RRR`が再帰エントリのインデクスです。インデクス(3ケタ)をオフセット(4ケタ)に変換するときは、8倍(ページテーブルエントリのサイズ倍)しています。 -`SSSSSS` are sign extension bits, which means that they are all copies of bit 47. This is a special requirement for valid addresses on the x86_64 architecture. We explained it in the [previous post][sign extension]. +`SSSSS`は符号拡張ビットです、すなわち47番目のビットのコピーです。これはx86_64におけるアドレスの特殊な要求の一つです。これは[前回の記事][sign extension]で説明しました。 [sign extension]: @/edition-2/posts/08-paging-introduction/index.md#paging-on-x86-64 -We use [octal] numbers for representing the addresses since each octal character represents three bits, which allows us to clearly separate the 9-bit indexes of the different page table levels. This isn't possible with the hexadecimal system where each character represents four bits. +[8進][octal]数を用いたのは、8進数の1文字が3ビットを表すため、9ビットからなるそれぞれのページテーブルをきれいに分けることができるためです。4ビットからなる16進ではこうはいきません。 -##### In Rust Code +##### Rustのコードでは…… -To construct such addresses in Rust code, you can use bitwise operations: +これらのアドレスをRustのコードで構成するには、ビット演算を用いるとよいです: ```rust -// the virtual address whose corresponding page tables you want to access +// この仮想アドレスに対応するページテーブルにアクセスしたい let addr: usize = […]; -let r = 0o777; // recursive index -let sign = 0o177777 << 48; // sign extension +let r = 0o777; // 再帰インデクス +let sign = 0o177777 << 48; // 符号拡張 -// 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 l4_idx = (addr >> 39) & 0o777; // レベル4インデクス +let l3_idx = (addr >> 30) & 0o777; // レベル3インデクス +let l2_idx = (addr >> 21) & 0o777; // レベル2インデクス +let l1_idx = (addr >> 12) & 0o777; // レベル1インデクス 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 = @@ -217,9 +220,10 @@ let level_1_table_addr = sign | (r << 39) | (l4_idx << 30) | (l3_idx << 21) | (l2_idx << 12); ``` -The above code assumes that the last level 4 entry with index `0o777` (511) is recursively mapped. This isn't the case currently, so the code won't work yet. See below on how to tell the bootloader to set up the recursive mapping. +上のコードは、レベル4エントリの最後(インデクス`0o777`すなわち511)が再帰対応していると仮定しています。この対応はまだ行っていないので、この仮定は正しくありません。以下でブートローダに再帰対応付けを設定させる方法を説明します。 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: +ビット演算を自前で行う代わりに、`x86_64`クレートの[`RecursivePageTable`]型を使うこともできます。これは様々なページ操作の安全な抽象化を提供します。例えば、以下のコードは仮想アドレスを対応付けられた物理アドレスに変換する方法を示しています [`RecursivePageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html @@ -229,7 +233,7 @@ Alternatively to performing the bitwise operations by hand, you can use the [`Re use x86_64::structures::paging::{Mapper, Page, PageTable, RecursivePageTable}; use x86_64::{VirtAddr, PhysAddr}; -/// Creates a RecursivePageTable instance from the level 4 address. +/// レベル4アドレスからRecursivePageTableインスタンスをつくる let level_4_table_addr = […]; let level_4_table_ptr = level_4_table_addr as *mut PageTable; let recursive_page_table = unsafe { @@ -238,63 +242,65 @@ let recursive_page_table = unsafe { } -/// Retrieve the physical address for the given virtual address +/// 与えられた仮想アドレスの物理アドレスを取得する let addr: u64 = […] 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())) ``` -Again, a valid recursive mapping is required for this code. With such a mapping, the missing `level_4_table_addr` can be calculated as in the first code example. +繰り返しになりますが、このコード(が正しく実行される)には正しい再帰対応がなされていることが必要となります。この対応付けがあるなら、空欄になっている`level_4_table_addr`を最初のコード例と同じ値にすればよいです。
--- -Recursive Paging is an interesting technique that shows how powerful a single mapping in a page table can be. It is relatively easy to implement and only requires a minimal amount of setup (just a single recursive entry), so it's a good choice for first experiments with paging. +再帰的ページングは、ページテーブルのたった一つの対応付けがいかに強力に使えるかを示す興味深いテクニックです。比較的実装するのが簡単であり、ほとんど設定も必要でない(一つ再帰エントリを作るだけ)ので、ページングを使って最初に実装するのに格好の対象でしょう。 -However, it also has some disadvantages: +しかし、いくつか欠点もあります: -- It occupies a large amount of virtual memory (512GiB). This isn't a big problem in the large 48-bit address space, but it might lead to suboptimal cache behavior. -- It only allows accessing the currently active address space easily. Accessing other address spaces is still possible by changing the recursive entry, but a temporary mapping is required for switching back. We described how to do this in the (outdated) [_Remap The Kernel_] post. -- It heavily relies on the page table format of x86 and might not work on other architectures. +- 大量の仮想メモリ領域(512GiB)を占有してしまう。私達の使っている48bitアドレス空間は巨大なのでこのことはさしたる問題にはなりませんが、キャッシュの挙動が最適でなくなってしまうかもしれません。 +- 現在有効なアドレス空間にしか簡単にはアクセスできない。他のアドレス空間にアクセスするのは再帰エントリを変更することで可能ではあるものの、もとに戻すためには一時的対応付けが必要。これを行う方法については[カーネルをリマップする][_Remap The Kernel_](未訳、また旧版のため情報が古い)という記事を読んでください。 +- x86のページテーブルの方式に強く依存しており、他のアーキテクチャでは動作しないかもしれない。 [_Remap The Kernel_]: https://os.phil-opp.com/remap-the-kernel/#overview -## Bootloader Support +## ブートローダのサポート -All of these approaches require page table modifications for their setup. For example, mappings for the physical memory need to be created or an entry of the level 4 table needs to be mapped recursively. The problem is that we can't create these required mappings without an existing way to access the page tables. +これらのアプローチはすべて、準備のためにページテーブルに対する修正が必要になります。例えば、物理メモリへの対応付けを作ったり、レベル4テーブルのエントリを再帰的に対応付けたりなどです。問題は、これらの必要な対応付けを作るためには、すでにページテーブルにアクセスできるようになっていなければいけないということです。 -This means that we need the help of the bootloader, which creates the page tables that our kernel runs on. The bootloader has access to the page tables, so it can create any mappings that we need. In its current implementation, the `bootloader` crate has support for two of the above approaches, controlled through [cargo features]: +これが意味するのは、私達のカーネルが使うページテーブルを作っている、ブートローダの手助けが必要になるということです。ブートローダはページテーブルへのアクセスができますから、私達の必要とするどんな対応付けも作れます。`bootloader`クレートは上の2つのアプローチをどちらもサポートしており、現在の実装においては[cargoのfeatures][cargo features]を使ってこれらをコントロールします。 [cargo features]: https://doc.rust-lang.org/cargo/reference/features.html#the-features-section -- The `map_physical_memory` feature maps the complete physical memory somewhere into the virtual address space. Thus, the kernel can access all physical memory and can follow the [_Map the Complete Physical Memory_](#map-the-complete-physical-memory) approach. -- With the `recursive_page_table` feature, the bootloader maps an entry of the level 4 page table recursively. This allows the kernel to access the page tables as described in the [_Recursive Page Tables_](#recursive-page-tables) section. +- `map_physical_memory` featureを使うと、全物理メモリを仮想アドレス空間のどこかに対応付けます。そのため、カーネルはすべての物理メモリにアクセスでき、[上で述べた方法に従って物理メモリ全体を対応付ける](#wu-li-memoriquan-ti-wodui-ying-fu-keru)ことができます。 +- `recursive_page_table` featureでは、ブートローダはレベル4ページテーブルのエントリを再帰的に対応付けます。これによりカーネルは[再帰的ページテーブル](#zai-gui-de-peziteburu)で述べた方法に従ってページテーブルにアクセスすることができます。 + +私達のカーネルには、シンプルでプラットフォーム非依存でより強力である(ページテーブルのフレームでないメモリにもアクセスできる)1つ目の方法を取ることにします。必要なブートローダの機能 (feature) を有効化するために、`map_physical_memory` featureを`bootloader`のdependencyに追加します。 -We choose the first approach for our kernel since it is simple, platform-independent, and more powerful (it also allows access to non-page-table-frames). To enable the required bootloader support, we add the `map_physical_memory` feature to our `bootloader` dependency: ```toml [dependencies] bootloader = { version = "0.9.8", features = ["map_physical_memory"]} ``` -With this feature enabled, the bootloader maps the complete physical memory to some unused virtual address range. To communicate the virtual address range to our kernel, the bootloader passes a _boot information_ structure. +この機能を有効化すると、ブートローダは物理メモリの全体を、とある未使用の仮想アドレス空間に対応付けます。この仮想アドレスの範囲をカーネルに伝えるために、ブートローダは**boot information**構造体を渡します。 + ### Boot Information -The `bootloader` crate defines a [`BootInfo`] struct that contains all the information it passes to our kernel. The struct is still in an early stage, so expect some breakage when updating to future [semver-incompatible] bootloader versions. With the `map_physical_memory` feature enabled, it currently has the two fields `memory_map` and `physical_memory_offset`: +`bootloader`クレートは、カーネルに渡されるすべての情報を格納する[`BootInfo`]構造体を定義しています。この構造体はまだ開発の初期段階にあり、将来の[対応していないsemverの][semver-incompatible]ブートローダのバージョンに更新した際には、うまく動かなくなることが予想されます。`map_physical_memory` featureが有効化されているので、いまこれは`memory_map`と`physical_memory_offset`という2つのフィールドを持っています: [`BootInfo`]: https://docs.rs/bootloader/0.9.3/bootloader/bootinfo/struct.BootInfo.html [semver-incompatible]: https://doc.rust-lang.org/stable/cargo/reference/specifying-dependencies.html#caret-requirements -- The `memory_map` field contains an overview of the available physical memory. This tells our kernel how much physical memory is available in the system and which memory regions are reserved for devices such as the VGA hardware. The memory map can be queried from the BIOS or UEFI firmware, but only very early in the boot process. For this reason, it must be provided by the bootloader because there is no way for the kernel to retrieve it later. We will need the memory map later in this post. -- The `physical_memory_offset` tells us the virtual start address of the physical memory mapping. By adding this offset to a physical address, we get the corresponding virtual address. This allows us to access arbitrary physical memory from our kernel. +- `memory_map`フィールドは、利用可能な物理メモリの情報の概要を保持しています。システムの利用可能な物理メモリがどのくらいかや、どのメモリ領域がVGAハードウェアのようなデバイスのために予約されているかをカーネルに伝えます。これらのメモリ対応付けはBIOSやUEFIファームウェアからブートのごく初期に限り取得することが可能です。そのため、これらをカーネルが後で取得することはできないので、ブートローダによって提供する必要があるわけです。このメモリ対応付けは後で必要となります。 +- `physical_memory_offset`は、物理メモリの対応付けの始まっている仮想アドレスです。このオフセットを物理アドレスに追加することによって、対応する仮想アドレスを得られます。これによって、カーネルから任意の物理アドレスにアクセスできます。 -The bootloader passes the `BootInfo` struct to our kernel in the form of a `&'static BootInfo` argument to our `_start` function. We don't have this argument declared in our function yet, so let's add it: +ブートローダは`BootInfo`構造体を`_start`関数の`&'static BootInfo`引数という形でカーネルに渡します。この引数は私達の関数ではまだ宣言していなかったので追加します: ```rust // in src/main.rs @@ -302,18 +308,18 @@ The bootloader passes the `BootInfo` struct to our kernel in the form of a `&'st use bootloader::BootInfo; #[no_mangle] -pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { // new argument +pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { // 新しい引数 […] } ``` -It wasn't a problem to leave off this argument before because the x86_64 calling convention passes the first argument in a CPU register. Thus, the argument is simply ignored when it isn't declared. However, it would be a problem if we accidentally used a wrong argument type, since the compiler doesn't know the correct type signature of our entry point function. +今までこの引数を無視していましたが、x86_64の呼出し規約は最初の引数をCPUレジスタに渡していたため、これは問題ではありませんでした。つまり、引数が宣言されていなかったとき、単に無視されていたわけです。しかし、もし引数の型を間違えてしまうと、コンパイラが私達のエントリポイント関数の正しい型シグネチャがわからなくなってしまうので、それは問題です。 -### The `entry_point` Macro +### `entry_point`マクロ -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. +私達の`_start`関数はブートローダから外部呼び出しされるので、私達の関数のシグネチャに対する検査は行われません。これにより、コンパイルエラーなしにあらゆる引数を取ることを許してしまい、いざ実行時にエラーになったり未定義動作を起こしてしまいます。 -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 the entry point. Let's rewrite our entry point function to use this macro: +私達のエントリポイント関数が常にブートローダの期待する正しいシグネチャを持っていることを保証するために、`bootloader`クレートは[`entry_point`]マクロによって、Rustの関数をエントリポイントとして型チェックしながら定義する方法を提供します。私達のエントリポイントをこのマクロを使って書き直してみましょう: [`entry_point`]: https://docs.rs/bootloader/0.6.4/bootloader/macro.entry_point.html @@ -329,9 +335,9 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { } ``` -We no longer need to use `extern "C"` or `no_mangle` for our entry point, as the macro defines the real lower 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 so that a compilation error occurs when we use a wrong function signature, for example by adding an argument or changing the argument type. +このマクロが本物の`_start`エントリポイントをより低レベルに定義してくれるので、`extern "C"`や`no_mangle`をエントリポイントに使う必要はもうありません。`kernel_main`関数は今や完全に普通のRustの関数なので、自由に名前をつけることができます。そして重要なのは、これは型チェックされているので、間違った関数シグネチャ(例えば引数を増やしたり引数の型を変えたり)にするとコンパイルエラーが発生するということです。 -Let's perform the same change in our `lib.rs`: +`lib.rs`に同じ変更を施しましょう: ```rust // in src/lib.rs @@ -342,23 +348,23 @@ use bootloader::{entry_point, BootInfo}; #[cfg(test)] entry_point!(test_kernel_main); -/// Entry point for `cargo test` +/// `cargo test`のエントリポイント #[cfg(test)] fn test_kernel_main(_boot_info: &'static BootInfo) -> ! { - // like before + // 前と同じ init(); test_main(); hlt_loop(); } ``` -Since the entry point is only used in test mode, we add the `#[cfg(test)]` attribute to all items. We give our test entry point the distinct name `test_kernel_main` to avoid confusion with the `kernel_main` of our `main.rs`. We don't use the `BootInfo` parameter for now, so we prefix the parameter name with a `_` to silence the unused variable warning. +こちらのエントリポイントはテストモードのときにのみ使用するので、`#[cfg(test)]`属性をすべての要素に付しています。`main.rs`の`kernel_main`関数と混同しないよう、`test_kernel_main`という別の名前をつけました。いまのところ`BootInfo`引数は使わないので、引数名の先頭に`_`をつけることでunused variable (未使用変数) 警告が出てくるのを防いでいます。 -## Implementation +## 実装 -Now that we have access to physical memory, we can finally start to implement our page table code. First, we will take a look at the currently active page tables that our kernel runs on. In the second step, we will create a translation function that returns the physical address that a given virtual address is mapped to. As the last step, we will try to modify the page tables in order to create a new mapping. +物理メモリへのアクセスができるようになったので、いよいよページテーブルのコードを実装できます。そのためにまず、現在有効な、私達のカーネルが使用しているページテーブルを見てみます。次に、与えられた仮想アドレスが対応付けられている物理アドレスを返す変換関数を作ります。最後に新しい対応付けを作るためにページテーブルを修正してみます。 -Before we begin, we create a new `memory` module for our code: +始める前に、`memory`モジュールを作ります: ```rust // in src/lib.rs @@ -366,11 +372,11 @@ Before we begin, we create a new `memory` module for our code: pub mod memory; ``` -For the module we create an empty `src/memory.rs` file. +このモジュールに合わせて`src/memory.rs`ファイルを作ります。 -### Accessing the Page Tables +### ページテーブルにアクセスする -At the [end of the previous post], we tried to take a look at the page tables our kernel runs on, but failed since we couldn't access the physical frame that the `CR3` register points to. We're now able to continue from there by creating an `active_level_4_table` function that returns a reference to the active level 4 page table: +[前の記事の最後]で、私達のカーネルの実行しているページテーブルを見てみようとしましたが、`CR3`レジスタの指す物理フレームにアクセスすることができなかったためそれはできませんでした。前回の続きとして、`active_level_4_table`という、現在有効 (アクティブ) なレベル4ページテーブルへの参照を返す関数を定義するところから始めましょう: [end of the previous post]: @/edition-2/posts/08-paging-introduction/index.md#accessing-the-page-tables @@ -382,12 +388,14 @@ use x86_64::{ VirtAddr, }; -/// Returns a mutable reference to the active level 4 table. +/// 有効なレベル4テーブルへの可変参照を返す。 /// -/// This function is unsafe because the caller must guarantee that the -/// complete physical memory is mapped to virtual memory at the passed -/// `physical_memory_offset`. Also, this function must be only called once -/// to avoid aliasing `&mut` references (which is undefined behavior). +/// この関数はunsafeである:全物理メモリが、渡された +/// `physical_memory_offset`(だけずらしたうえ)で +/// 仮想メモリへと対応付けられていることを呼び出し元が +/// 保証しなければならない。また、`&mut`参照が複数の +/// 名称を持つこと (mutable aliasingといい、動作が未定義) +/// につながるため、この関数は一度しか呼び出してはならない。 pub unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable { @@ -403,11 +411,11 @@ pub unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) } ``` -First, we read the physical frame of the active level 4 table from the `CR3` register. We then take its physical start address, convert it to an `u64`, and add it to `physical_memory_offset` to get the virtual address where the page table frame is mapped. Finally, we convert the virtual address to a `*mut PageTable` raw pointer through the `as_mut_ptr` method and then unsafely create a `&mut PageTable` reference from it. We create a `&mut` reference instead of a `&` reference because we will mutate the page tables later in this post. +まず、有効なレベル4テーブルの物理フレームを`CR3`レジスタから読みます。その開始物理アドレスを取り出し、`u64`に変換し、`physical_memory_offset`に足すことでそのページテーブルフレームに対応する仮想アドレスを得ます。最後に、`as_mut_ptr`メソッドを使ってこの仮想アドレスを`*mut PageTable`生ポインタに変換し、これから`&mut PageTable`参照を作ります(ここがunsafe)。`&`参照ではなく`&mut`参照にしているのは、後でこのページテーブルを変更するためです。 -We don't need to use an unsafe block here because Rust treats the complete body of an `unsafe fn` like a large `unsafe` block. This makes our code more dangerous since we could accidentally introduce an unsafe operation in previous lines without noticing. It also makes it much more difficult to spot the unsafe operations. There is an [RFC](https://github.com/rust-lang/rfcs/pull/2585) to change this behavior. +Rustは`unsafe fn`の中身全体を大きな`unsafe`ブロックであるかのように扱うので、ここでunsafeブロックを使う必要はありません。これでは、(unsafeを意図した)最後の行より前の行に間違ってunsafeな操作を書いても気づけないので、コードがより危険になります。また、どこがunsafeな操作であるのかを探すのも非常に難しくなります。そのため、この挙動を変更する[RFC](https://github.com/rust-lang/rfcs/pull/2585)が提案されています。 -We can now use this function to print the entries of the level 4 table: +この関数を使って、レベル4テーブルのエントリを出力してみましょう: ```rust // in src/main.rs @@ -437,34 +445,34 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { } ``` -First, we convert the `physical_memory_offset` of the `BootInfo` struct to a [`VirtAddr`] and pass it to the `active_level_4_table` function. We then use the `iter` function to iterate over the page table entries and the [`enumerate`] combinator to additionally add an index `i` to each element. We only print non-empty entries because all 512 entries wouldn't fit on the screen. +まず、`BootInfo`構造体の`physical_memory_offset`を[`VirtAddr`]に変換し、`active_level_4_table`関数に渡します。つぎに`iter`関数を使ってページテーブルの全エントリに対してfor文を回し、[`enumerate`]コンビネータをつかってそれぞれの要素にインデックス`i`を追加します。全512エントリを出力すると画面に収まらないので、 (から) でないエントリのみ出力します。 [`VirtAddr`]: https://docs.rs/x86_64/0.14.2/x86_64/addr/struct.VirtAddr.html [`enumerate`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.enumerate -When we run it, we see the following output: +実行すると、以下の出力を得ます: ![QEMU printing entry 0 (0x2000, PRESENT, WRITABLE, ACCESSED), entry 1 (0x894000, PRESENT, WRITABLE, ACCESSED, DIRTY), entry 31 (0x88e000, PRESENT, WRITABLE, ACCESSED, DIRTY), entry 175 (0x891000, PRESENT, WRITABLE, ACCESSED, DIRTY), and entry 504 (0x897000, PRESENT, WRITABLE, ACCESSED, DIRTY)](qemu-print-level-4-table.png) -We see that there are various non-empty entries, which all map to different level 3 tables. There are so many regions because kernel code, kernel stack, the physical memory mapping, and the boot information all use separate memory areas. +いくつかの空でないエントリがあり、いずれも異なるレベル3テーブルに対応づけられていることがわかります。このようにたくさんの領域があるのは、カーネルコード、カーネルスタック、物理メモリ対応、ブート情報が互いに離れたメモリ領域を使っているためです。 -To traverse the page tables further and take a look at a level 3 table, we can take the mapped frame of an entry convert it to a virtual address again: +ページテーブルを更に辿りレベル3テーブルを見るには、エントリに対応するフレームを取り出し再び仮想アドレスに変換すればよいです: ```rust -// in the `for` loop in src/main.rs +// src/main.rsのforループ内にて…… use x86_64::structures::paging::PageTable; if !entry.is_unused() { println!("L4 Entry {}: {:?}", i, entry); - // get the physical address from the entry and convert it + // このエントリから物理アドレスを得て、それを変換する let phys = entry.frame().unwrap().start_address(); let virt = phys.as_u64() + boot_info.physical_memory_offset; let ptr = VirtAddr::new(virt).as_mut_ptr(); let l3_table: &PageTable = unsafe { &*ptr }; - // print non-empty entries of the level 3 table + // レベル3テーブルの空でないエントリを出力する for (i, entry) in l3_table.iter().enumerate() { if !entry.is_unused() { println!(" L3 Entry {}: {:?}", i, entry); @@ -473,25 +481,25 @@ if !entry.is_unused() { } ``` -For looking at the level 2 and level 1 tables, we repeat that process for the level 3 and level 2 entries. As you can imagine, this gets very verbose quickly, so we don't show the full code here. +レベル2やレベル1のテーブルも、同じ手続きをレベル3とレベル2のエントリに対して繰り返すことで見ることができます。お察しの通りそれを書くとかなり長くなるので、コードの全てはここには示しません。 -Traversing the page tables manually is interesting because it helps to understand how the CPU performs the translation. However, most of the time we are only interested in the mapped physical address for a given virtual address, so let's create a function for that. +ページテーブルを手作業で辿ると、CPUが変換を行う様子を理解できて面白いです。しかし、多くの場合は与えられた仮想アドレスに対応する物理アドレスにのみ興味があるので、そのための関数を作りましょう。 -### Translating Addresses +### アドレスの変換 -For translating a virtual to a physical address, we have to traverse the four-level page table until we reach the mapped frame. Let's create a function that performs this translation: +仮想アドレスを物理アドレスに変換するには、4層のページテーブルを辿って対応するフレームにたどり着けばよいです。この変換を行う関数を作りましょう: ```rust // in src/memory.rs use x86_64::PhysAddr; -/// Translates the given virtual address to the mapped physical address, or -/// `None` if the address is not mapped. +/// 与えられた仮想アドレスを対応する物理アドレスに変換し、 +/// そのアドレスが対応付けられていないなら`None`を返す。 /// -/// This function is unsafe because the caller must guarantee that the -/// complete physical memory is mapped to virtual memory at the passed -/// `physical_memory_offset`. +/// この関数はunsafeである。なぜなら、呼び出し元は全物理メモリが与えられた +/// `physical_memory_offset`(だけずらした上)で対応付けられていることを +/// 保証しなくてはならないからである。 pub unsafe fn translate_addr(addr: VirtAddr, physical_memory_offset: VirtAddr) -> Option { @@ -499,25 +507,26 @@ pub unsafe fn translate_addr(addr: VirtAddr, physical_memory_offset: VirtAddr) } ``` -We forward the function to a safe `translate_addr_inner` function to limit the scope of `unsafe`. As we noted above, Rust treats the complete body of an unsafe fn like a large unsafe block. By calling into a private safe function, we make each `unsafe` operation explicit again. +`unsafe`の範囲を制限するために、この関数は、すぐにunsafeでない`translate_addr_inner`関数に制御を渡しています。先に述べたように、Rustはunsafeな関数の全体をunsafeブロックとして扱ってしまいます。呼び出した非公開の (プライベートな) unsafeでない関数の中にコードを書くことで、それぞれのunsafeな操作を明確にします。 -The private inner function contains the real implementation: +非公開な内部の関数に本当の実装を書いていきます: ```rust // in src/memory.rs -/// Private function that is called by `translate_addr`. +/// `translate_addr`により呼び出される非公開関数。 /// -/// This function is safe to limit the scope of `unsafe` because Rust treats -/// the whole body of unsafe functions as an unsafe block. This function must -/// only be reachable through `unsafe fn` from outside of this module. +/// Rustはunsafeな関数の全体をunsafeブロックとして扱ってしまうので、 +/// unsafeの範囲を絞るためにこの関数はunsafeにしていない。 +/// この関数をモジュール外から呼び出すときは、 +/// unsafeな関数を通じてのみ呼び出すこと。 fn translate_addr_inner(addr: VirtAddr, physical_memory_offset: VirtAddr) -> Option { use x86_64::structures::paging::page_table::FrameError; use x86_64::registers::control::Cr3; - // read the active level 4 frame from the CR3 register + // 有効なレベル4フレームをCR3レジスタから読む let (level_4_table_frame, _) = Cr3::read(); let table_indexes = [ @@ -525,56 +534,57 @@ fn translate_addr_inner(addr: VirtAddr, physical_memory_offset: VirtAddr) ]; let mut frame = level_4_table_frame; - // traverse the multi-level page table + // 複数層のページテーブルを辿る for &index in &table_indexes { - // convert the frame into a page table reference + // フレームをページテーブルの参照に変換する let virt = physical_memory_offset + frame.start_address().as_u64(); let table_ptr: *const PageTable = virt.as_ptr(); let table = unsafe {&*table_ptr}; - // read the page table entry and update `frame` + // ページテーブルエントリを読んで、`frame`を更新する let entry = &table[index]; frame = match entry.frame() { Ok(frame) => frame, Err(FrameError::FrameNotPresent) => return None, Err(FrameError::HugeFrame) => panic!("huge pages not supported"), + //huge pageはサポートしていません }; } - // calculate the physical address by adding the page offset + // ページオフセットを足すことで、目的の物理アドレスを計算する Some(frame.start_address() + u64::from(addr.page_offset())) } ``` -Instead of reusing our `active_level_4_table` function, we read the level 4 frame from the `CR3` register again. We do this because it simplifies this prototype implementation. Don't worry, we will create a better solution in a moment. +先程作った`active_level_4_table`関数を再利用せず、`CR3`レジスタからレベル4フレームを読み出すコードを再び書いています。これは簡単に試作するためであり、後でもっと良い方法で作り直すのでご心配なく。 -The `VirtAddr` struct already provides methods to compute the indexes into the page tables of the four levels. We store these indexes in a small array because it allows us to traverse the page tables using a `for` loop. Outside of the loop, we remember the last visited `frame` to calculate the physical address later. The `frame` points to page table frames while iterating, and to the mapped frame after the last iteration, i.e. after following the level 1 entry. +`Virtaddr`構造体には、インデクスから4つの階層のページテーブルを計算してくれるメソッドが備わっています。この4つのインデクスを配列に格納することで、これらを`for`ループを使って辿ります。`for`ループを抜けたら、最後に計算した`frame`を覚えているので、物理アドレスを計算できます。この`frame`は、forループの中ではページテーブルのフレームを指していて、最後のループのあと(すなわちレベル1エントリを辿ったあと)では対応する(物理)フレームを指しています。 -Inside the loop, we again use the `physical_memory_offset` to convert the frame into a page table reference. We then read the entry of the current page table and use the [`PageTableEntry::frame`] function to retrieve the mapped frame. If the entry is not mapped to a frame we return `None`. If the entry maps a huge 2MiB or 1GiB page we panic for now. +ループの中の話をします。前と同じように`physical_memory_offset`を使ってフレームをページテーブルの参照に変換します。次に、そのページテーブルのエントリを読み、[`PageTableEntry::frame`]関数を使って対応するフレームを取得します。もしエントリがフレームに対応付けられていなければ`None`を返します。もしエントリが2MiBや1GiBのhuge pageに対応付けられていたら、今のところはpanicすることにします。 [`PageTableEntry::frame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html#method.frame -Let's test our translation function by translating some addresses: +いくつかのアドレスを変換して、この変換関数がうまく行くかテストしてみましょう: ```rust // in src/main.rs fn kernel_main(boot_info: &'static BootInfo) -> ! { - // new import + // 新しいインポート use blog_os::memory::translate_addr; - […] // hello world and blog_os::init + […] // hello world と blog_os::init let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset); let addresses = [ - // the identity-mapped vga buffer page + // 恒等対応しているVGAバッファのページ 0xb8000, - // some code page + // コードページのどこか 0x201008, - // some stack page + // スタックページのどこか 0x0100_0020_1a10, - // virtual address mapped to physical address 0 + // 物理アドレス "0" に対応付けられている仮想アドレス boot_info.physical_memory_offset, ]; @@ -584,28 +594,28 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { println!("{:?} -> {:?}", virt, phys); } - […] // test_main(), "it did not crash" printing, and hlt_loop() + […] // test_main(), "it did not crash" の出力, および hlt_loop() } ``` -When we run it, we see the following output: +実行すると、以下の出力を得ます: ![0xb8000 -> 0xb8000, 0x201008 -> 0x401008, 0x10000201a10 -> 0x279a10, "panicked at 'huge pages not supported'](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. It's worth noting that the last 12 bits always stay the same after translation, which makes sense because these bits are the [_page offset_] and not part of the translation. +期待したとおり、恒等対応しているアドレス`0xb8000`は同じ物理アドレスに変換されました。コードページとスタックページは物理アドレスのどこかしかに変換されていますが、その場所はブートローダが私達のカーネルのために最初に作った対応づけに依存します。また、下から12ビットは変換のあとも常に同じであるということも注目に値します:この部分は[ページオフセット][_page offset_]であり、変換には関わらないためです。 [_page offset_]: @/edition-2/posts/08-paging-introduction/index.md#paging-on-x86-64 -Since each physical address can be accessed by adding the `physical_memory_offset`, the translation of the `physical_memory_offset` address itself should point to physical address `0`. However, the translation fails because the mapping uses huge pages for efficiency, which is not supported in our implementation yet. +それぞれの物理アドレスは`physical_memory_offset`を足すことでアクセスできるわけですから、`physical_memory_offset`自体を(仮想アドレスとして)変換すると物理アドレス`0`を指すはずです。しかし、効率よく対応付けを行うためにここではhuge pageが使われており、これはまだサポートしていないので変換には失敗しています。 -### Using `OffsetPageTable` +### `OffsetPageTable`を使う -Translating virtual to physical addresses is a common task in an OS kernel, therefore the `x86_64` crate provides an abstraction for it. The implementation already supports huge pages and several other page table functions apart from `translate_addr`, so we will use it in the following instead of adding huge page support to our own implementation. +仮想アドレスから物理アドレスへの変換はOSのカーネルがよく行うことですから、`x86_64`クレートはそのための抽象化を提供しています。この実装はすでにhuge pageや`translate_addr`以外の様々な関数もサポートしているので、以下ではhuge pageのサポートを自前で実装する代わりにこれを使うことにします。 -The base of the abstraction are two traits that define various page table mapping functions: +この抽象化の基礎となっているのは、様々なページテーブル対応付け関数を定義している2つのトレイトです。 -- The [`Mapper`] trait is generic over the page size and provides functions that operate on pages. Examples are [`translate_page`], which translates a given page to a frame of the same size, and [`map_to`], which creates a new mapping in the page table. -- The [`Translate`] trait provides functions that work with multiple page sizes such as [`translate_addr`] or the general [`translate`]. +- [`Mapper`]トレイトはページサイズを型引数とする汎用型 (ジェネリクス) です。例えば、[`translate_page`]は与えられたページを同じサイズのフレームに変換し、[`map_to`]はページテーブルに新しい対応付けを作成します。 +- [`Translate`] トレイトは[`translate_addr`]や一般の[`translate`]のような、さまざまなページサイズに対して動くような関数を提供します。 [`Mapper`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html [`translate_page`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html#tymethod.translate_page @@ -614,95 +624,97 @@ The base of the abstraction are two traits that define various page table mappin [`translate_addr`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Translate.html#method.translate_addr [`translate`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Translate.html#tymethod.translate -The traits only define the interface, they don't provide any implementation. The `x86_64` crate currently provides three types that implement the traits with different requirements. The [`OffsetPageTable`] type assumes that the complete physical memory is mapped to the virtual address space at some offset. The [`MappedPageTable`] is a bit more flexible: It only requires that each page table frame is mapped to the virtual address space at a calculable address. Finally, the [`RecursivePageTable`] type can be used to access page table frames through [recursive page tables](#recursive-page-tables). +これらのトレイトはインターフェイスを定義しているだけであり、その実装は何一つ提供していません。`x86_64`クレートは現在、このトレイトを実装する型を異なる要件に合わせて3つ用意しています。[`OffsetPageTable`]型は、全物理メモリがあるオフセットで仮想アドレスに対応していることを前提とします。[`MappedPageTable`]はもう少し融通が効き、それぞれのページテーブルフレームが計算可能などこかの仮想アドレスに対応していることだけを前提とします。最後に[`RecursivePageTable`]型は、ページテーブルのフレームに[再帰的ページテーブル](#zai-gui-de-peziteburu)を使ってアクセスするときに使えます。 [`OffsetPageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html [`MappedPageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MappedPageTable.html [`RecursivePageTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html -In our case, the bootloader maps the complete physical memory at a virtual address specified by the `physical_memory_offset` variable, so we can use the `OffsetPageTable` type. To initialize it, we create a new `init` function in our `memory` module: +私達の場合、ブートローダは全物理メモリを`physical_memory_offset`変数で指定された仮想アドレスで物理メモリに対応付けているので、`OffsetPageTable`型を使えます。これを初期化するために、`memory`モジュールに新しく`init`関数を作りましょう: ```rust use x86_64::structures::paging::OffsetPageTable; -/// Initialize a new OffsetPageTable. +/// 新しいOffsetPageTableを初期化する。 /// -/// This function is unsafe because the caller must guarantee that the -/// complete physical memory is mapped to virtual memory at the passed -/// `physical_memory_offset`. Also, this function must be only called once -/// to avoid aliasing `&mut` references (which is undefined behavior). +/// この関数はunsafeである:全物理メモリが、渡された +/// `physical_memory_offset`(だけずらしたうえ)で +/// 仮想メモリへと対応付けられていることを呼び出し元が +/// 保証しなければならない。また、`&mut`参照が複数の +/// 名称を持つこと (mutable aliasingといい、動作が未定義) +/// につながるため、この関数は一度しか呼び出してはならない。 pub unsafe fn init(physical_memory_offset: VirtAddr) -> OffsetPageTable<'static> { let level_4_table = active_level_4_table(physical_memory_offset); OffsetPageTable::new(level_4_table, physical_memory_offset) } -// make private +// これは非公開にする unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable {…} ``` -The function takes the `physical_memory_offset` as an argument and returns a new `OffsetPageTable` instance with a `'static` lifetime. This means that the instance stays valid for the complete runtime of our kernel. In the function body, we first call the `active_level_4_table` function to retrieve a mutable reference to the level 4 page table. We then invoke the [`OffsetPageTable::new`] function with this reference. As the second parameter, the `new` function expects the virtual address at which the mapping of the physical memory starts, which is given in the `physical_memory_offset` variable. +この関数は`physical_memory_offset`を引数としてとり、`'static`ライフタイムを持つ`OffsetPageTable`を作って返します。このライフタイムは、私達のカーネルが実行している間この実体 (インスタンス) はずっと有効であるという意味です。関数の中ではまず`active_level_4_table`関数を呼び出し、レベル4ページテーブルへの可変参照を取得します。次に[`OffsetPageTable::new`]関数をこの参照を使って呼び出します。この`new`関数の第二引数には、物理メモリの対応付けの始まる仮想アドレスが入ることになっています。つまり`physical_memory_offset`です。 [`OffsetPageTable::new`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html#method.new -The `active_level_4_table` function should be only called from the `init` function from now on because it can easily lead to aliased mutable references when called multiple times, which can cause undefined behavior. For this reason, we make the function private by removing the `pub` specifier. +可変参照が複数の名称を持つと未定義動作を起こす可能性があるので、以降`active_level_4_table`関数は`init`関数から一度呼び出されることを除いては呼び出されてはなりません。そのため、`pub`指定子を外してこの関数を非公開にしています。 -We now can use the `Translate::translate_addr` method instead of our own `memory::translate_addr` function. We only need to change a few lines in our `kernel_main`: +これで、自前の`memory::translate_addr`関数の代わりに`Translate::translate_addr`メソッドを使うことができます。これには`kernel_main`を数行だけ書き換えればよいです: ```rust // in src/main.rs fn kernel_main(boot_info: &'static BootInfo) -> ! { - // new: different imports + // インポートが追加・変更されている use blog_os::memory; use x86_64::{structures::paging::Translate, VirtAddr}; - […] // hello world and blog_os::init + […] // hello worldとblog_os::init let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset); - // new: initialize a mapper + // 追加:mapperを初期化 let mapper = unsafe { memory::init(phys_mem_offset) }; - let addresses = […]; // same as before + let addresses = […]; // 前と同じ for &address in &addresses { let virt = VirtAddr::new(address); - // new: use the `mapper.translate_addr` method + // 追加:`mapper.translate_addr`メソッドを使う let phys = mapper.translate_addr(virt); println!("{:?} -> {:?}", virt, phys); } - […] // test_main(), "it did not crash" printing, and hlt_loop() + […] // test_main(), "it did not crash" の出力, および hlt_loop() } ``` -We need to import the `Translate` trait in order to use the [`translate_addr`] method it provides. +[`translate_addr`]メソッドを使うために、それを提供している`Translate`トレイトをインポートする必要があります。 -When we run it now, we see the same translation results as before, with the difference that the huge page translation now also works: +これを実行すると、同じ変換結果が得られますが、今度はhuge pageの変換もうまく行っています: ![0xb8000 -> 0xb8000, 0x201008 -> 0x401008, 0x10000201a10 -> 0x279a10, 0x18000000000 -> 0x0](qemu-mapper-translate-addr.png) -As expected, the translations of `0xb8000` and the code and stack addresses stay the same as with our own translation function. Additionally, we now see that the virtual address `physical_memory_offset` is mapped to the physical address `0x0`. +想定通り、`0xb8000`やコード・スタックアドレスの変換結果は自前の変換関数と同じになっています。また、`physical_memory_offset`は物理アドレス`0x0`に対応付けられているのもわかります。 -By using the translation function of the `MappedPageTable` type we can spare ourselves the work of implementing huge page support. We also have access to other page functions such as `map_to`, which we will use in the next section. +`MappedPageTable`型の変換関数を使うことで、huge pageをサポートする手間が省けます。また`map_to`のような他のページング関数も利用でき、これは次のセクションで使います。 -At this point we no longer need our `memory::translate_addr` and `memory::translate_addr_inner` functions, so we can delete them. +この時点で、自作した`memory::translate_addr`関数や`memory::translate_addr_inner`関数はもう必要ではないので、削除して構いません。 -### Creating a new Mapping +### 新しい対応を作る -Until now we only looked at the page tables without modifying anything. Let's change that by creating a new mapping for a previously unmapped page. +これまでページテーブルを見てきましたが修正はしていませんでした。対応のなかったページに対応を作ることで、ページテーブルを修正してみましょう。 -We will use the [`map_to`] function of the [`Mapper`] trait for our implementation, so let's take a look at that function first. The documentation tells us that it takes four arguments: the page that we want to map, the frame that the page should be mapped to, a set of flags for the page table entry, and a `frame_allocator`. The frame allocator is needed because mapping the given page might require creating additional page tables, which need unused frames as backing storage. +これを実装するには[`Mapper`]トレイトの[`map_to`]関数を使うので、この関数について少し見てみましょう。ドキュメントによると四つ引数があります:対応に使うページ、ページを対応させるフレーム、ページテーブルエントリにつかうフラグの集合、そして`frame_allocator`です。フレームアロケータ (frame allocator) (フレームを割り当てる (アロケートする) 機能を持つ)が必要な理由は、与えられたページを対応付けるために追加でページテーブルを作成する必要があるかもしれず、これを格納するためには使われていないフレームが必要となるからです。 [`map_to`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.Mapper.html#tymethod.map_to [`Mapper`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.Mapper.html -#### A `create_example_mapping` Function +#### `create_example_mapping`関数 -The first step of our implementation is to create a new `create_example_mapping` function that maps a given virtual page to `0xb8000`, the physical frame of the VGA text buffer. We choose that frame because it allows us to easily test if the mapping was created correctly: We just need to write to the newly mapped page and see whether we see the write appear on the screen. +私達が実装していく最初のステップとして、`create_example_mapping`関数という、与えられた仮想ページを`0xb8000`すなわちVGAテキストバッファの物理フレームに対応付ける関数を作ってみましょう。このフレームを選んだ理由は、対応付けが正しくなされたかをテストするのが容易だからです:対応付けたページに書き込んで、それが画面に現れるか確認するだけでよいのですから。 -The `create_example_mapping` function looks like this: +`create_example_mapping`は以下のようになります: ```rust // in src/memory.rs @@ -712,7 +724,7 @@ use x86_64::{ structures::paging::{Page, PhysFrame, Mapper, Size4KiB, FrameAllocator} }; -/// Creates an example mapping for the given page to frame `0xb8000`. +/// 与えられたページをフレーム`0xb8000`に試しに対応付ける。 pub fn create_example_mapping( page: Page, mapper: &mut OffsetPageTable, @@ -724,27 +736,27 @@ pub fn create_example_mapping( let flags = Flags::PRESENT | Flags::WRITABLE; let map_to_result = unsafe { - // FIXME: this is not safe, we do it only for testing + // FIXME: unsafeであり、テストのためにのみ行う mapper.map_to(page, frame, flags, frame_allocator) }; map_to_result.expect("map_to failed").flush(); } ``` -In addition to the `page` that should be mapped, the function expects a mutable reference to an `OffsetPageTable` instance and a `frame_allocator`. The `frame_allocator` parameter uses the [`impl Trait`][impl-trait-arg] syntax to be [generic] over all types that implement the [`FrameAllocator`] trait. The trait is generic over the [`PageSize`] trait to work with both standard 4KiB pages and huge 2MiB/1GiB pages. We only want to create a 4KiB mapping, so we set the generic parameter to `Size4KiB`. +この関数は、対応付ける`page`に加え`OffsetPageTable`のインスタンスと`frame_allocator`への可変参照を引数に取ります。`frame_allocator`引数は[`impl Trait`][impl-trait-arg]構文により[`FrameAllocator`]トレイトを実装するあらゆる型の[汎用型][generic]になっています。`FrameAllocator`トレイトは[`PageSize`]トレイトを実装するなら(引数のサイズが)4KiBでも2MiBや1GiBのhuge pageでも大丈夫な汎用 (ジェネリック) トレイトです。私達は4KiBの対応付けのみを作りたいので、ジェネリック引数は`Size4KiB`にしています。 [impl-trait-arg]: https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters [generic]: https://doc.rust-lang.org/book/ch10-00-generics.html [`FrameAllocator`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.FrameAllocator.html [`PageSize`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page/trait.PageSize.html -The [`map_to`] method is unsafe because the caller must ensure that the frame is not already in use. The reason for this is that mapping the same frame twice could result in undefined behavior, for example when two different `&mut` references point to the same physical memory location. In our case, we reuse the VGA text buffer frame, which is already mapped, so we break the required condition. However, the `create_example_mapping` function is only a temporary testing function and will be removed after this post, so it is ok. To remind us of the unsafety, we put a `FIXME` comment on the line. +[`map_to`]メソッドは、呼び出し元がフレームはまだ使われていないことを保証しないといけないので、unsafeです。なぜなら、同じフレームを二度対応付けると(例えば2つの異なる`&mut`参照が物理メモリの同じ場所を指すことで)未定義動作を起こす可能性があるからです。今回、VGAテキストバッファのフレームという、すでに対応付けられているフレームを再度使っているので、この要件を破ってしまっています。しかしながら、`create_example_mapping`関数は一時的なテスト関数でありこの記事のあとには取り除かれるので、大丈夫です。この危険性 (unsafety) のことを忘れないようにするために、その行に`FIXME` (要修正) コメントをつけておきます。 -In addition to the `page` and the `unused_frame`, the `map_to` method takes a set of flags for the mapping and a reference to the `frame_allocator`, which will be explained in a moment. For the flags, we set the `PRESENT` flag because it is required for all valid entries and the `WRITABLE` flag to make the mapped page writable. For a list of all possible flags, see the [_Page Table Format_] section of the previous post. +`map_to`関数が`page`と`unused_frame`に加えてフラグの集合と`frame_allocator`への参照を取りますが、これについてはすぐに説明します。フラグについては、`PRESENT`フラグという有効なエントリ全てに必須のフラグと、`WRITABLE`フラグという対応するページを書き込み可能にするフラグをセットしています。フラグの一覧については、前記事の[ページテーブルの形式][_Page Table Format_]を参照してください。 [_Page Table Format_]: @/edition-2/posts/08-paging-introduction/index.md#page-table-format -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]`][must_use] attribute to emit a warning when we accidentally forget to use it. +[`map_to`]関数は失敗しうるので、[`Result`]を返します。これは失敗しても構わない単なるテストコードなので、エラーが起きたときは[`expect`]を使ってパニックしてしまうことにします。この関数は成功したとき[`MapperFlush`]型を返します。この型の[`flush`]メソッドを使うと、新しく対応させたページをトランスレーション・ルックアサイド・バッファ (TLB) から簡単にflushすることができます。この型は`Result`と同じく[`#[must_use]`][must_use]属性を使っており、使用し忘れると警告を出します。 [`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html [`expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect @@ -752,16 +764,16 @@ The [`map_to`] function can fail, so it returns a [`Result`]. Since this is just [`flush`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush [must_use]: https://doc.rust-lang.org/std/result/#results-must-be-used -#### A dummy `FrameAllocator` +#### ダミーの`FrameAllocator` -To be able to call `create_example_mapping` we need to create a type that implements the `FrameAllocator` trait first. As noted above, the trait is responsible for allocating frames for new page table if they are needed by `map_to`. +`create_example_mapping`関数を呼べるようにするためには、まず`FrameAllocator`トレイトを実装する型を作成する必要があります。上で述べたように、このトレイトは新しいページのためのフレームを`map_to`が必要としたときに割り当てる役割を持っています。 -Let's start with the simple case and assume that we don't need to create new page tables. For this case, a frame allocator that always returns `None` suffices. We create such an `EmptyFrameAllocator` for testing our mapping function: +単純なケースを考えましょう:新しいページテーブルを作る必要がないと仮定してしまいます。この場合、常に`None`を返すフレームアロケータで十分です。私達の対応付け関数をテストするために、そのような`EmptyFrameAllocator`を作ります。 ```rust // in src/memory.rs -/// A FrameAllocator that always returns `None`. +/// つねに`None`を返すFrameAllocator pub struct EmptyFrameAllocator; unsafe impl FrameAllocator for EmptyFrameAllocator { @@ -771,69 +783,70 @@ unsafe impl FrameAllocator for EmptyFrameAllocator { } ``` -Implementing the `FrameAllocator` is unsafe because the implementer must guarantee that the allocator yields only unused frames. Otherwise undefined behavior might occur, for example when two virtual pages are mapped to the same physical frame. Our `EmptyFrameAllocator` only returns `None`, so this isn't a problem in this case. +`FrameAllocator`を実装するのはunsafeです。なぜなら、実装する人は、作ったアロケータが未使用のフレームのみ取得することを保証しなければならないからです。さもなくば、例えば二つの仮想ページが同じ物理フレームに対応付けられたときに未定義動作が起こるかもしれません。この`EmptyFrameAllocator`は`None`しか返さないので、これは問題ではありません。 -#### Choosing a Virtual Page +#### 仮想ページを選ぶ -We now have a simple frame allocator that we can pass to our `create_example_mapping` function. However, the allocator always returns `None`, so this will only work if no additional page table frames are needed for creating the mapping. To understand when additional page table frames are needed and when not, let's consider an example: +`create_example_mapping`関数に渡すための単純なフレームアロケータを手に入れました。しかし、このアロケータは常に`None`を返すので、対応を作る際に追加のページテーブルフレームが必要でなかったときにのみうまく行きます。いつ追加のページテーブルフレームが必要でありいつそうでないのかを知るために、例をとって考えてみましょう: ![A virtual and a physical address space with a single mapped page and the page tables of all four levels](required-page-frames-example.svg) -The graphic shows the virtual address space on the left, the physical address space on the right, and the page tables in between. The page tables are stored in physical memory frames, indicated by the dashed lines. The virtual address space contains a single mapped page at address `0x803fe00000`, marked in blue. To translate this page to its frame, the CPU walks the 4-level page table until it reaches the frame at address 36 KiB. +この図の左は仮想アドレス空間を、右は物理アドレス空間を、真ん中はページテーブルを示します。このページテーブルが格納されている物理フレームが破線で示されています。仮想アドレス空間は一つの対応付けられたページをアドレス`0x803fe00000`に持っており、これは青色で示されています。このページをフレームに変換するために、CPUは4層のページテーブルを辿り、アドレス36KiBのフレームに到達します。 -Additionally, the graphic shows the physical frame of the VGA text buffer in red. Our goal is to map a previously unmapped virtual page to this frame using our `create_example_mapping` function. Since our `EmptyFrameAllocator` always returns `None`, we want to create the mapping so that no additional frames are needed from the allocator. This depends on the virtual page that we select for the mapping. +また、この図はVGAテキストバッファの物理フレームを赤色で示しています。私達の目的は、`create_example_mapping`関数を使ってまだ対応付けられていない仮想ページをこのフレームに対応付けることです。私達の`EmptyFrameAllocator`は常に`None`を返すので、アロケータからフレームを追加する必要がないように対応付けを作りたいです。これができるかは、私達が対応付けにどの仮想ページを使うかに依存します。 -The graphic shows two candidate pages in the virtual address space, both marked in yellow. One page is at address `0x803fdfd000`, which is 3 pages before the mapped page (in blue). While the level 4 and level 3 page table indices are the same as for the blue page, the level 2 and level 1 indices are different (see the [previous post][page-table-indices]). The different index into the level 2 table means that a different level 1 table is used for this page. Since this level 1 table does not exist yet, we would need to create it if we chose that page for our example mapping, which would require an additional unused physical frame. In contrast, the second candidate page at address `0x803fe02000` does not have this problem because it uses the same level 1 page table than the blue page. Thus, all required page tables already exist. + +この図の仮想アドレス空間には、2つの候補となるページを黄色で示しています。ページのうち一つはアドレス`0x803fe00000`で、これは(青で示された)対応付けられたページの3つ前です。レベル4と3のテーブルのインデクスは青いページと同じですが、レベル2と1のインデクスは違います([前の記事][page-table-indices]を参照)。レベル2テーブルのインデクスが違うということは、異なるレベル1テーブルが使われることを意味します。そんなレベル1テーブルは存在しないので、もしこちらを使っていたら、使われていない物理フレームを追加(でアロケート)する必要が出てきます。対して、2つ目のアドレス`0x803fe02000`にある候補のページは、青のページと同じレベル1ページテーブルを使うのでこの問題は発生しません。よって、必要となるすべてのページテーブルはすでに存在しています。 [page-table-indices]: @/edition-2/posts/08-paging-introduction/index.md#paging-on-x86-64 -In summary, 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. +まとめると、新しい対応を作るときの難易度は、対応付けようとしている仮想ページに依存するということです。作ろうとしているページのレベル1ページテーブルがすでに存在すると最も簡単で、エントリを一つ書き込むだけです。ページがレベル3のテーブルすら存在しない領域にある場合が最も難しく、その場合まずレベル3,2,1のページテーブルを新しく作る必要があります。 -For calling our `create_example_mapping` function with the `EmptyFrameAllocator`, we need to choose a page for that all page tables already exist. To find such a page, we can utilize the fact that the bootloader loads itself in the first megabyte of the virtual address space. This means that a valid level 1 table exists for all pages this region. Thus, we can choose any unused page in this memory region for our example mapping, such as the page at address `0`. Normally, this page should stay unused to guarantee that dereferencing a null pointer causes a page fault, so we know that the bootloader leaves it unmapped. +`EmptyFrameAllocator`を使って`create_example_mapping`を呼び出すためには、すべての(階層の)ページテーブルがすでに存在しているページを選ぶ必要があります。そんなページを探すにあたっては、ブートローダが自分自身を仮想アドレス空間の最初の1メガバイトに読み込んでいるということを利用できます。つまり、この領域のすべてのページについて、レベル1テーブルがきちんと存在しているということです。したがって、試しに対応を作るときに、このメモリ領域のいずれかの未使用ページ、例えばアドレス`0`を使えばよいです。普通このページは、ヌルポインタの参照外しがページフォルトを引き起こすことを保証するために使用しないので、ブートローダもここを対応させてはいないはずです。 -#### Creating the Mapping +#### 対応を作る -We now have all the required parameters for calling our `create_example_mapping` function, so let's modify our `kernel_main` function to map the page at virtual address `0`. Since we map the page to the frame of the VGA text buffer, we should be able to write to the screen through it afterwards. The implementation looks like this: +というわけで、`create_example_mapping`関数を呼び出すために必要なすべての引数を手に入れたので、仮想アドレス`0`を対応付けるよう`kernel_main`関数を変更していきましょう。このページはVGAテキストバッファのフレームに対応付けているので、以後、画面に書き込むことができるはずです。実装は以下のようになります: ```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 + use x86_64::{structures::paging::Page, VirtAddr}; // 新しいインポート - […] // hello world and blog_os::init + […] // hello worldと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 + // 新しい対応付けを使って、文字列`New!`を画面に書き出す 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() + […] // test_main(), "it did not crash" printing, および hlt_loop() } ``` -We first create the mapping for the page at address `0` by calling our `create_example_mapping` function with a mutable reference to the `mapper` and the `frame_allocator` instances. This maps the page to the VGA text buffer frame, so we should see any write to it on the screen. +まず、`mapper`と`frame_allocator`インスタンスの可変参照を渡して`create_example_mapping`を呼ぶことで、アドレス`0`のページに対応を作っています。これはVGAテキストバッファのフレームに対応付けているので、これに書き込んだものは何であれ画面に出てくるはずです。 -Then we convert the page to a raw pointer and write a value to offset `400`. We don't write to the start of the page because the top line of the VGA buffer is directly shifted off the screen by the next `println`. We write the value `0x_f021_f077_f065_f04e`, which represents the string _"New!"_ on white background. As we learned [in the _“VGA Text Mode”_ post], writes to the VGA buffer should be volatile, so we use the [`write_volatile`] method. +次にページを生ポインタに変更して、オフセット`400`に値を書き込みます。このページの最初に書き込むとVGAバッファの一番上の行になり、次のprintlnで即座に画面外に流れていってしまうのでそれはしません。値`0x_f021_f077_f065_f04e`は、白背景の"New!"という文字列を表します。[VGAテキストモードの記事][in the _“VGA Text Mode”_ post]で学んだように、VGAバッファへの書き込みはvolatileでなければならないので、[`write_volatile`]メソッドを使っています。 [in the _“VGA Text Mode”_ post]: @/edition-2/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で実行すると、以下の出力を得ます: ![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 page `0`, which means that we successfully created a new mapping in the page tables. +画面の"New!"はページ`0`への書き込みによるものなので、ページテーブルへの新しい対応付けの作成が成功したということを意味します。 -Creating that mapping only worked because the level 1 table responsible for the page at address `0` already exists. 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 `0`: +この対応付けが成功したのは、アドレス`0`を管轄するレベル1テーブルがすでに存在していたからに過ぎません。レベル1テーブルがまだ存在しないページを対応付けようとすると、`map_to`関数は新しいページテーブルを作るために`EmptyFrameAllocator`からフレームを割り当てようとしてエラーになります。`0`の代わりに`0xdeadbeaf000`を対応付けようとするとそれが発生するのが見られます。 ```rust // in src/main.rs @@ -845,35 +858,37 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { } ``` -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? +レベル1テーブルのまだ存在しないページを対応付けるためには、ちゃんとした`FrameAllocator`を作らないといけません。しかし、どのフレームが未使用で、どのフレームが利用可能かはどうすればわかるのでしょう? -### Allocating Frames +### フレームを割り当てる -In order to create new page tables, we need to create a proper frame allocator. For that we use the `memory_map` that is passed by the bootloader as part of the `BootInfo` struct: +新しいページテーブルを作成するためには、ちゃんとしたフレームアロケータを作る必要があります。このためには、ブートローダによって渡される`BootInfo`構造体の一部である`memory_map`を使います: ```rust // in src/memory.rs use bootloader::bootinfo::MemoryMap; -/// A FrameAllocator that returns usable frames from the bootloader's memory map. +/// ブートローダのメモリマップから、使用可能な +/// フレームを返すFrameAllocator pub struct BootInfoFrameAllocator { memory_map: &'static MemoryMap, next: usize, } impl BootInfoFrameAllocator { - /// Create a FrameAllocator from the passed memory map. + /// 渡されたメモリマップからFrameAllocatorを作る。 /// - /// This function is unsafe because the caller must guarantee that the passed - /// memory map is valid. The main requirement is that all frames that are marked - /// as `USABLE` in it are really unused. + /// この関数はunsafeである:呼び出し元は渡された + /// メモリマップが有効であることを保証しなければ + /// ならない。特に、`USABLE`なフレームは実際に + /// 未使用でなくてはならない。 pub unsafe fn init(memory_map: &'static MemoryMap) -> Self { BootInfoFrameAllocator { memory_map, @@ -883,15 +898,15 @@ impl BootInfoFrameAllocator { } ``` -The struct has two fields: A `'static` reference to the memory map passed by the bootloader and a `next` field that keeps track of number of the next frame that the allocator should return. +この構造体は2つのフィールドを持ちます。ブートローダによって渡されたメモリマップへの`'static`な参照と、アロケータが次に返すべきフレームの番号を覚えておくための`next`フィールドです。 -As we explained in the [_Boot Information_](#boot-information) section, the memory map is provided by the BIOS/UEFI firmware. It can only be queried very early in the boot process, so the bootloader already calls the respective functions for us. The memory map consists of a list of [`MemoryRegion`] structs, which contain the start address, the length, and the type (e.g. unused, reserved, etc.) of each memory region. +[_Boot Information_](#boot-information)節で説明したように、このメモリマップはBIOS/UEFIファームウェアから提供されます。これはブートプロセスのごく初期にのみ取得できるので、ブートローダがそのための関数を呼ぶようになっています。メモリマップは`MemoryRegion`構造体のリストからなり、この構造体はそれぞれのメモリ領域の開始アドレス、長さ、型(未使用、予約済み、など)を格納しています。 -The `init` function initializes a `BootInfoFrameAllocator` with a given memory map. The `next` field is initialized with `0` and will be increased for every frame allocation to avoid returning the same frame twice. Since we don't know if the usable frames of the memory map were already used somewhere else, our `init` function must be `unsafe` to require additional guarantees from the caller. +`init`関数は`BootInfoFrameAllocator`を与えられたメモリマップで初期化します。`next`フィールドは`0`で初期化し、フレームを割当てるたびに値を増やすことで同じフレームを二度返すことを防ぎます。メモリマップのusable (使用可能) なフレームが他の誰かに使われたりしていないかは知ることができないので、この`init`関数はそれを呼び出し元に追加で保証させるために`unsafe`でないといけません。 -#### A `usable_frames` Method +#### `usable_frames`メソッド -Before we implement the `FrameAllocator` trait, we add an auxiliary method that converts the memory map into an iterator of usable frames: +`FrameAllocator`トレイトを実装していく前に、渡されたメモリマップをusableなフレームのイテレータに変換する補助メソッドを追加します: ```rust // in src/memory.rs @@ -899,30 +914,30 @@ Before we implement the `FrameAllocator` trait, we add an auxiliary method that use bootloader::bootinfo::MemoryRegionType; impl BootInfoFrameAllocator { - /// Returns an iterator over the usable frames specified in the memory map. + /// メモリマップによって指定されたusableなフレームのイテレータを返す。 fn usable_frames(&self) -> impl Iterator { - // get usable regions from memory map + // メモリマップからusableな領域を得る let regions = self.memory_map.iter(); let usable_regions = regions .filter(|r| r.region_type == MemoryRegionType::Usable); - // map each region to its address range + // それぞれの領域をアドレス範囲にmapで変換する let addr_ranges = usable_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.step_by(4096)); - // create `PhysFrame` types from the start addresses + // 開始アドレスから`PhysFrame`型を作る frame_addresses.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr))) } } ``` -This function uses iterator combinator methods to transform the initial `MemoryMap` into an iterator of usable physical frames: +この関数はイテレータのコンビネータメソッドを使って、最初に与えられる`MemoryMap`を使用可能な物理フレームのイテレータに変換します: -- 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. -- Afterwards, we use the [`map`] combinator and Rust's [range syntax] to transform our iterator of memory regions to an iterator of address ranges. -- Next, we use [`flat_map`] to transform the address ranges into an iterator of frame start addresses, choosing 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>`. -- Finally, we convert the start addresses to `PhysFrame` types to construct the an `Iterator`. +- まず`iter`メソッドを使ってメモリマップを[`MemoryRegion`]のイテレータに変える。 +- 次に[`filter`]メソッドを使って、予約済みなどの理由で使用不可能な領域を飛ばすようにする。ブートローダは作った対応付けに使ったメモリマップはきちんと更新するので、私達のカーネル(コード、データ、スタック)に使われているフレームやブート情報を格納するのに使われているフレームはすでに`InUse` (使用中) などでマークされています。そのため`Usable`なフレームは他の場所では使われていないはずとわかります。 +- つぎに、[`map`]コンビネータとRustの[range構文][range syntax]を使って、メモリ領域のイテレータからアドレス範囲のイテレータへと変換する。 +- つぎに、アドレス範囲から[`step_by`]で4096個ごとにアドレスを選び、[`flat_map`]を使うことでフレームの最初のアドレスのイテレータを得る。4096バイト(=4KiB)はページのサイズに等しいので、それぞれのフレームの開始地点のアドレスが得られます。ブートローダのページは使用可能なメモリ領域をすべてアラインするので、ここで改めてアラインや丸めを行う必要はありません。`map`ではなく[`flat_map`]を使うことで、`Iterator>`ではなく`Iterator`を得ています。 +- 最後に、開始アドレスの型を`PhysFrame`に変更することで`Iterator`を得ている。 [`MemoryRegion`]: https://docs.rs/bootloader/0.6.4/bootloader/bootinfo/struct.MemoryRegion.html [`filter`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.filter @@ -931,14 +946,14 @@ This function uses iterator combinator methods to transform the initial `MemoryM [`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 -The return type of the function uses the [`impl Trait`] feature. This way, we can specify that we return some type that implements the [`Iterator`] trait with item type `PhysFrame`, but don't need to name the concrete return type. This is important here because we _can't_ name the concrete type since it depends on unnamable closure types. +この関数の戻り型は[`impl Trait`]機能を用いています。こうすると、`PhysFrame`をitemの型として持つような[`Iterator`]トレイトを実装する何らかの型を返すのだと指定できます。これは重要です――なぜなら、戻り値の型は名前のつけられないクロージャ型に依存し、具体的な名前をつけるのが**不可能だ**からです。 [`impl Trait`]: https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits [`Iterator`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html -#### Implementing the `FrameAllocator` Trait +#### `FrameAllocator`トレイトを実装する -Now we can implement the `FrameAllocator` trait: +これで`FrameAllocator`トレイトを実装できます: ```rust // in src/memory.rs @@ -952,18 +967,18 @@ unsafe impl FrameAllocator for BootInfoFrameAllocator { } ``` -We first use the `usable_frames` method to get an iterator of usable frames from the memory map. Then, we use the [`Iterator::nth`] function to get the frame with index `self.next` (thereby skipping `(self.next - 1)` frames). Before returning that frame, we increase `self.next` by one so that we return the following frame on the next call. +まず`usable_frames`メソッドを使ってメモリマップからusableなフレームのイテレータを得ます。つぎに、[`Iterator::nth`]関数で`self.next`番目の(つまり`(self.next - 1)`だけ飛ばして)フレームを得ます。このフレームを返してリターンする前に、`self.next`を1だけ増やして次の呼び出しで1つ後のフレームが得られるようにします。 [`Iterator::nth`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.nth -This implementation is not quite optimal since it recreates the `usable_frame` allocator on every allocation. It would be better to directly store the iterator as a struct field instead. Then we wouldn't need the `nth` method and could just call [`next`] on every allocation. The problem with this approach is that it's not possible to store an `impl Trait` type in a struct field currently. It might work someday when [_named existential types_] are fully implemented. +この実装は割当てを行うごとに`usable_frames`アロケータを作り直しているので、あまり最適ではありません。イテレータを構造体のフィールドとして直接格納するほうが良いでしょう。すると`nth`メソッドを使う必要はなくなり、割り当てのたびに[`next`]を使えばいいだけです。このアプローチの問題は、今の所構造体のフィールドに`impl Trait`型(の変数)を格納することができないことです。いつの日か、[named existential type][_named existential types_]が完全に実装されたときにはこれが可能になるかもしれません。 [`next`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#tymethod.next [_named existential types_]: https://github.com/rust-lang/rfcs/pull/2071 -#### Using the `BootInfoFrameAllocator` +#### `BootInfoFrameAllocator`を使う -We can now modify our `kernel_main` function to pass a `BootInfoFrameAllocator` instance instead of an `EmptyFrameAllocator`: +`kernel_main`関数を修正して`EmptyFrameAllocator`のインスタンスの代わりに`BootInfoFrameAllocator`を渡しましょう: ```rust // in src/main.rs @@ -978,28 +993,28 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { } ``` -With the boot info frame allocator, 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: +ブート情報を使うフレームアロケータのおかげで対応付けは成功し、白背景に黒文字の"New!"が再び画面に現れました。舞台裏では、`map_to`メソッドが不足しているページテーブルを以下のやり方で作っています: -- Allocate an unused frame from the passed `frame_allocator`. -- Zero the frame to create a new, empty page table. -- Map the entry of the higher level table to that frame. -- Continue with the next table level. +- 渡された`frame_allocator`を使って未使用のフレームを割り当てる。 +- フレームをゼロで埋め、新しい空のページテーブルを作る。 +- 上位のテーブルのエントリをそのフレームに対応付ける。 +- 次の層で同じことを続ける。 -While our `create_example_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. +`create_example_mapping`関数はただのお試しコードにすぎませんが、今や私達は任意のページに対応付けを作れるようになりました。これは、今後の記事で行うメモリ割り当てやマルチスレッディングにおいて不可欠です。 -At this point, we should delete the `create_example_mapping` function again to avoid accidentally invoking undefined behavior, as explained [above](#a-create-example-mapping-function). +[上](#create-example-mappingguan-shu)で説明したような未定義動作を誤って引き起こしてしまうことのないよう、この時点で`create_example_mapping`関数を再び取り除いておきましょう。 -## Summary +## まとめ -In this post we learned about different techniques to access the physical frames of page tables, including identity mapping, mapping of the complete physical memory, temporary mapping, and recursive page tables. We chose to map the complete physical memory since it's simple, portable, and powerful. +この記事ではページテーブルのある物理フレームにアクセスするための様々なテクニックを学びました。恒等対応、物理メモリ全体の対応付け、一時的な対応、再帰的ページテーブルなどです。このうち、シンプルでポータブル(アーキテクチャ非依存という意味)で強力な、物理メモリ全体の対応付けを選びました。 -We can't map the physical memory from our kernel without page table access, so we needed support from the bootloader. The `bootloader` crate supports creating the required mapping through optional cargo features. It passes the required information to our kernel in the form of a `&BootInfo` argument to our entry point function. +ページテーブルにアクセスできなければ物理メモリを対応付けられないので、ブートローダの補助が必要でした。`bootloader`クレートはcargoのfeaturesというオプションを通じて、必要となる対応付けの作成をサポートしています。さらに、必要となる情報をエントリポイント関数の`&BootInfo`引数という形で私達のカーネルに渡してくれます。 -For our implementation, we first manually traversed the page tables to implement a translation function, and then used the `MappedPageTable` type of the `x86_64` crate. We also learned how to create new mappings in the page table and how to create the necessary `FrameAllocator` on top of the memory map passed by the bootloader. +実装について。最初は手作業でページテーブルを辿ることで変換関数を実装し、そのあとで`x86_64`クレートの`MappedPageTable`型を使いました。また、ページテーブルに新しい対応を作る方法や、そのために必要な`FrameAllocator`をブートローダに渡されたメモリマップをラップすることで作る方法を学びました。 -## 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]や各種の[コレクション型][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