mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
Apply suggestions from code review and Add garasubo as a co-translator
Co-authored-by: garasubo <garasubo@gmail.com>
This commit is contained in:
@@ -7,7 +7,7 @@ date = 2019-03-14
|
||||
[extra]
|
||||
chapter = "Memory Management"
|
||||
translation_based_on_commit = "27ab4518acbb132e327ed4f4f0508393e9d4d684"
|
||||
translators = ["woodyZootopia"]
|
||||
translators = ["woodyZootopia", "garasubo"]
|
||||
+++
|
||||
|
||||
この記事では私達のカーネルをページングに対応させる方法についてお伝えします。まずページテーブルの物理フレームにカーネルがアクセスできるようにする様々な方法を示し、それらの利点と欠点について議論します。次にアドレス変換関数を、ついで新しい対応付けを作るための関数を実装します。
|
||||
@@ -24,7 +24,7 @@ translators = ["woodyZootopia"]
|
||||
|
||||
## 導入
|
||||
|
||||
[1つ前の記事][previous post]ではページングの概念を説明しました。セグメンテーションと比較することによってページングのメリットを示し、ページングとページテーブルの仕組みを説明し、そして`x86_64`における4層ページテーブルの設計を導入しました。ブートローダはすでにページテーブルの階層構造を設定してしまっているので、私達のカーネルは既に仮想アドレス上で動いているということを学びました。これにより、不正なメモリアクセスは、任意の物理メモリを書き換えてしまう代わりにページフォルト例外を発生させるので、安全性が向上しています。
|
||||
[1つ前の記事][previous post]ではページングの概念を説明しました。セグメンテーションと比較することによってページングのメリットを示し、ページングとページテーブルの仕組みを説明し、そして`x86_64`における4層ページテーブルの設計を導入しました。ブートローダはすでにページテーブルの階層構造を設定してしまっているので、私達のカーネルは既に仮想アドレス上で動いているということを学びました。これにより、不正なメモリアクセスは、任意の物理メモリを書き換えてしまうのではなくページフォルト例外を発生させるので、安全性が向上しています。
|
||||
|
||||
[previous post]: @/edition-2/posts/08-paging-introduction/index.ja.md
|
||||
|
||||
@@ -59,7 +59,7 @@ translators = ["woodyZootopia"]
|
||||
[memory-mapping a file]: https://en.wikipedia.org/wiki/Memory-mapped_file
|
||||
[segmentation]: @/edition-2/posts/08-paging-introduction/index.ja.md#duan-pian-hua-fragmentation
|
||||
|
||||
同様に、新しいページテーブルを作ることもずっと難しくなります。なぜなら、対応するページがまだ使われていない物理フレームを見つけないといけないからです。例えば、メモリ<ruby>マップト<rp> (</rp><rt>に対応づけられた</rt><rp>) </rp></ruby>ファイルのために`1008KiB`から1000KiBにわたって仮想メモリを占有したとしましょう。すると、すると、物理アドレス`1000KiB`から`2008KiB`までのフレームは、もう恒等対応を作ることができないので使用することができません。
|
||||
同様に、新しいページテーブルを作ることもずっと難しくなります。なぜなら、対応するページがまだ使われていない物理フレームを見つけないといけないからです。例えば、メモリ<ruby>マップト<rp> (</rp><rt>に対応づけられた</rt><rp>) </rp></ruby>ファイルのために`1008KiB`から1000KiBにわたって仮想メモリを占有したとしましょう。すると、物理アドレス`1000KiB`から`2008KiB`までのフレームは、もう恒等対応を作ることができないので使用することができません。
|
||||
|
||||
### 固定オフセットの対応づけ
|
||||
|
||||
@@ -121,7 +121,7 @@ translators = ["woodyZootopia"]
|
||||
|
||||
[example at the beginning of this post]: #peziteburuniakusesusuru
|
||||
|
||||
CPUにこのエントリを辿らせるようにすると、レベル3テーブルではなく、そのレベル4テーブルに再び到達します。これは再帰関数(自らを呼び出す関数)に似ているので、<ruby>**再帰的ページテーブル**<rp> (</rp><rt>recursive page table</rt><rp>) </rp></ruby>と呼ばれます。CPUはレベル4テーブルのすべてのエントリはレベル3テーブルを指していると思っているので、CPUはいまレベル4テーブルをレベル3テーブルとして扱っているということに注目してください。これがうまく行くのは、x86_64においてはすべてのレベルのテーブルが全く同じレイアウトを持っているためです。
|
||||
CPUにこのエントリを辿らせるようにすると、レベル3テーブルではなく、そのレベル4テーブルに再び到達します。これは再帰関数(自らを呼び出す関数)に似ているので、**<ruby>再帰的<rp> (</rp><rt>recursive</rt><rp>) </rp></ruby>ページテーブル**と呼ばれます。CPUはレベル4テーブルのすべてのエントリはレベル3テーブルを指していると思っているので、CPUはいまレベル4テーブルをレベル3テーブルとして扱っているということに注目してください。これがうまく行くのは、x86_64においてはすべてのレベルのテーブルが全く同じレイアウトを持っているためです。
|
||||
|
||||
実際に変換を始める前に、この再帰エントリを1回以上たどることで、CPUのたどる階層の数を短くできます。例えば、一度再帰エントリを辿ったあとでレベル3テーブルに進むと、CPUはレベル3テーブルをレベル2テーブルだと思い込みます。同様に、レベル2テーブルをレベル1テーブルだと、レベル1テーブルを対応付けられた(物理)フレームだと思います。CPUがこれを物理フレームだと思っているということは、レベル1ページテーブルを読み書きできるということを意味します。下の図はこの5回の変換ステップを示しています:
|
||||
|
||||
@@ -217,7 +217,7 @@ let level_1_table_addr =
|
||||
sign | (r << 39) | (l4_idx << 30) | (l3_idx << 21) | (l2_idx << 12);
|
||||
```
|
||||
|
||||
上のコードは、レベル4エントリの最後(インデクス`0o777`すなわち511)が再帰対応していると仮定しています。この対応はまだ行っていないので、この仮定は正しくありません。以下でブートローダに再帰対応付けを設定させる方法を説明します。
|
||||
上のコードは、レベル4エントリの最後(インデクス`0o777`すなわち511)が再帰対応していると仮定しています。この仮定は正しくないので,このコードは動作しません。ブートローダに再帰対応付けを設定させる方法については後述します。
|
||||
|
||||
ビット演算を自前で行う代わりに、`x86_64`クレートの[`RecursivePageTable`]型を使うこともできます。これは様々なページ操作の安全な抽象化を提供します。例えば、以下のコードは仮想アドレスを対応付けられた物理アドレスに変換する方法を示しています。
|
||||
|
||||
@@ -248,7 +248,7 @@ let frame = recursive_page_table.translate_page(page);
|
||||
frame.map(|frame| frame.start_address() + u64::from(addr.page_offset()))
|
||||
```
|
||||
|
||||
繰り返しになりますが、このコード(が正しく実行される)には正しい再帰対応がなされていることが必要となります。この対応付けがあるなら、空欄になっている`level_4_table_addr`を最初のコード例と同じ値にすればよいです。
|
||||
繰り返しになりますが、このコード(が正しく実行される)には正しい再帰対応がなされていることが必要となります。この対応付けがあるのなら、空欄になっている`level_4_table_addr`は最初のコード例を使って計算すればよいです。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -275,7 +275,7 @@ frame.map(|frame| frame.start_address() + u64::from(addr.page_offset()))
|
||||
- `map_physical_memory` featureを使うと、全物理メモリを仮想アドレス空間のどこかに対応付けます。そのため、カーネルはすべての物理メモリにアクセスでき、[上で述べた方法に従って物理メモリ全体を対応付ける](#wu-li-memoriquan-ti-wodui-ying-fu-keru)ことができます。
|
||||
- `recursive_page_table` featureでは、ブートローダはレベル4ページテーブルのエントリを再帰的に対応付けます。これによりカーネルは[再帰的ページテーブル](#zai-gui-de-peziteburu)で述べた方法に従ってページテーブルにアクセスすることができます。
|
||||
|
||||
私達のカーネルには、シンプルでプラットフォーム非依存でより強力である(ページテーブルのフレームでないメモリにもアクセスできるので)1つ目の方法を採ることにします。必要なブートローダの<ruby>機能<rp> (</rp><rt>feature</rt><rp>) </rp></ruby>を有効化するために、`map_physical_memory` featureを`bootloader`のdependencyに追加します。
|
||||
私達のカーネルには、シンプルでプラットフォーム非依存かつ(ページテーブルのフレームでないメモリにもアクセスできるので)より強力である1つ目の方法を採ることにします。必要なブートローダの<ruby>機能<rp> (</rp><rt>feature</rt><rp>) </rp></ruby>を有効化するために、`map_physical_memory` featureを`bootloader`のdependencyに追加します。
|
||||
|
||||
|
||||
```toml
|
||||
@@ -313,7 +313,7 @@ pub extern "C" fn _start(boot_info: &'static BootInfo) -> ! { // 新しい引数
|
||||
|
||||
### `entry_point`マクロ
|
||||
|
||||
私達の`_start`関数はブートローダから外部呼び出しされるので、私達の関数のシグネチャに対する検査は行われません。これにより、この関数はコンパイルエラーなしにあらゆる引数を取ることができるので、いざ実行時にエラーになったり未定義動作を起こしてしまいます。
|
||||
私達の`_start`関数はブートローダから外部呼び出しされるので、私達の関数のシグネチャに対する検査は行われません。これにより、この関数はコンパイルエラーなしにあらゆる引数を取ることができるので、いざ実行時にエラーになったり未定義動作を起こしたりしてしまいます。
|
||||
|
||||
私達のエントリポイント関数が常にブートローダの期待する正しいシグネチャを持っていることを保証するために、`bootloader`クレートは[`entry_point`]マクロによって、Rustの関数を型チェックしたうえでエントリポイントとして定義する方法を提供します。私達のエントリポイント関数をこのマクロを使って書き直してみましょう:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user