Compare commits

...

6 Commits

Author SHA1 Message Date
Darius Wiles
b15e8688d7 Merge 37869dfe65 into 8b341c96c1 2025-01-16 18:19:06 +01:00
Philipp Oppermann
8b341c96c1 Merge pull request #1377 from phil-opp/raw-const
Use new `&raw const` operator instead of `unsafe {& _}`
2025-01-15 19:59:04 +01:00
Philipp Oppermann
7f1f658358 Use new &raw const operator instead of unsafe {& _} 2025-01-15 19:57:38 +01:00
Philipp Oppermann
0417fd22ef Merge pull request #1376 from phil-opp/remove-const_mut_refs-feature
The `const_mut_refs` feature is stable now and no longer required
2025-01-15 19:43:08 +01:00
Philipp Oppermann
69a498c6a3 The const_mut_refs feature is stable now and no longer required 2025-01-15 19:40:20 +01:00
Darius Wiles
37869dfe65 Correct minimum number of page table entries needed for multilevel tables
A page table that needs to map virtual addresses up to 1_000_150 when the page size is 50 bytes needs 1_000_150 ÷ 50 = 20_003 entries.
2024-11-21 12:16:07 -08:00
8 changed files with 51 additions and 44 deletions

View File

@@ -266,7 +266,7 @@ lazy_static! {
const STACK_SIZE: usize = 4096 * 5; const STACK_SIZE: usize = 4096 * 5;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); let stack_start = VirtAddr::from_ptr(&raw const STACK);
let stack_end = stack_start + STACK_SIZE; let stack_end = stack_start + STACK_SIZE;
stack_end stack_end
}; };
@@ -277,7 +277,9 @@ lazy_static! {
ما از `lazy_static` استفاده می‌کنیم زیرا ارزیابی کننده ثابت راست هنوز آن‌قدر توانمند نیست که بتواند این مقداردهی اولیه را در زمان کامپایل انجام دهد. ما تعریف می‌کنیم که ورودی صفرم IST پشته خطای دوگانه است (هر اندیس دیگری از IST نیز قابل استفاده است). سپس آدرس بالای یک پشته خطای دوگانه را در ورودی صفرم می‌نویسیم. ما آدرس بالایی را می‌نویسیم زیرا پشته‌های x86 به سمت پایین رشد می‌کنند، یعنی از آدرس‌های بالا به آدرس‌های پایین می‌آیند. ما از `lazy_static` استفاده می‌کنیم زیرا ارزیابی کننده ثابت راست هنوز آن‌قدر توانمند نیست که بتواند این مقداردهی اولیه را در زمان کامپایل انجام دهد. ما تعریف می‌کنیم که ورودی صفرم IST پشته خطای دوگانه است (هر اندیس دیگری از IST نیز قابل استفاده است). سپس آدرس بالای یک پشته خطای دوگانه را در ورودی صفرم می‌نویسیم. ما آدرس بالایی را می‌نویسیم زیرا پشته‌های x86 به سمت پایین رشد می‌کنند، یعنی از آدرس‌های بالا به آدرس‌های پایین می‌آیند.
ما هنوز مدیریت حافظه را پیاده سازی نکرده‌ایم، بنابراین روش مناسبی برای اختصاص پشته جدید نداریم. در عوض، فعلاً از یک آرایه `static mut` به عنوان حافظه پشته استفاده میکنیم. `unsafe` لازم است زیرا هنگام دسترسی به استاتیک‌های تغییرپذیر (ترجمه: mutable)، کامپایلر نمی‌تواند عدم وجود رقابت بین داده ها را تضمین کند. مهم است که یک `static mut` باشد و نه یک استاتیک‌ تغییرناپذیر (ترجمه: immutable)، زیرا در غیر این صورت bootloader آن را به یک صفحه فقط خواندنی نگاشت می‌کند. ما در پست بعدی این را با یک تخصیص پشته مناسب جایگزین خواهیم کرد، سپس `unsafe` دیگر در این‌جا مورد نیاز نخواهد بود. ما هنوز مدیریت حافظه را پیاده سازی نکرده‌ایم، بنابراین روش مناسبی برای اختصاص پشته جدید نداریم.
در عوض، فعلاً از یک آرایه `static mut` به عنوان حافظه پشته استفاده میکنیم.
مهم است که یک `static mut` باشد و نه یک استاتیک‌ تغییرناپذیر (ترجمه: immutable)، زیرا در غیر این صورت bootloader آن را به یک صفحه فقط خواندنی نگاشت می‌کند.
توجه داشته باشید که این پشته خطای دوگانه فاقد صفحه محافظ در برابر سرریز پشته است. یعنی ما نباید هیچ کاری که اضافه شدن ایتمی در پشته شود را انجام دهیم زیرا سرریز پشته ممکن است حافظه زیر پشته را خراب کند. توجه داشته باشید که این پشته خطای دوگانه فاقد صفحه محافظ در برابر سرریز پشته است. یعنی ما نباید هیچ کاری که اضافه شدن ایتمی در پشته شود را انجام دهیم زیرا سرریز پشته ممکن است حافظه زیر پشته را خراب کند.

View File

@@ -126,10 +126,10 @@ CPUはダブルフォルトハンドラを呼べるようになったので、
幸いにもAMD64のマニュアル[PDF][AMD64 manual]には正確な定義が書かれています8.2.9章)。それによると「ダブルフォルト例外は直前の(一度目の)例外ハンドラの処理中に二度目の例外が発生したとき**起きうる** can occur」と書かれています。**起きうる**というのが重要で、とても特別な例外の組み合わせでのみダブルフォルトとなります。この組み合わせは以下のようになっています。 幸いにもAMD64のマニュアル[PDF][AMD64 manual]には正確な定義が書かれています8.2.9章)。それによると「ダブルフォルト例外は直前の(一度目の)例外ハンドラの処理中に二度目の例外が発生したとき**起きうる** can occur」と書かれています。**起きうる**というのが重要で、とても特別な例外の組み合わせでのみダブルフォルトとなります。この組み合わせは以下のようになっています。
最初の例外 | 二度目の例外 | 最初の例外 | 二度目の例外 |
----------------|----------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
[ゼロ除算],<br>[無効TSS],<br>[セグメント不在],<br>[スタックセグメントフォルト],<br>[一般保護違反] | [無効TSS],<br>[セグメント不在],<br>[スタックセグメントフォルト],<br>[一般保護違反] | [ゼロ除算],<br>[無効TSS],<br>[セグメント不在],<br>[スタックセグメントフォルト],<br>[一般保護違反] | [無効TSS],<br>[セグメント不在],<br>[スタックセグメントフォルト],<br>[一般保護違反] |
[ページフォルト] | [ページフォルト],<br>[無効TSS],<br>[セグメント不在],<br>[スタックセグメントフォルト],<br>[一般保護違反] | [ページフォルト] | [ページフォルト],<br>[無効TSS],<br>[セグメント不在],<br>[スタックセグメントフォルト],<br>[一般保護違反] |
[ゼロ除算]: https://wiki.osdev.org/Exceptions#Division_Error [ゼロ除算]: https://wiki.osdev.org/Exceptions#Division_Error
[無効TSS]: https://wiki.osdev.org/Exceptions#Invalid_TSS [無効TSS]: https://wiki.osdev.org/Exceptions#Invalid_TSS
@@ -217,15 +217,15 @@ x86_64ではTSSはタスク固有の情報は全く持たなくなりました
64ビットのTSSは下記のようなフォーマットです 64ビットのTSSは下記のようなフォーマットです
フィールド | 型 | フィールド | 型 |
------ | ---------------- | -------------------------------------------- | ---------- |
<span style="opacity: 0.5">(予約済み)</span> | `u32` | <span style="opacity: 0.5">(予約済み)</span> | `u32` |
特権スタックテーブル | `[u64; 3]` | 特権スタックテーブル | `[u64; 3]` |
<span style="opacity: 0.5">(予約済み)</span> | `u64` | <span style="opacity: 0.5">(予約済み)</span> | `u64` |
割り込みスタックテーブル | `[u64; 7]` | 割り込みスタックテーブル | `[u64; 7]` |
<span style="opacity: 0.5">(予約済み)</span> | `u64` | <span style="opacity: 0.5">(予約済み)</span> | `u64` |
<span style="opacity: 0.5">(予約済み)</span> | `u16` | <span style="opacity: 0.5">(予約済み)</span> | `u16` |
I/Oマップベースアドレス | `u16` | I/Oマップベースアドレス | `u16` |
**特権スタックテーブル**は特権レベルが変わった際にCPUが使用します。例えば、CPUがユーザーモード特権レベル3の時に例外が発生した場合、CPUは通常例外ハンドラを呼び出す前にカーネルモード特権レベル0に切り替わります。この場合、CPUは特権レベルスタックテーブルの0番目のスタックに切り替わります。ユーザーモードについてはまだ実装してないため、このテープルはとりあえず無視しておきましょう。 **特権スタックテーブル**は特権レベルが変わった際にCPUが使用します。例えば、CPUがユーザーモード特権レベル3の時に例外が発生した場合、CPUは通常例外ハンドラを呼び出す前にカーネルモード特権レベル0に切り替わります。この場合、CPUは特権レベルスタックテーブルの0番目のスタックに切り替わります。ユーザーモードについてはまだ実装してないため、このテープルはとりあえず無視しておきましょう。
@@ -256,7 +256,7 @@ lazy_static! {
const STACK_SIZE: usize = 4096 * 5; const STACK_SIZE: usize = 4096 * 5;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); let stack_start = VirtAddr::from_ptr(&raw const STACK);
let stack_end = stack_start + STACK_SIZE; let stack_end = stack_start + STACK_SIZE;
stack_end stack_end
}; };
@@ -267,7 +267,12 @@ lazy_static! {
Rustの定数評価機はこの初期化をコンパイル時に行うことがまだできないので`lazy_static`を使います。ここでは0番目のISTエントリをダブルフォルト用のスタックとして定義します他のISTのインデックスでも動くでしょう。そして、ダブルフォルト用スタックの先頭アドレスを0番目のエントリに書き込みます。先頭アドレスを書き込むのはx86のスタックは下、つまり高いアドレスから低いアドレスに向かって伸びていくからです。 Rustの定数評価機はこの初期化をコンパイル時に行うことがまだできないので`lazy_static`を使います。ここでは0番目のISTエントリをダブルフォルト用のスタックとして定義します他のISTのインデックスでも動くでしょう。そして、ダブルフォルト用スタックの先頭アドレスを0番目のエントリに書き込みます。先頭アドレスを書き込むのはx86のスタックは下、つまり高いアドレスから低いアドレスに向かって伸びていくからです。
私達はまだメモリ管理を実装していません。そのため、新しいスタックを確保する適切な方法がありません。その代わり今回は、スタックのストレージとして`static mut`な配列を使います。コンパイラが変更可能な静的変数がアクセスされるとき競合がないことを保証できないため`unsafe`が必要となります。これが不変の`static`ではなく`static mut`であることは重要です。そうでなければブートローダーはこれをリードオンリーのページにマップしてしまうからです。私達は後の記事でこの部分を適切なスタック確保処理に置き換えます。そうすればこの部分での`unsafe`は必要なくなります。 私達はまだメモリ管理を実装していません。そのため、新しいスタックを確保する適切な方法がありません。
その代わり今回は、スタックのストレージとして`static mut`な配列を使います。
これが不変の`static`ではなく`static mut`であることは重要です。
そうでなければブートローダーはこれをリードオンリーのページにマップしてしまうからです。
私達は後の記事でこの部分を適切なスタック確保処理に置き換えます。
ちなみに、このダブルフォルトスタックはスタックオーバーフローに対する保護をするガードページを持ちません。つまり、スタックオーバーフローがスタックより下のメモリを破壊するかもしれないので、私達はダブルフォルトハンドラ内でスタックを多用すべきではないということです。 ちなみに、このダブルフォルトスタックはスタックオーバーフローに対する保護をするガードページを持ちません。つまり、スタックオーバーフローがスタックより下のメモリを破壊するかもしれないので、私達はダブルフォルトハンドラ内でスタックを多用すべきではないということです。

View File

@@ -259,7 +259,7 @@ lazy_static! {
const STACK_SIZE: usize = 4096 * 5; const STACK_SIZE: usize = 4096 * 5;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); let stack_start = VirtAddr::from_ptr(&raw const STACK);
let stack_end = stack_start + STACK_SIZE; let stack_end = stack_start + STACK_SIZE;
stack_end stack_end
}; };
@@ -270,7 +270,9 @@ lazy_static! {
Rust의 const evaluator가 위와 같은 TSS의 초기화를 컴파일 중에 진행하지 못해서 `lazy_static`을 사용합니다. IST의 0번째 엔트리가 더블 폴트 스택이 되도록 정합니다 (꼭 0번째일 필요는 없음). 그다음 더블 폴트 스택의 최상단 주소를 IST의 0번째 엔트리에 저장합니다. 스택의 최상단 주소를 저장하는 이유는 x86 시스템에서 스택은 높은 주소에서 출발해 낮은 주소 영역 쪽으로 성장하기 때문입니다. Rust의 const evaluator가 위와 같은 TSS의 초기화를 컴파일 중에 진행하지 못해서 `lazy_static`을 사용합니다. IST의 0번째 엔트리가 더블 폴트 스택이 되도록 정합니다 (꼭 0번째일 필요는 없음). 그다음 더블 폴트 스택의 최상단 주소를 IST의 0번째 엔트리에 저장합니다. 스택의 최상단 주소를 저장하는 이유는 x86 시스템에서 스택은 높은 주소에서 출발해 낮은 주소 영역 쪽으로 성장하기 때문입니다.
우리가 아직 커널에 메모리 관리 (memory management) 기능을 구현하지 않아서 스택을 할당할 정규적인 방법이 없습니다. 임시방편으로 `static mut` 배열을 스택 메모리인 것처럼 사용할 것입니다. 값 변경이 가능한 static 변수에 접근하는 경우 컴파일러가 데이터 경쟁 상태 (data race)의 부재를 보장하지 못해 `unsafe` 키워드가 필요합니다. 배열은 꼭 `static`이 아닌 `static mut`로 설정해야 하는데, 그 이유는 부트로더가 `static` 변수를 읽기 전용 메모리 페이지에 배치하기 때문입니다. 이후에 다른 글에서 이 임시적인 스택 메모리 구현을 정석적인 구현으로 수정할 계획이며, 그 후에는 스택 메모리 접근에 더 이상 `unsafe`가 필요하지 않을 것입니다. 우리가 아직 커널에 메모리 관리 (memory management) 기능을 구현하지 않아서 스택을 할당할 정규적인 방법이 없습니다.
임시방편으로 `static mut` 배열을 스택 메모리인 것처럼 사용할 것입니다.
배열은 꼭 `static`이 아닌 `static mut`로 설정해야 하는데, 그 이유는 부트로더가 `static` 변수를 읽기 전용 메모리 페이지에 배치하기 때문입니다.
이 더블 폴트 스택에 스택 오버플로우를 감지하기 위한 보호 페이지가 없다는 것에 유의해야 합니다. 더블 폴트 스택에서 스택 오버플로우가 발생하면 스택 아래의 메모리 영역을 일부 덮어쓸 수 있기 때문에, 더블 폴트 처리 함수 안에서 스택 메모리를 과도하게 소모해서는 안됩니다. 이 더블 폴트 스택에 스택 오버플로우를 감지하기 위한 보호 페이지가 없다는 것에 유의해야 합니다. 더블 폴트 스택에서 스택 오버플로우가 발생하면 스택 아래의 메모리 영역을 일부 덮어쓸 수 있기 때문에, 더블 폴트 처리 함수 안에서 스택 메모리를 과도하게 소모해서는 안됩니다.

View File

@@ -124,10 +124,10 @@ For example, what happens if:
Fortunately, the AMD64 manual ([PDF][AMD64 manual]) has an exact definition (in Section 8.2.9). According to it, a “double fault exception _can_ occur when a second exception occurs during the handling of a prior (first) exception handler”. The _“can”_ is important: Only very specific combinations of exceptions lead to a double fault. These combinations are: Fortunately, the AMD64 manual ([PDF][AMD64 manual]) has an exact definition (in Section 8.2.9). According to it, a “double fault exception _can_ occur when a second exception occurs during the handling of a prior (first) exception handler”. The _“can”_ is important: Only very specific combinations of exceptions lead to a double fault. These combinations are:
First Exception | Second Exception | First Exception | Second Exception |
----------------|----------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
[Divide-by-zero],<br>[Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault] | [Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault] | [Divide-by-zero],<br>[Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault] | [Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault] |
[Page Fault] | [Page Fault],<br>[Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault] | [Page Fault] | [Page Fault],<br>[Invalid TSS],<br>[Segment Not Present],<br>[Stack-Segment Fault],<br>[General Protection Fault] |
[Divide-by-zero]: https://wiki.osdev.org/Exceptions#Division_Error [Divide-by-zero]: https://wiki.osdev.org/Exceptions#Division_Error
[Invalid TSS]: https://wiki.osdev.org/Exceptions#Invalid_TSS [Invalid TSS]: https://wiki.osdev.org/Exceptions#Invalid_TSS
@@ -215,15 +215,15 @@ On x86_64, the TSS no longer holds any task-specific information at all. Instead
The 64-bit TSS has the following format: The 64-bit TSS has the following format:
Field | Type | Field | Type |
------ | ---------------- | -------------------------------------------- | ---------- |
<span style="opacity: 0.5">(reserved)</span> | `u32` | <span style="opacity: 0.5">(reserved)</span> | `u32` |
Privilege Stack Table | `[u64; 3]` | Privilege Stack Table | `[u64; 3]` |
<span style="opacity: 0.5">(reserved)</span> | `u64` | <span style="opacity: 0.5">(reserved)</span> | `u64` |
Interrupt Stack Table | `[u64; 7]` | Interrupt Stack Table | `[u64; 7]` |
<span style="opacity: 0.5">(reserved)</span> | `u64` | <span style="opacity: 0.5">(reserved)</span> | `u64` |
<span style="opacity: 0.5">(reserved)</span> | `u16` | <span style="opacity: 0.5">(reserved)</span> | `u16` |
I/O Map Base Address | `u16` | I/O Map Base Address | `u16` |
The _Privilege Stack Table_ is used by the CPU when the privilege level changes. For example, if an exception occurs while the CPU is in user mode (privilege level 3), the CPU normally switches to kernel mode (privilege level 0) before invoking the exception handler. In that case, the CPU would switch to the 0th stack in the Privilege Stack Table (since 0 is the target privilege level). We don't have any user-mode programs yet, so we will ignore this table for now. The _Privilege Stack Table_ is used by the CPU when the privilege level changes. For example, if an exception occurs while the CPU is in user mode (privilege level 3), the CPU normally switches to kernel mode (privilege level 0) before invoking the exception handler. In that case, the CPU would switch to the 0th stack in the Privilege Stack Table (since 0 is the target privilege level). We don't have any user-mode programs yet, so we will ignore this table for now.
@@ -254,7 +254,7 @@ lazy_static! {
const STACK_SIZE: usize = 4096 * 5; const STACK_SIZE: usize = 4096 * 5;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); let stack_start = VirtAddr::from_ptr(&raw const STACK);
let stack_end = stack_start + STACK_SIZE; let stack_end = stack_start + STACK_SIZE;
stack_end stack_end
}; };
@@ -265,7 +265,7 @@ lazy_static! {
We use `lazy_static` because Rust's const evaluator is not yet powerful enough to do this initialization at compile time. We define that the 0th IST entry is the double fault stack (any other IST index would work too). Then we write the top address of a double fault stack to the 0th entry. We write the top address because stacks on x86 grow downwards, i.e., from high addresses to low addresses. We use `lazy_static` because Rust's const evaluator is not yet powerful enough to do this initialization at compile time. We define that the 0th IST entry is the double fault stack (any other IST index would work too). Then we write the top address of a double fault stack to the 0th entry. We write the top address because stacks on x86 grow downwards, i.e., from high addresses to low addresses.
We haven't implemented memory management yet, so we don't have a proper way to allocate a new stack. Instead, we use a `static mut` array as stack storage for now. The `unsafe` is required because the compiler can't guarantee race freedom when mutable statics are accessed. It is important that it is a `static mut` and not an immutable `static`, because otherwise the bootloader will map it to a read-only page. We will replace this with a proper stack allocation in a later post, then the `unsafe` will no longer be needed at this place. We haven't implemented memory management yet, so we don't have a proper way to allocate a new stack. Instead, we use a `static mut` array as stack storage for now. It is important that it is a `static mut` and not an immutable `static`, because otherwise the bootloader will map it to a read-only page. We will replace this with a proper stack allocation in a later post.
Note that this double fault stack has no guard page that protects against stack overflow. This means that we should not do anything stack-intensive in our double fault handler because a stack overflow might corrupt the memory below the stack. Note that this double fault stack has no guard page that protects against stack overflow. This means that we should not do anything stack-intensive in our double fault handler because a stack overflow might corrupt the memory below the stack.

View File

@@ -264,7 +264,7 @@ lazy_static! {
const STACK_SIZE: usize = 4096 * 5; const STACK_SIZE: usize = 4096 * 5;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); let stack_start = VirtAddr::from_ptr(&raw const STACK);
let stack_end = stack_start + STACK_SIZE; let stack_end = stack_start + STACK_SIZE;
stack_end stack_end
}; };
@@ -275,7 +275,9 @@ lazy_static! {
这次依然是使用 `lazy_static`Rust的静态变量求值器还没有强大到能够在编译器执行初始化代码。我们将IST的0号位定义为 double fault 的专属栈其他IST序号也可以如此施为。然后我们将栈的高地址指针写入0号位之所以这样做那是因为 x86 的栈内存分配是从高地址到低地址的。 这次依然是使用 `lazy_static`Rust的静态变量求值器还没有强大到能够在编译器执行初始化代码。我们将IST的0号位定义为 double fault 的专属栈其他IST序号也可以如此施为。然后我们将栈的高地址指针写入0号位之所以这样做那是因为 x86 的栈内存分配是从高地址到低地址的。
由于我们还没有实现内存管理机制,所以目前无法直接申请新栈,但我们可以使用 `static mut` 形式的数组来在内存中模拟出栈存储区。`unsafe` 块也是必须的,因为编译器认为这种可以被竞争的变量是不安全的,而且这里必须是 `static mut` 而不是不可修改的 `static`,否则 bootloader 会将其分配到只读页中。当然,在后续的文章中,我们会将其修改为真正的栈分配,`unsafe` 块也一定会去掉的。 由于我们还没有实现内存管理机制,所以目前无法直接申请新栈,但我们可以使用 `static mut` 形式的数组来在内存中模拟出栈存储区。
而且这里必须是 `static mut` 而不是不可修改的 `static`,否则 bootloader 会将其分配到只读页中。
当然,在后续的文章中,我们会将其修改为真正的栈分配。
但要注意,由于现在 double fault 获取的栈不再具有用于防止栈溢出的 guard page所以我们不应该做任何栈密集型操作了否则就有可能会污染到栈下方的内存区域。 但要注意,由于现在 double fault 获取的栈不再具有用于防止栈溢出的 guard page所以我们不应该做任何栈密集型操作了否则就有可能会污染到栈下方的内存区域。

View File

@@ -118,7 +118,7 @@ The simple page tables we just saw have a problem in larger address spaces: they
![Page 0 mapped to frame 0 and pages `1_000_000``1_000_150` mapped to frames 100250](single-level-page-table.svg) ![Page 0 mapped to frame 0 and pages `1_000_000``1_000_150` mapped to frames 100250](single-level-page-table.svg)
It only needs 4 physical frames, but the page table has over a million entries. We can't omit the empty entries because then the CPU would no longer be able to jump directly to the correct entry in the translation process (e.g., it is no longer guaranteed that the fourth page uses the fourth entry). It only needs 4 physical frames, but the page table has over twenty thousand entries. We can't omit the empty entries because then the CPU would no longer be able to jump directly to the correct entry in the translation process (e.g., it is no longer guaranteed that the fourth page uses the fourth entry).
To reduce the wasted memory, we can use a **two-level page table**. The idea is that we use different page tables for different address regions. An additional table called _level 2_ page table contains the mapping between address regions and (level 1) page tables. To reduce the wasted memory, we can use a **two-level page table**. The idea is that we use different page tables for different address regions. An additional table called _level 2_ page table contains the mapping between address regions and (level 1) page tables.
@@ -130,7 +130,7 @@ Page 0 falls into the first `10_000` byte region, so it uses the first entry of
The pages `1_000_000`, `1_000_050`, and `1_000_100` all fall into the 100th `10_000` byte region, so they use the 100th entry of the level 2 page table. This entry points to a different level 1 page table T2, which maps the three pages to frames `100`, `150`, and `200`. Note that the page address in level 1 tables does not include the region offset. For example, the entry for page `1_000_050` is just `50`. The pages `1_000_000`, `1_000_050`, and `1_000_100` all fall into the 100th `10_000` byte region, so they use the 100th entry of the level 2 page table. This entry points to a different level 1 page table T2, which maps the three pages to frames `100`, `150`, and `200`. Note that the page address in level 1 tables does not include the region offset. For example, the entry for page `1_000_050` is just `50`.
We still have 100 empty entries in the level 2 table, but much fewer than the million empty entries before. The reason for these savings is that we don't need to create level 1 page tables for the unmapped memory regions between `10_000` and `1_000_000`. We still have 100 empty entries in the level 2 table, but much fewer than the twenty thousand empty entries before. The reason for these savings is that we don't need to create level 1 page tables for the unmapped memory regions between `10_000` and `1_000_000`.
The principle of two-level page tables can be extended to three, four, or more levels. Then the page table register points to the highest level table, which points to the next lower level table, which points to the next lower level, and so on. The level 1 page table then points to the mapped frame. The principle in general is called a _multilevel_ or _hierarchical_ page table. The principle of two-level page tables can be extended to three, four, or more levels. Then the page table register points to the highest level table, which points to the next lower level table, which points to the next lower level, and so on. The level 1 page table then points to the mapped frame. The principle in general is called a _multilevel_ or _hierarchical_ page table.

View File

@@ -511,7 +511,7 @@ impl ListNode {
} }
``` ```
この型は`new`という単純なコンストラクタ関数を持ち、表現する領域の開始・終端アドレスを計算するメソッドを持っています。`new`関数は[const関数][const function]としていますが、これは後で静的な連結リストアロケータを作る際に必要になるためです。const関数においては、あらゆる可変参照の使用`next`フィールドを`None`にすることも含めはunstableであることに注意してください。コンパイルを通すためには、`#![feature(const_mut_refs)]``lib.rs`の最初に追加する必要があります。 この型は`new`という単純なコンストラクタ関数を持ち、表現する領域の開始・終端アドレスを計算するメソッドを持っています。`new`関数は[const関数][const function]としていますが、これは後で静的な連結リストアロケータを作る際に必要になるためです。
[const function]: https://doc.rust-lang.org/reference/items/functions.html#const-functions [const function]: https://doc.rust-lang.org/reference/items/functions.html#const-functions
@@ -968,8 +968,6 @@ impl FixedSizeBlockAllocator {
[`empty`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.empty [`empty`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.empty
もし`LinkedListAllocator`を実装するときにまだやっていないのなら、 **`#![feature(const_mut_refs)]`** を`lib.rs`の最初に追記しないといけません。const関数内におけるあらゆる可変参照型の使用はまだunstableで、それには`list_heads`フィールドの配列要素の型である`Option<&'static mut ListNode>`も(その値を`None`にしているにもかかわらず)含まれるからです。
このunsafeな`init`関数は`fallback_allocator`の[`init`]関数を呼ぶだけで、`list_heads`配列の初期化などは行いません。これらの配列の初期化は、`alloc``dealloc`呼び出しが行われたときに初めて行います。 このunsafeな`init`関数は`fallback_allocator`の[`init`]関数を呼ぶだけで、`list_heads`配列の初期化などは行いません。これらの配列の初期化は、`alloc``dealloc`呼び出しが行われたときに初めて行います。
[`init`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.init [`init`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.init

View File

@@ -510,7 +510,7 @@ impl ListNode {
} }
``` ```
The type has a simple constructor function named `new` and methods to calculate the start and end addresses of the represented region. We make the `new` function a [const function], which will be required later when constructing a static linked list allocator. Note that any use of mutable references in const functions (including setting the `next` field to `None`) is still unstable. In order to get it to compile, we need to add **`#![feature(const_mut_refs)]`** to the beginning of our `lib.rs`. The type has a simple constructor function named `new` and methods to calculate the start and end addresses of the represented region. We make the `new` function a [const function], which will be required later when constructing a static linked list allocator.
[const function]: https://doc.rust-lang.org/reference/items/functions.html#const-functions [const function]: https://doc.rust-lang.org/reference/items/functions.html#const-functions
@@ -967,8 +967,6 @@ The `new` function just initializes the `list_heads` array with empty nodes and
[`empty`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.empty [`empty`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.empty
If you haven't done so already for the `LinkedListAllocator` implementation, you also need to add **`#![feature(const_mut_refs)]`** to the top of your `lib.rs`. The reason is that any use of mutable reference types in const functions is still unstable, including the `Option<&'static mut ListNode>` array element type of the `list_heads` field (even if we set it to `None`).
The unsafe `init` function only calls the [`init`] function of the `fallback_allocator` without doing any additional initialization of the `list_heads` array. Instead, we will initialize the lists lazily on `alloc` and `dealloc` calls. The unsafe `init` function only calls the [`init`] function of the `fallback_allocator` without doing any additional initialization of the `list_heads` array. Instead, we will initialize the lists lazily on `alloc` and `dealloc` calls.
[`init`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.init [`init`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.init