mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
Refined translation
This commit is contained in:
@@ -44,12 +44,12 @@ x86には20種類のCPU例外があります。中でも重要なものは:
|
|||||||
[exceptions]: https://wiki.osdev.org/Exceptions
|
[exceptions]: https://wiki.osdev.org/Exceptions
|
||||||
|
|
||||||
### 割り込み記述子表
|
### 割り込み記述子表
|
||||||
例外を捕捉し処理するためには、いわゆる<ruby>割り込み記述子表<rp> (</rp><rt>interrupt descriptor table</rt><rp>) </rp></ruby> (IDT) を設定しないといけません。この表にそれぞれのCPU例外に対するハンドラ関数を指定することができます。ハードウェアはこの表を直接使うので、決められたフォーマットに従わないといけません。それぞれのエントリは以下の16バイトの構造を持たなければなりません:
|
例外を捕捉し処理するためには、いわゆる割り込み記述子表 (Interrupt Descriptor Table, IDT) を設定しないといけません。この表にそれぞれのCPU例外に対するハンドラ関数を指定することができます。ハードウェアはこの表を直接使うので、決められたフォーマットに従わないといけません。それぞれのエントリは以下の16バイトの構造を持たなければなりません:
|
||||||
|
|
||||||
型 | 名前 | 説明
|
型 | 名前 | 説明
|
||||||
----|--------------------------|-----------------------------------
|
----|--------------------------|-----------------------------------
|
||||||
u16 | 関数ポインタ [0:15] | ハンドラ関数へのポインタの下位ビット。
|
u16 | 関数ポインタ [0:15] | ハンドラ関数へのポインタの下位ビット。
|
||||||
u16 | GDTセレクタ | [<ruby>大域記述子表<rp> (</rp><rt>global descriptor table</rt><rp>) </rp></ruby>][global descriptor table]におけるコードセグメントを選ぶ。
|
u16 | GDTセレクタ | [大域記述子表 (Global Descriptor Table)][global descriptor table] におけるコードセグメントを選ぶ。
|
||||||
u16 | オプション | (下を参照)
|
u16 | オプション | (下を参照)
|
||||||
u16 | 関数ポインタ [16:31] | ハンドラ関数へのポインタの中位ビット。
|
u16 | 関数ポインタ [16:31] | ハンドラ関数へのポインタの中位ビット。
|
||||||
u32 | 関数ポインタ [32:63] | ハンドラ関数へのポインタの上位ビット。
|
u32 | 関数ポインタ [32:63] | ハンドラ関数へのポインタの上位ビット。
|
||||||
@@ -69,7 +69,7 @@ u32 | 予約済 |
|
|||||||
13‑14 | <ruby>記述子の特権レベル<rp> (</rp><rt>Descriptor Privilege Level</rt><rp>) </rp></ruby> (DPL) | このハンドラを呼ぶ際に必要になる最低限の特権レベル。
|
13‑14 | <ruby>記述子の特権レベル<rp> (</rp><rt>Descriptor Privilege Level</rt><rp>) </rp></ruby> (DPL) | このハンドラを呼ぶ際に必要になる最低限の特権レベル。
|
||||||
15 | Present |
|
15 | Present |
|
||||||
|
|
||||||
それぞれの例外がIDTの何番目に対応するかは事前に定義されています。例えば、"無効な命令コード"例外は6番目で、"ページフォルト"例外は14番目です。これにより、ハードウェアがそれぞれの例外に対応するIDTの設定を(特に設定の必要なく)自動的に読み出せるというわけです。OSDev wikiの[「例外表」][exceptions]の "Vector nr." 列にすべての例外のIDTインデックスが記されています。
|
それぞれの例外がIDTの何番目に対応するかは事前に定義されています。例えば、「無効な命令コード」の例外は6番目で、「ページフォルト」例外は14番目です。これにより、ハードウェアがそれぞれの例外に対応するIDTの設定を(特に設定の必要なく)自動的に読み出せるというわけです。OSDev wikiの[「例外表」][exceptions]の "Vector nr." 列に、すべての例外についてIDTの何番目かが記されています。
|
||||||
|
|
||||||
例外が起こると、ざっくりCPUは以下のことを行います:
|
例外が起こると、ざっくりCPUは以下のことを行います:
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ pub struct InterruptDescriptorTable {
|
|||||||
type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame);
|
type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame);
|
||||||
```
|
```
|
||||||
|
|
||||||
これは、`extern "x86-interrupt" fn`型への[型エイリアス][type alias]です。`extern`は[外部呼び出し規約][foreign calling convention]に従う関数を定義するのに使われ、おもにC言語のコードとの間で通信をしたいときに使われます (`extern "C" fn`) 。しかし、`x86-interrupt`呼び出し規約とは何なのでしょう?
|
これは、`extern "x86-interrupt" fn`型への[型エイリアス][type alias]です。`extern`は[外部呼び出し規約][foreign calling convention]に従う関数を定義するのに使われ、おもにC言語のコードと連携したいときに使われます (`extern "C" fn`) 。しかし、`x86-interrupt`呼び出し規約とは何なのでしょう?
|
||||||
|
|
||||||
[type alias]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases
|
[type alias]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases
|
||||||
[foreign calling convention]: https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions
|
[foreign calling convention]: https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions
|
||||||
@@ -149,18 +149,18 @@ type HandlerFunc = extern "x86-interrupt" fn(_: &mut InterruptStackFrame);
|
|||||||
- 追加の引数はスタックで渡される
|
- 追加の引数はスタックで渡される
|
||||||
- 結果は`rax`と`rdx`で返される
|
- 結果は`rax`と`rdx`で返される
|
||||||
|
|
||||||
注意してほしいのは、RustはC言語のABIに従っていない(実は、[RustのABIすらまだありません][rust abi])ので、このルールは`extern "C" fn`と宣言された関数にしか適用しないということです。
|
注意してほしいのは、RustはC言語のABIに従っていない(実は、[RustにはABIすらまだありません][rust abi])ので、このルールは`extern "C" fn`と宣言された関数にしか適用しないということです。
|
||||||
|
|
||||||
[rust abi]: https://github.com/rust-lang/rfcs/issues/600
|
[rust abi]: https://github.com/rust-lang/rfcs/issues/600
|
||||||
|
|
||||||
### PreservedレジスタとScratchレジスタ
|
### PreservedレジスタとScratchレジスタ
|
||||||
呼び出し規約はレジスタを2種類に分けています:<ruby>preserved<rp> (</rp><rt>保存</rt><rp>) </rp></ruby>レジスタと<ruby>scratch<rp> (</rp><rt>下書き</rt><rp>) </rp></ruby>レジスタです。
|
呼び出し規約はレジスタを2種類に分けています:<ruby>preserved<rp> (</rp><rt>保存</rt><rp>) </rp></ruby>レジスタと<ruby>scratch<rp> (</rp><rt>下書き</rt><rp>) </rp></ruby>レジスタです。
|
||||||
|
|
||||||
preservedレジスタの値は関数呼び出しの前後で変化してはいけません。ですので、呼び出された関数(訳注:callの受け身で"callee"と呼ばれます)は、リターンする前にその値をもとに戻す場合に限り、その値を上書きできます。そのため、これらのレジスタは<ruby>callee-saved<rp> (</rp><rt>呼び出し先によって保存される</rt><rp>) </rp></ruby>と呼ばれます。よくやる方法は、関数の最初でそのレジスタをスタックに保存し、リターンする直前にその値をもとに戻すことです。
|
preservedレジスタの値は関数呼び出しの前後で変化してはいけません。ですので、呼び出された関数(訳注:callの受け身で"callee"と呼ばれます)は、リターンする前にその値をもとに戻す場合に限り、その値を上書きできます。そのため、これらのレジスタは<ruby>callee-saved<rp> (</rp><rt>呼び出し先によって保存される</rt><rp>) </rp></ruby>と呼ばれます。よくとられる方法は、関数の最初でそのレジスタをスタックに保存し、リターンする直前にその値をもとに戻すことです。
|
||||||
|
|
||||||
それとは対照的に、呼び出された関数はscratchレジスタを何の制限もなく上書きすることができます。呼び出し元の関数がscratchレジスタの値を関数呼び出しの間保存しておきたいなら、関数呼び出しの前に自分で(スタックにプッシュするなどして)バックアップしておいて、もとに戻す必要があります。なので、scratchレジスタは<ruby>caller-saved<rp> (</rp><rt>呼び出し元によって保存される</rt><rp>) </rp></ruby>です。
|
それとは対照的に、呼び出された関数はscratchレジスタを何の制限もなく上書きすることができます。呼び出し元の関数がscratchレジスタの値を関数呼び出しの前後で保存したいなら、関数呼び出しの前に自分で(スタックにプッシュするなどして)バックアップしておいて、もとに戻す必要があります。なので、scratchレジスタは<ruby>caller-saved<rp> (</rp><rt>呼び出し元によって保存される</rt><rp>) </rp></ruby>です。
|
||||||
|
|
||||||
x86_64においては、C言語の呼び出し規約は以下のpreservedとscratchレジスタを指定します:
|
x86_64においては、C言語の呼び出し規約は以下のpreservedレジスタとscratchレジスタを指定します:
|
||||||
|
|
||||||
preservedレジスタ | scratchレジスタ
|
preservedレジスタ | scratchレジスタ
|
||||||
--- | ---
|
--- | ---
|
||||||
@@ -176,7 +176,7 @@ _callee-saved_ | _caller-saved_
|
|||||||
|
|
||||||
これは、関数の初めにすべてのレジスタがスタックに保存されるということを意味しないことに注意してください。その代わりに、コンパイラは関数によって上書きされてしまうレジスタのみをバックアップします。こうすれば、数個のレジスタしか使わない短い関数に対して、とても効率的なコードが生成できるでしょう。
|
これは、関数の初めにすべてのレジスタがスタックに保存されるということを意味しないことに注意してください。その代わりに、コンパイラは関数によって上書きされてしまうレジスタのみをバックアップします。こうすれば、数個のレジスタしか使わない短い関数に対して、とても効率的なコードが生成できるでしょう。
|
||||||
|
|
||||||
### The Interrupt Stack Frame
|
### 割り込み時のスタックフレーム
|
||||||
通常の関数呼び出し(`call`命令を使います)においては、CPUは対象の関数にジャンプする前にリターンアドレスをプッシュします。関数がリターンするとき(`ret`命令を使います)、CPUはこのリターンアドレスをポップし、そこにジャンプします。そのため、通常の関数呼び出しの際のスタックフレームは以下のようになっています:
|
通常の関数呼び出し(`call`命令を使います)においては、CPUは対象の関数にジャンプする前にリターンアドレスをプッシュします。関数がリターンするとき(`ret`命令を使います)、CPUはこのリターンアドレスをポップし、そこにジャンプします。そのため、通常の関数呼び出しの際のスタックフレームは以下のようになっています:
|
||||||
|
|
||||||

|

|
||||||
@@ -193,21 +193,21 @@ _callee-saved_ | _caller-saved_
|
|||||||
|
|
||||||
[`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register
|
[`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register
|
||||||
|
|
||||||
ですので、<ruby>割り込み時のスタックフレーム<rp> (</rp><rt>interrupt stack frame</rt><rp>) </rp></ruby>は以下のようになります:
|
ですので、割り込み時のスタックフレーム (interrupt stack frame) は以下のようになります:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
`x86_64`クレートにおいては、割り込み時のスタックフレームは[`InterruptStackFrame`]構造体によって表現されます。これは割り込みハンドラに`&mut`として渡すことができ、これを使うことで例外の原因に関して追加で情報を手に入れることができます。エラーコードは例外のうちいくつかしかプッシュしないので、構造体にはエラーコードのためのフィールドはありません。これらの例外は[`HandlerFuncWithErrCode`]という別の関数型を使いますが、これらは追加で`error_code`引数を持ちます。
|
`x86_64`クレートにおいては、割り込み時のスタックフレームは[`InterruptStackFrame`]構造体によって表現されます。これは割り込みハンドラに`&mut`として渡されるため、これを使うことで例外の原因に関して追加で情報を手に入れることができます。例外のすべてがエラーコードをプッシュするわけではないので、この構造体にはエラーコードのためのフィールドはありません。これらの例外は[`HandlerFuncWithErrCode`]という別の関数型を使いますが、これらは追加で`error_code`引数を持ちます。
|
||||||
|
|
||||||
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptStackFrame.html
|
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptStackFrame.html
|
||||||
|
|
||||||
### 舞台裏では何が
|
### 舞台裏では何が
|
||||||
`x86-interrupt`呼び出し規約は、この例外<ruby>処理<rp> (</rp><rt>ハンドル</rt><rp>) </rp></ruby>プロセスのややこしいところをほぼ全て隠蔽してくれる、強力な抽象化です。しかし、その後ろで何が起こっているのかを知っておいたほうが良いこともあるでしょう。以下に、`x86-interrupt`呼び出し規約がやってくれることを簡単なリストにして示しました。
|
`x86-interrupt`呼び出し規約は、この例外<ruby>処理<rp> (</rp><rt>ハンドル</rt><rp>) </rp></ruby>プロセスのややこしいところをほぼ全て隠蔽してくれる、強力な抽象化です。しかし、その後ろで何が起こっているのかを知っておいたほうが良いこともあるでしょう。以下に、`x86-interrupt`呼び出し規約がやってくれることを簡単なリストにして示しました。
|
||||||
|
|
||||||
- **引数を取得する**: 多くの呼び出し規約においては、引数はレジスタを使って渡されることを想定しています。例外ハンドラにおいては、スタックにバックアップする前にレジスタの値を上書きしてはいけないので、これは不可能です。代わりに、`x86-interrupt`呼び出し規約は、引数が既に特定のオフセットでスタック上にあることを認識しています。
|
- **引数を取得する**: 多くの呼び出し規約においては、引数はレジスタを使って渡されることを想定しています。例外ハンドラにおいては、スタックにバックアップする前にレジスタの値を上書きしてはいけないので、これは不可能です。その代わり、`x86-interrupt`呼び出し規約は、引数が既に特定のオフセットでスタック上にあることを認識しています。
|
||||||
- **`iretq`を使ってリターンする**: 割り込み時のスタックフレームは通常の関数呼び出しのスタックフレームとは全く異なるため、通常の `ret` 命令を使ってハンドラ関数から戻ることはできません。その代わりに、`iretq` 命令を使う必要があります。
|
- **`iretq`を使ってリターンする**: 割り込み時のスタックフレームは通常の関数呼び出しのスタックフレームとは全く異なるため、通常の `ret` 命令を使ってハンドラ関数から戻ることはできません。その代わりに、`iretq` 命令を使う必要があります。
|
||||||
- **エラーコードを処理する**: いくつかの例外の場合、エラーコードがプッシュされるのですが、これが状況をより複雑にします。エラーコードはスタックのアラインメントを変更し(次の箇条を参照)、リターンする前にスタックからポップされる必要があります。`x86-interrupt`呼び出し規約は、このややこしい仕組みをすべて処理してくれます。しかし、どのハンドラ関数がどの例外に使われているかは呼び出し規約側にはわからないので、関数の引数の数からその情報を推測する必要があります。つまり、プログラマはやはりそれぞれの例外に対して正しい関数型を使う責任があるということです。幸いにも、`x86_64`クレートで定義されている`InterruptDescriptorTable`型が、正しい関数型が確実に使われるようにしてくれます。
|
- **エラーコードを処理する**: いくつかの例外の場合、エラーコードがプッシュされるのですが、これが状況をより複雑にします。エラーコードはスタックのアラインメントを変更し(次の箇条を参照)、リターンする前にスタックからポップされる必要があるのです。`x86-interrupt`呼び出し規約は、このややこしい仕組みをすべて処理してくれます。しかし、どのハンドラ関数がどの例外に使われているかは呼び出し規約側にはわからないので、関数の引数の数からその情報を推測する必要があります。つまり、プログラマはやはりそれぞれの例外に対して正しい関数型を使う責任があるということです。幸いにも、`x86_64`クレートで定義されている`InterruptDescriptorTable`型が、正しい関数型が確実に使われるようにしてくれます。
|
||||||
- **スタックをアラインする**: 一部の命令(特にSSE命令)には、16バイトのスタックアラインメントを必要とするものがあります。CPUは例外が発生したときには必ずこのようにスタックが<ruby>整列<rp> (</rp><rt>アライン</rt><rp>) </rp></ruby>されることを保証しますが、例外の中には、エラーコードをプッシュしたとき再びスタックの整列を壊してしまうものもあります。この場合、`x86-interrupt`の呼び出し規約は、スタックを再整列させることでこの問題を解決します。
|
- **スタックをアラインする**: 一部の命令(特にSSE命令)には、16バイトのスタックアラインメントを必要とするものがあります。CPUは例外が発生したときには必ずこのようにスタックが<ruby>整列<rp> (</rp><rt>アライン</rt><rp>) </rp></ruby>されることを保証しますが、例外の中には、エラーコードをプッシュして再びスタックの整列を壊してしまうものもあります。この場合、`x86-interrupt`の呼び出し規約は、スタックを再整列させることでこの問題を解決します。
|
||||||
|
|
||||||
もしより詳しく知りたい場合は、例外の処理について[naked function][naked functions]を使って説明する一連の記事があります。[この記事の最下部][too-much-magic]にそこへのリンクがあります。
|
もしより詳しく知りたい場合は、例外の処理について[naked function][naked functions]を使って説明する一連の記事があります。[この記事の最下部][too-much-magic]にそこへのリンクがあります。
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ _callee-saved_ | _caller-saved_
|
|||||||
[too-much-magic]: #sasuganijian-dan-sugi
|
[too-much-magic]: #sasuganijian-dan-sugi
|
||||||
|
|
||||||
## 実装
|
## 実装
|
||||||
理論を理解したところで、私達のカーネルでCPUの例外を実際に処理していきましょう。まず、`src/interrupts.rs`に新しい割り込みのためのモジュールを作ります。このモジュールはまず、`init_idt`関数という、新しい`InterruptDescriptorTable`を作る関数を定義します。
|
理屈は理解したので、私達のカーネルでCPUの例外を実際に処理していきましょう。まず、`src/interrupts.rs`に割り込みのための新しいモジュールを作ります。このモジュールはまず、`init_idt`関数という、新しい`InterruptDescriptorTable`を作る関数を定義します。
|
||||||
|
|
||||||
``` rust
|
``` rust
|
||||||
// in src/lib.rs
|
// in src/lib.rs
|
||||||
@@ -231,7 +231,7 @@ pub fn init_idt() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
これで、ハンドラ関数を追加していくことができます。まず、[ブレークポイント例外][breakpoint exception]のハンドラを追加するところから始めましょう。ブレークポイント例外は、例外処理のテストをするのにうってつけの例外なのです。この例外の唯一の目的は、ブレークポイント命令`int3`が実行された時、プログラムを一時停止させるということです。
|
これで、ハンドラ関数を追加していくことができます。まず、[ブレークポイント例外][breakpoint exception]のハンドラを追加するところから始めましょう。ブレークポイント例外は、例外処理のテストをするのにうってつけの例外なのです。この例外の唯一の目的は、ブレークポイント命令`int3`が実行された時、プログラムを一時停止させることです。
|
||||||
|
|
||||||
[breakpoint exception]: https://wiki.osdev.org/Exceptions#Breakpoint
|
[breakpoint exception]: https://wiki.osdev.org/Exceptions#Breakpoint
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ pub fn init_idt() {
|
|||||||
|
|
||||||
["_How debuggers work_"]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
|
["_How debuggers work_"]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
|
||||||
|
|
||||||
今回の場合、命令を上書きする必要はありません。ブレークポイント命令が実行された時、メッセージを表示したうえで実行を継続したいだけです。ですので、単純な`breakpoint_handler`関数を作ってIDTに追加してみましょう。
|
今回の場合、命令を上書きしたりする必要はありません。ブレークポイント命令が実行された時、メッセージを表示したうえで実行を継続したいだけです。ですので、単純な`breakpoint_handler`関数を作ってIDTに追加してみましょう。
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// in src/interrupts.rs
|
// in src/interrupts.rs
|
||||||
@@ -309,7 +309,7 @@ error: `idt` does not live long enough
|
|||||||
|
|
||||||
`load`メソッドは(`idt`に)`&'static self`、つまりプログラムの実行されている間ずっと有効な参照を期待しています。これは、私達が別のIDTを読み込まない限り、CPUは割り込みのたびにこの表にアクセスするからです。そのため、`'static`より短いライフタイムの場合、<ruby>use-after-free<rp> (</rp><rt>解放後にアクセス</rt><rp>) </rp></ruby>バグが発生する可能性があります。
|
`load`メソッドは(`idt`に)`&'static self`、つまりプログラムの実行されている間ずっと有効な参照を期待しています。これは、私達が別のIDTを読み込まない限り、CPUは割り込みのたびにこの表にアクセスするからです。そのため、`'static`より短いライフタイムの場合、<ruby>use-after-free<rp> (</rp><rt>解放後にアクセス</rt><rp>) </rp></ruby>バグが発生する可能性があります。
|
||||||
|
|
||||||
実際、これはまさにここで起こっていることです。私達の`idt`はスタック上に生成されるので、`init`関数の中でしか有効ではないのです。この関数が終わると、このスタックメモリは他の関数に使い回されるので、CPUはランダムに中身が変更されたスタックメモリをIDTとして解釈してしまうのです。幸運にも、`InterruptDescriptorTable::load`メソッドは関数定義にこのライフタイムの要件を組み込んでいるので、Rustコンパイラはこのバグをコンパイル時に未然に防ぐことができたというわけです。
|
実際、これはまさにここで起こっていることです。私達の`idt`はスタック上に生成されるので、`init`関数の中でしか有効ではないのです。この関数が終わると、このスタックメモリは他の関数に使い回されるので、CPUはどこかもわからないスタックメモリをIDTとして解釈してしまうのです。幸運にも、`InterruptDescriptorTable::load`メソッドは関数定義にこのライフタイムの要件を組み込んでいるので、Rustコンパイラはこのバグをコンパイル時に未然に防ぐことができたというわけです。
|
||||||
|
|
||||||
この問題を解決するには、`idt`を`'static`なライフタイムの場所に格納する必要があります。これを達成するには、[`Box`]を使ってIDTをヒープに割当て、続いてそれを`'static`な参照に変換すればよいです。しかし、私達はOSのカーネルを書いている途中であり、(まだ)ヒープを持っていません。
|
この問題を解決するには、`idt`を`'static`なライフタイムの場所に格納する必要があります。これを達成するには、[`Box`]を使ってIDTをヒープに割当て、続いてそれを`'static`な参照に変換すればよいです。しかし、私達はOSのカーネルを書いている途中であり、(まだ)ヒープを持っていません。
|
||||||
|
|
||||||
@@ -346,13 +346,12 @@ pub fn init_idt() {
|
|||||||
|
|
||||||
[`unsafe` block]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
|
[`unsafe` block]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
|
||||||
|
|
||||||
#### Lazy Staticsならきっとなんとかしてくれる
|
#### Lazy Staticsにおまかせ
|
||||||
幸いにも、例の`lazy_static`マクロが存在します。このマクロは`static`をコンパイル時に評価する代わりに、最初に参照されたときに初期化を行います。このため、初期化時にはほとんどすべてのことができ、実行時にのみ決定する値を読み込むこともできます。
|
幸いにも、例の`lazy_static`マクロが存在します。このマクロは`static`をコンパイル時に評価する代わりに、最初に参照されたときに初期化を行います。このため、初期化時にはほとんどすべてのことができ、実行時にのみ決定する値を読み込むこともできます。
|
||||||
|
|
||||||
[VGAテキストバッファの抽象化をした][vga text buffer lazy static]ときに、すでに`lazy_static`クレートはインポートしました。そのため、すぐに`lazy_static!`マクロを使って静的なIDTを作ることができます。
|
[VGAテキストバッファの抽象化をした][vga text buffer lazy static]ときに、すでに`lazy_static`クレートはインポートしました。そのため、すぐに`lazy_static!`マクロを使って静的なIDTを作ることができます。
|
||||||
|
|
||||||
[vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.md#lazy-statics
|
[vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.ja.md#dai-keta-lazy-jing-de-bian-shu
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// in src/interrupts.rs
|
// in src/interrupts.rs
|
||||||
|
|
||||||
@@ -371,7 +370,7 @@ pub fn init_idt() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
この解決法では、`unsafe`ブロックが必要ないことに注目してください。`lazy_static!`マクロはその内部で確かに`unsafe`を使っているのですが、安全なインターフェースの中に抽象化されているのです。
|
この方法では`unsafe`ブロックが必要ないことに注目してください。`lazy_static!`マクロはその内部で`unsafe`を使ってはいるのですが、これは安全なインターフェースの中に抽象化されているのです。
|
||||||
|
|
||||||
### 実行する
|
### 実行する
|
||||||
|
|
||||||
@@ -385,8 +384,7 @@ pub fn init() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
With this function we now have a central place for initialization routines that can be shared between the different `_start` functions in our `main.rs`, `lib.rs`, and integration tests.
|
この関数により、`main.rs`、`lib.rs`および結合テストにおける、異なる`_start`関数で共有される、初期化ルーチンの「中央広場」ができました。
|
||||||
この関数により、`main.rs`、`lib.rs`、それに結合テストにおける異なる`_start`関数で共有できる、初期化ルーチンの中心地ができました。
|
|
||||||
|
|
||||||
`main.rs`内の`_start`関数を更新して、`init`を呼び出し、そのあとブレークポイント例外を発生させるようにしてみましょう:
|
`main.rs`内の`_start`関数を更新して、`init`を呼び出し、そのあとブレークポイント例外を発生させるようにしてみましょう:
|
||||||
|
|
||||||
@@ -421,8 +419,6 @@ pub extern "C" fn _start() -> ! {
|
|||||||
|
|
||||||
### テストを追加する
|
### テストを追加する
|
||||||
|
|
||||||
Let's create a test that ensures that the above continues to work. First, we update the `_start` function to also call `init`:
|
|
||||||
|
|
||||||
上記の動作が継続することを確認するテストを作成してみましょう。まず、`_start` 関数を更新して `init` を呼び出すようにします。
|
上記の動作が継続することを確認するテストを作成してみましょう。まず、`_start` 関数を更新して `init` を呼び出すようにします。
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|||||||
Reference in New Issue
Block a user