mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
Update post to use x86_64 v0.13.2
This commit is contained in:
@@ -179,18 +179,18 @@ test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"]
|
||||
|
||||
به جای فراخوانی دستی دستورالعمل های اسمبلی `in` و `out`، ما از انتزاعات ارائه شده توسط کریت [`x86_64`] استفاده میکنیم. برای افزودن یک وابستگی به آن کریت، آن را به بخش `dependencies` در `Cargo.toml` اضافه میکنیم:
|
||||
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.12.1/x86_64/
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.13.2/x86_64/
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
x86_64 = "0.12.1"
|
||||
x86_64 = "0.13.2"
|
||||
```
|
||||
|
||||
اکنون میتوانیم از نوع [`Port`] ارائه شده توسط کریت برای ایجاد عملکرد `exit_qemu` استفاده کنیم:
|
||||
|
||||
[`Port`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/port/struct.Port.html
|
||||
[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
|
||||
@@ -183,18 +183,18 @@ CPUと<ruby>周辺機器<rp> (</rp><rt>ペリフェラル</rt><rp>) </rp></ruby>
|
||||
|
||||
`in`と`out`のアセンブリ命令を手動で呼び出す代わりに、[`x86_64`]クレートによって提供される<ruby>abstraction<rp> (</rp><rt>抽象化されたもの</rt><rp>) </rp></ruby>を使います。このクレートへの依存を追加するため、`Cargo.toml`の`dependencies`セクションにこれを追加しましょう:
|
||||
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.12.1/x86_64/
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.13.2/x86_64/
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
x86_64 = "0.12.1"
|
||||
x86_64 = "0.13.2"
|
||||
```
|
||||
|
||||
これで、このクレートによって提供される[`Port`]型を使って`exit_qemu`関数を作ることができます。
|
||||
|
||||
[`Port`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/port/struct.Port.html
|
||||
[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
|
||||
@@ -174,18 +174,18 @@ The functionality of the `isa-debug-exit` device is very simple. When a `value`
|
||||
|
||||
Instead of manually invoking the `in` and `out` assembly instructions, we use the abstractions provided by the [`x86_64`] crate. To add a dependency on that crate, we add it to the `dependencies` section in our `Cargo.toml`:
|
||||
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.12.1/x86_64/
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.13.2/x86_64/
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
x86_64 = "0.12.1"
|
||||
x86_64 = "0.13.2"
|
||||
```
|
||||
|
||||
Now we can use the [`Port`] type provided by the crate to create an `exit_qemu` function:
|
||||
|
||||
[`Port`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/port/struct.Port.html
|
||||
[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
|
||||
@@ -90,7 +90,7 @@ Bits | Name | Description
|
||||
|
||||
به جای ایجاد نوع IDT خود ، از [ساختمان `InterruptDescriptorTable`] کرت `x86_64` استفاده خواهیم کرد که به این شکل است:
|
||||
|
||||
[ساختمان `InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[ساختمان `InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
|
||||
``` rust
|
||||
#[repr(C)]
|
||||
@@ -121,10 +121,10 @@ pub struct InterruptDescriptorTable {
|
||||
|
||||
فیلدها از نوع [`<idt::Entry<F`] هستند ، این ساختمانی است که فیلد های یک عنصر IDT را نشان می دهد (به جدول بالا مراجعه کنید). پارامتر نوع `F`، نوع تابع کنترل کننده مورد انتظار را تعریف می کند. می بینیم که برخی از عناصر به یک [`HandlerFunc`] و برخی دیگر به [`HandlerFuncWithErrCode`] نیاز دارند. خطای صفحه حتی نوع خاص خود را دارد: [`PageFaultHandlerFunc`].
|
||||
|
||||
[`<idt::Entry<F`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.Entry.html
|
||||
[`HandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFunc.html
|
||||
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
|
||||
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.PageFaultHandlerFunc.html
|
||||
[`<idt::Entry<F`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.Entry.html
|
||||
[`HandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFunc.html
|
||||
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
|
||||
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.PageFaultHandlerFunc.html
|
||||
|
||||
بیایید ابتدا به نوع `HandlerFunc` نگاه کنیم:
|
||||
|
||||
@@ -201,7 +201,7 @@ _callee-saved_ | _caller-saved_
|
||||
|
||||
در کرت `x86_64` ، فریم پشته وقفه توسط ساختمان [`InterruptStackFrame`] نشان داده می شود. این ساختمان به عنوان `&mut` به کنترل کننده وقفه منتقل می شود و می تواند برای دریافت اطلاعات بیشتر در مورد علت استثنا استفاده شود. ساختمان بدون فیلد کد خطا است ، زیرا فقط برخی از استثناها کد خطا را پوش میکنند. این استثناها از نوع تابع جداگانه [`HandlerFuncWithErrCode`] استفاده میکنند ، که دارای یک آرگومان اضافی `error_code` است.
|
||||
|
||||
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.12.1/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` یک انتزاع قدرتمند است که تقریباً تمام جزئیات پیچیده فرآیند مدیریت استثناها را پنهان می کند. با این حال ، گاهی اوقات مفید است که بدانیم پشت پرده چه اتفاقی می افتد. در اینجا یک مرور کوتاه از مواردی که قرارداد فراخوانی `x86-interrupt` انجام میدهد را میبینید:
|
||||
@@ -283,7 +283,7 @@ error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue
|
||||
برای اینکه پردازنده از جدول توصیف کننده وقفه جدید ما استفاده کند ، باید آن را با استفاده از دستورالعمل [`lidt`] بارگیری کنیم. ساختمان `InterruptDescriptorTable` از کرت ` x86_64` متد [`load`][InterruptDescriptorTable::load] را برای این کار فراهم می کند. بیایید سعی کنیم از آن استفاده کنیم:
|
||||
|
||||
[`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt
|
||||
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
|
||||
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
|
||||
|
||||
```rust
|
||||
// in src/interrupts.rs
|
||||
@@ -462,7 +462,7 @@ blog_os::interrupts::test_breakpoint_exception... [ok]
|
||||
قرارداد فراخوانی `x86-interrupt` و نوع [`InterruptDescriptorTable`] روند مدیریت استثناها را نسبتاً سر راست و بدون درد ساختهاند. اگر این برای شما بسیار جادویی بود و دوست دارید تمام جزئیات مهم مدیریت استثنا را بیاموزید، برای شما هم مطالبی داریم: مجموعه ["مدیریت استثناها با توابع برهنه"] ما، نحوه مدیریت استثناها بدون قرارداد فراخوانی`x86-interrupt` را نشان می دهد و همچنین نوع IDT خاص خود را ایجاد می کند. از نظر تاریخی، این پستها مهمترین پستهای مدیریت استثناها قبل از وجود قرارداد فراخوانی `x86-interrupt` و کرت `x86_64` بودند. توجه داشته باشید که این پستها بر اساس [نسخه اول] این وبلاگ هستند و ممکن است قدیمی باشند.
|
||||
|
||||
["مدیریت استثناها با توابع برهنه"]: @/edition-1/extra/naked-exceptions/_index.md
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[نسخه اول]: @/edition-1/_index.md
|
||||
|
||||
## مرحله بعدی چیست؟
|
||||
|
||||
@@ -84,7 +84,7 @@ Don't worry about steps 4 and 5 for now, we will learn about the global descript
|
||||
## An IDT Type
|
||||
Instead of creating our own IDT type, we will use the [`InterruptDescriptorTable` struct] of the `x86_64` crate, which looks like this:
|
||||
|
||||
[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
|
||||
``` rust
|
||||
#[repr(C)]
|
||||
@@ -115,10 +115,10 @@ pub struct InterruptDescriptorTable {
|
||||
|
||||
The fields have the type [`idt::Entry<F>`], which is a struct that represents the fields of an IDT entry (see the table above). The type parameter `F` defines the expected handler function type. We see that some entries require a [`HandlerFunc`] and some entries require a [`HandlerFuncWithErrCode`]. The page fault even has its own special type: [`PageFaultHandlerFunc`].
|
||||
|
||||
[`idt::Entry<F>`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.Entry.html
|
||||
[`HandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFunc.html
|
||||
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
|
||||
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/type.PageFaultHandlerFunc.html
|
||||
[`idt::Entry<F>`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.Entry.html
|
||||
[`HandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFunc.html
|
||||
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
|
||||
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/type.PageFaultHandlerFunc.html
|
||||
|
||||
Let's look at the `HandlerFunc` type first:
|
||||
|
||||
@@ -195,7 +195,7 @@ So the _interrupt stack frame_ looks like this:
|
||||
|
||||
In the `x86_64` crate, the interrupt stack frame is represented by the [`InterruptStackFrame`] struct. It is passed to interrupt handlers as `&mut` and can be used to retrieve additional information about the exception's cause. The struct contains no error code field, since only some few exceptions push an error code. These exceptions use the separate [`HandlerFuncWithErrCode`] function type, which has an additional `error_code` argument.
|
||||
|
||||
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptStackFrame.html
|
||||
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptStackFrame.html
|
||||
|
||||
### Behind the Scenes
|
||||
The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of:
|
||||
@@ -277,7 +277,7 @@ This error occurs because the `x86-interrupt` calling convention is still unstab
|
||||
In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it:
|
||||
|
||||
[`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt
|
||||
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
|
||||
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
|
||||
|
||||
```rust
|
||||
// in src/interrupts.rs
|
||||
@@ -457,7 +457,7 @@ blog_os::interrupts::test_breakpoint_exception... [ok]
|
||||
The `x86-interrupt` calling convention and the [`InterruptDescriptorTable`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own IDT type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed. Note that these posts are based on the [first edition] of this blog and might be out of date.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: @/edition-1/extra/naked-exceptions/_index.md
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[first edition]: @/edition-1/_index.md
|
||||
|
||||
## What's next?
|
||||
|
||||
@@ -242,7 +242,7 @@ I/O Map Base Address | `u16`
|
||||
|
||||
بیایید یک TSS جدید ایجاد کنیم که شامل یک پشته خطای دوگانه جداگانه در جدول پشته وقفه خود باشد. برای این منظور ما به یک ساختار TSS نیاز داریم. خوشبختانه کریت `x86_64` از قبل حاوی [ساختار `TaskStateSegment`] است که میتوانیم از آن استفاده کنیم.
|
||||
|
||||
[ساختار `TaskStateSegment`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/tss/struct.TaskStateSegment.html
|
||||
[ساختار `TaskStateSegment`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/tss/struct.TaskStateSegment.html
|
||||
|
||||
ما TSS را در یک ماژول جدید به نام `gdt` ایجاد میکنیم (نام این ماژول بعداً برایتان معنا پیدا میکند):
|
||||
|
||||
@@ -391,8 +391,8 @@ pub fn init() {
|
||||
|
||||
ما با استفاده از [`set_cs`] ثبات کد سگمنت را بارگذاری مجدد میکنیم و برای بارگذاری TSS با از [`load_tss`] استفاده میکنیم. توابع به عنوان `unsafe` علامت گذاری شدهاند، بنابراین برای فراخوانی آنها به یک بلوک `unsafe` نیاز داریم. چون ممکن است با بارگذاری انتخابگرهای نامعتبر، ایمنی حافظه از بین برود.
|
||||
|
||||
[`set_cs`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/segmentation/fn.set_cs.html
|
||||
[`load_tss`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/tables/fn.load_tss.html
|
||||
[`set_cs`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/segmentation/fn.set_cs.html
|
||||
[`load_tss`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/tables/fn.load_tss.html
|
||||
|
||||
اکنون که یک TSS معتبر و جدول پشته وقفه را بارگذاری کردیم، میتوانیم اندیس پشته را برای کنترل کننده خطای دوگانه در IDT تنظیم کنیم:
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ The _Privilege Stack Table_ is used by the CPU when the privilege level changes.
|
||||
### Creating a TSS
|
||||
Let's create a new TSS that contains a separate double fault stack in its interrupt stack table. For that we need a TSS struct. Fortunately, the `x86_64` crate already contains a [`TaskStateSegment` struct] that we can use.
|
||||
|
||||
[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.12.1/x86_64/structures/tss/struct.TaskStateSegment.html
|
||||
[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.13.2/x86_64/structures/tss/struct.TaskStateSegment.html
|
||||
|
||||
We create the TSS in a new `gdt` module (the name will make sense later):
|
||||
|
||||
@@ -375,8 +375,8 @@ pub fn init() {
|
||||
|
||||
We reload the code segment register using [`set_cs`] and to load the TSS using [`load_tss`]. The functions are marked as `unsafe`, so we need an `unsafe` block to invoke them. The reason is that it might be possible to break memory safety by loading invalid selectors.
|
||||
|
||||
[`set_cs`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/segmentation/fn.set_cs.html
|
||||
[`load_tss`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/tables/fn.load_tss.html
|
||||
[`set_cs`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/segmentation/fn.set_cs.html
|
||||
[`load_tss`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/tables/fn.load_tss.html
|
||||
|
||||
Now that we loaded a valid TSS and interrupt stack table, we can set the stack index for our double fault handler in the IDT:
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ extern "x86-interrupt" fn timer_interrupt_handler(
|
||||
|
||||
`timer_interrupt_handler` ما دارای امضای مشابه کنترل کننده های استثنای ما است ، زیرا پردازنده به طور یکسان به استثناها و وقفه های خارجی واکنش نشان می دهد (تنها تفاوت این است که برخی از استثناها کد خطا را در پشته ذخیره میکنند). ساختمان [`InterruptDescriptorTable`] تریت [`IndexMut`] را پیاده سازی می کند، بنابراین می توانیم از طریق سینتکس ایندکسدهی آرایه، به ایتم های جداگانه دسترسی پیدا کنیم.
|
||||
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[`IndexMut`]: https://doc.rust-lang.org/core/ops/trait.IndexMut.html
|
||||
|
||||
در کنترل کننده وقفه تایمر، یک نقطه را روی صفحه چاپ می کنیم. همانطور که وقفه تایمر به صورت دوره ای اتفاق می افتد ، انتظار داریم که در هر تیک تایمر یک نقطه ظاهر شود. با این حال، هنگامی که آن را اجرا می کنیم می بینیم که فقط یک نقطه چاپ می شود:
|
||||
@@ -338,7 +338,7 @@ pub fn _print(args: fmt::Arguments) {
|
||||
|
||||
تابع [`without_interrupts`] یک [کلوژر] را گرفته و آن را در یک محیط بدون وقفه اجرا می کند. ما از آن استفاده می کنیم تا اطمینان حاصل کنیم که تا زمانی که `Mutex` قفل شده است ، هیچ وقفه ای رخ نمی دهد. اکنون هنگامی که هسته را اجرا می کنیم ، می بینیم که آن بدون هنگ کردن به کار خود ادامه می دهد. (ما هنوز هیچ نقطه ای را مشاهده نمی کنیم ، اما این به این دلیل است که سرعت حرکت آنها بسیار سریع است. سعی کنید سرعت چاپ را کم کنید، مثلاً با قرار دادن `for _ in 0..10000 {}` در داخل حلقه.)
|
||||
|
||||
[`without_interrupts`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/interrupts/fn.without_interrupts.html
|
||||
[`without_interrupts`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/interrupts/fn.without_interrupts.html
|
||||
[کلوژر]: https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html
|
||||
|
||||
ما می توانیم همین تغییر را در تابع چاپ سریال نیز اعمال کنیم تا اطمینان حاصل کنیم که هیچ بنبستی در آن رخ نمی دهد:
|
||||
@@ -585,7 +585,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(
|
||||
|
||||
ما برای خواندن یک بایت از پورت داده صفحه کلید از نوع [`Port`] کرت `x86_64` استفاده میکنیم. این بایت [_اسکن کد_] نامیده می شود و عددی است که کلید فشرده شده / رها شده را نشان می دهد. ما هنوز کاری با اسکن کد انجام نمی دهیم ، فقط آن را روی صفحه چاپ می کنیم:
|
||||
|
||||
[`Port`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/port/struct.Port.html
|
||||
[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html
|
||||
[_اسکن کد_]: https://en.wikipedia.org/wiki/Scancode
|
||||
|
||||

|
||||
|
||||
@@ -208,7 +208,7 @@ extern "x86-interrupt" fn timer_interrupt_handler(
|
||||
|
||||
Our `timer_interrupt_handler` has the same signature as our exception handlers, because the CPU reacts identically to exceptions and external interrupts (the only difference is that some exceptions push an error code). The [`InterruptDescriptorTable`] struct implements the [`IndexMut`] trait, so we can access individual entries through array indexing syntax.
|
||||
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
|
||||
[`IndexMut`]: https://doc.rust-lang.org/core/ops/trait.IndexMut.html
|
||||
|
||||
In our timer interrupt handler, we print a dot to the screen. As the timer interrupt happens periodically, we would expect to see a dot appearing on each timer tick. However, when we run it we see that only a single dot is printed:
|
||||
@@ -333,7 +333,7 @@ pub fn _print(args: fmt::Arguments) {
|
||||
|
||||
The [`without_interrupts`] function takes a [closure] and executes it in an interrupt-free environment. We use it to ensure that no interrupt can occur as long as the `Mutex` is locked. When we run our kernel now we see that it keeps running without hanging. (We still don't notice any dots, but this is because they're scrolling by too fast. Try to slow down the printing, e.g. by putting a `for _ in 0..10000 {}` inside the loop.)
|
||||
|
||||
[`without_interrupts`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/interrupts/fn.without_interrupts.html
|
||||
[`without_interrupts`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/interrupts/fn.without_interrupts.html
|
||||
[closure]: https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html
|
||||
|
||||
We can apply the same change to our serial printing function to ensure that no deadlocks occur with it either:
|
||||
@@ -580,7 +580,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(
|
||||
|
||||
We use the [`Port`] type of the `x86_64` crate to read a byte from the keyboard's data port. This byte is called the [_scancode_] and is a number that represents the key press/release. We don't do anything with the scancode yet, we just print it to the screen:
|
||||
|
||||
[`Port`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/port/struct.Port.html
|
||||
[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html
|
||||
[_scancode_]: https://en.wikipedia.org/wiki/Scancode
|
||||
|
||||

|
||||
|
||||
@@ -239,8 +239,8 @@ Bit(s) | Name | Meaning
|
||||
|
||||
کریت `x86_64` انواع مختلفی را برای [جدولهای صفحه] و [ورودیهای] آنها فراهم میکند، بنابراین نیازی نیست که خودمان این ساختارها را ایجاد کنیم.
|
||||
|
||||
[جدولهای صفحه]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page_table/struct.PageTable.html
|
||||
[ورودیهای]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page_table/struct.PageTableEntry.html
|
||||
[جدولهای صفحه]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTable.html
|
||||
[ورودیهای]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html
|
||||
|
||||
### بافر ترجمه Lookaside
|
||||
|
||||
@@ -249,7 +249,7 @@ Bit(s) | Name | Meaning
|
||||
برخلاف سایر حافظههای پنهان پردازنده، TLB کاملاً شفاف نبوده و با تغییر محتوای جدولهای صفحه، ترجمهها را بهروز و حذف نمیکند. این بدان معنی است که هسته هر زمان که جدول صفحه را تغییر میدهد باید TLB را به صورت دستی بهروز کند. برای انجام این کار، یک دستورالعمل ویژه پردازنده وجود دارد به نام [`invlpg`] ("صفحه نامعتبر") که ترجمه برای صفحه مشخص شده را از TLB حذف میکند، بنابراین دوباره از جدول صفحه در دسترسی بعدی بارگیری میشود. TLB همچنین میتواند با بارگیری مجدد رجیستر `CR3`، که یک تعویض فضای آدرس را شبیهسازی میکند، کاملاً فلاش (کلمه: flush) شود. کریت `x86_64` توابع راست را برای هر دو نوع در [ماژول `tlb`] فراهم میکند.
|
||||
|
||||
[`invlpg`]: https://www.felixcloutier.com/x86/INVLPG.html
|
||||
[ماژول `tlb`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/tlb/index.html
|
||||
[ماژول `tlb`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/tlb/index.html
|
||||
|
||||
مهم است که به یاد داشته باشید که TLB را روی هر جدول صفحه فلاش کنید، زیرا در غیر اینصورت پردازنده ممکن است از ترجمه قدیمی استفاده کند، که میتواند منجر به باگهای غیرقطعی شود که اشکالزدایی آن بسیار سخت است.
|
||||
|
||||
@@ -304,8 +304,8 @@ extern "x86-interrupt" fn page_fault_handler(
|
||||
ثبات [`CR2`] بهطور خودکار توسط CPU روی خطای صفحه تنظیم میشود و حاوی آدرس مجازی قابل دسترسی است که باعث رخ دادن خطای صفحه شده است. ما برای خواندن و چاپ آن از تابع [`Cr2::read`] کریت ` x86_64` استفاده میکنیم. نوع [`PageFaultErrorCode`] اطلاعات بیشتری در مورد نوع دسترسی به حافظهای که باعث خطای صفحه شده است، فراهم می کند، به عنوان مثال این امر به دلیل خواندن یا نوشتن بوده است. به همین دلیل ما آن را نیز چاپ میکنیم. بدون رفع خطای صفحه نمیتوانیم به اجرا ادامه دهیم، بنابراین در انتها یک [hlt_loop] اضافه میکنیم.
|
||||
|
||||
[`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2
|
||||
[`Cr2::read`]: https://docs.rs/x86_64/0.12.1/x86_64/registers/control/struct.Cr2.html#method.read
|
||||
[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.PageFaultErrorCode.html
|
||||
[`Cr2::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr2.html#method.read
|
||||
[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html
|
||||
[LLVM bug]: https://github.com/rust-lang/rust/issues/57270
|
||||
[`hlt_loop`]: @/edition-2/posts/07-hardware-interrupts/index.md#the-hlt-instruction
|
||||
|
||||
@@ -339,7 +339,7 @@ pub extern "C" fn _start() -> ! {
|
||||
|
||||
ثبات `CR2` در واقع حاوی` 0xdeadbeaf` هست، آدرسی که سعی کردیم به آن دسترسی پیدا کنیم. کد خطا از طریق [`CAUSED_BY_WRITE`] به ما میگوید که خطا هنگام تلاش برای انجام یک عملیات نوشتن رخ داده است. حتی از طریق [بیتهایی که تنظیم _نشدهاند_][`PageFaultErrorCode`] اطلاعات بیشتری به ما میدهد. به عنوان مثال، عدم تنظیم پرچم `PROTECTION_VIOLATION` به این معنی است که خطای صفحه رخ داده است زیرا صفحه هدف وجود ندارد.
|
||||
|
||||
[`CAUSED_BY_WRITE`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.CAUSED_BY_WRITE
|
||||
[`CAUSED_BY_WRITE`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.CAUSED_BY_WRITE
|
||||
|
||||
میبینیم که اشارهگر دستورالعمل فعلی `0x2031b2` میباشد، بنابراین میدانیم که این آدرس به یک صفحه کد اشاره دارد. صفحات کد توسط بوتلودر بصورت فقط خواندنی نگاشت میشوند، بنابراین خواندن از این آدرس امکانپذیر است اما نوشتن باعث خطای صفحه میشود. میتوانید این کار را با تغییر اشارهگر `0xdeadbeaf` به `0x2031b2` امتحان کنید:
|
||||
|
||||
@@ -363,7 +363,7 @@ println!("write worked");
|
||||
|
||||
میبینیم که پیام _"read worked"_ چاپ شده است، که نشان میدهد عملیات خواندن هیچ خطایی ایجاد نکرده است. با این حال، به جای پیام _"write worked"_ خطای صفحه رخ میدهد. این بار پرچم [`PROTECTION_VIOLATION`] علاوه بر پرچم [`CAUSED_BY_WRITE`] تنظیم شده است، که نشاندهنده وجود صفحه است، اما عملیات روی آن مجاز نیست. در این حالت نوشتن در صفحه مجاز نیست زیرا صفحات کد به صورت فقط خواندنی نگاشت میشوند.
|
||||
|
||||
[`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION
|
||||
[`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION
|
||||
|
||||
### دسترسی به جدولهای صفحه
|
||||
|
||||
@@ -389,9 +389,9 @@ pub extern "C" fn _start() -> ! {
|
||||
|
||||
تابع [`Cr3::read`] از ` x86_64` جدول صفحه سطح 4 که در حال حاضر فعال است را از ثبات `CR3` برمیگرداند. یک تاپل (کلمه: tuple) از نوع [`PhysFrame`] و [`Cr3Flags`] برمیگرداند. ما فقط به قاب علاقهمَندیم، بنابراین عنصر دوم تاپل را نادیده میگیریم.
|
||||
|
||||
[`Cr3::read`]: https://docs.rs/x86_64/0.12.1/x86_64/registers/control/struct.Cr3.html#method.read
|
||||
[`PhysFrame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/frame/struct.PhysFrame.html
|
||||
[`Cr3Flags`]: https://docs.rs/x86_64/0.12.1/x86_64/registers/control/struct.Cr3Flags.html
|
||||
[`Cr3::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr3.html#method.read
|
||||
[`PhysFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/frame/struct.PhysFrame.html
|
||||
[`Cr3Flags`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr3Flags.html
|
||||
|
||||
هنگامی که آن را اجرا میکنیم، خروجی زیر را مشاهده میکنیم:
|
||||
|
||||
@@ -401,7 +401,7 @@ Level 4 page table at: PhysAddr(0x1000)
|
||||
|
||||
بنابراین جدول صفحه سطح 4 که در حال حاضر فعال است در آدرس `0x100` در حافظه _فیزیکی_ ذخیره میشود، همانطور که توسط نوع بستهبندی [`PhysAddr`] نشان داده شده است. حال سوال این است: چگونه میتوانیم از هسته خود به این جدول دسترسی پیدا کنیم؟
|
||||
|
||||
[`PhysAddr`]: https://docs.rs/x86_64/0.12.1/x86_64/addr/struct.PhysAddr.html
|
||||
[`PhysAddr`]: https://docs.rs/x86_64/0.13.2/x86_64/addr/struct.PhysAddr.html
|
||||
|
||||
دسترسی مستقیم به حافظه فیزیکی در هنگام فعال بودن صفحهبندی امکان پذیر نیست، زیرا برنامهها به راحتی میتوانند محافظت از حافظه (ترجمه: memory protection) را دور بزنند و در غیر اینصورت به حافظه سایر برنامهها دسترسی پیدا میکنند. بنابراین تنها راه دسترسی به جدول از طریق برخی از صفحههای مجازی است که به قاب فیزیکی در آدرس`0x1000` نگاشت شده. این مشکل ایجاد نگاشت برای قابهای جدول صفحه یک مشکل کلی است، زیرا هسته به طور مرتب به جدولهای صفحه دسترسی دارد، به عنوان مثال هنگام اختصاص پشته برای یک نخِ (ترجمه: thread) جدید.
|
||||
|
||||
|
||||
@@ -235,8 +235,8 @@ Let's take a closer look at the available flags:
|
||||
|
||||
The `x86_64` crate provides types for [page tables] and their [entries], so we don't need to create these structures ourselves.
|
||||
|
||||
[page tables]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page_table/struct.PageTable.html
|
||||
[entries]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page_table/struct.PageTableEntry.html
|
||||
[page tables]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTable.html
|
||||
[entries]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html
|
||||
|
||||
### The Translation Lookaside Buffer
|
||||
|
||||
@@ -245,7 +245,7 @@ A 4-level page table makes the translation of virtual addresses expensive, becau
|
||||
Unlike the other CPU caches, the TLB is not fully transparent and does not update or remove translations when the contents of page tables change. This means that the kernel must manually update the TLB whenever it modifies a page table. To do this, there is a special CPU instruction called [`invlpg`] (“invalidate page”) that removes the translation for the specified page from the TLB, so that it is loaded again from the page table on the next access. The TLB can also be flushed completely by reloading the `CR3` register, which simulates an address space switch. The `x86_64` crate provides Rust functions for both variants in the [`tlb` module].
|
||||
|
||||
[`invlpg`]: https://www.felixcloutier.com/x86/INVLPG.html
|
||||
[`tlb` module]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/tlb/index.html
|
||||
[`tlb` module]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/tlb/index.html
|
||||
|
||||
It is important to remember flushing the TLB on each page table modification because otherwise the CPU might keep using the old translation, which can lead to non-deterministic bugs that are very hard to debug.
|
||||
|
||||
@@ -300,8 +300,8 @@ extern "x86-interrupt" fn page_fault_handler(
|
||||
The [`CR2`] register is automatically set by the CPU on a page fault and contains the accessed virtual address that caused the page fault. We use the [`Cr2::read`] function of the `x86_64` crate to read and print it. The [`PageFaultErrorCode`] type provides more information about the type of memory access that caused the page fault, for example whether it was caused by a read or write operation. For this reason we print it too. We can't continue execution without resolving the page fault, so we enter a [`hlt_loop`] at the end.
|
||||
|
||||
[`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2
|
||||
[`Cr2::read`]: https://docs.rs/x86_64/0.12.1/x86_64/registers/control/struct.Cr2.html#method.read
|
||||
[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.PageFaultErrorCode.html
|
||||
[`Cr2::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr2.html#method.read
|
||||
[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html
|
||||
[LLVM bug]: https://github.com/rust-lang/rust/issues/57270
|
||||
[`hlt_loop`]: @/edition-2/posts/07-hardware-interrupts/index.md#the-hlt-instruction
|
||||
|
||||
@@ -335,7 +335,7 @@ When we run it, we see that our page fault handler is called:
|
||||
|
||||
The `CR2` register indeed contains `0xdeadbeaf`, the address that we tried to access. The error code tells us through the [`CAUSED_BY_WRITE`] that the fault occurred while trying to perform a write operation. It tells us even more through the [bits that are _not_ set][`PageFaultErrorCode`]. For example, the fact that the `PROTECTION_VIOLATION` flag is not set means that the page fault occurred because the target page wasn't present.
|
||||
|
||||
[`CAUSED_BY_WRITE`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.CAUSED_BY_WRITE
|
||||
[`CAUSED_BY_WRITE`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.CAUSED_BY_WRITE
|
||||
|
||||
We see that the current instruction pointer is `0x2031b2`, so we know that this address points to a code page. Code pages are mapped read-only by the bootloader, so reading from this address works but writing causes a page fault. You can try this by changing the `0xdeadbeaf` pointer to `0x2031b2`:
|
||||
|
||||
@@ -359,7 +359,7 @@ By commenting out the last line, we see that the read access works, but the writ
|
||||
|
||||
We see that the _"read worked"_ message is printed, which indicates that the read operation did not cause any errors. However, instead of the _"write worked"_ message a page fault occurs. This time the [`PROTECTION_VIOLATION`] flag is set in addition to the [`CAUSED_BY_WRITE`] flag, which indicates that the page was present, but the operation was not allowed on it. In this case, writes to the page are not allowed since code pages are mapped as read-only.
|
||||
|
||||
[`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION
|
||||
[`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION
|
||||
|
||||
### Accessing the Page Tables
|
||||
|
||||
@@ -385,9 +385,9 @@ pub extern "C" fn _start() -> ! {
|
||||
|
||||
The [`Cr3::read`] function of the `x86_64` returns the currently active level 4 page table from the `CR3` register. It returns a tuple of a [`PhysFrame`] and a [`Cr3Flags`] type. We are only interested in the frame, so we ignore the second element of the tuple.
|
||||
|
||||
[`Cr3::read`]: https://docs.rs/x86_64/0.12.1/x86_64/registers/control/struct.Cr3.html#method.read
|
||||
[`PhysFrame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/frame/struct.PhysFrame.html
|
||||
[`Cr3Flags`]: https://docs.rs/x86_64/0.12.1/x86_64/registers/control/struct.Cr3Flags.html
|
||||
[`Cr3::read`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr3.html#method.read
|
||||
[`PhysFrame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/frame/struct.PhysFrame.html
|
||||
[`Cr3Flags`]: https://docs.rs/x86_64/0.13.2/x86_64/registers/control/struct.Cr3Flags.html
|
||||
|
||||
When we run it, we see the following output:
|
||||
|
||||
@@ -397,7 +397,7 @@ Level 4 page table at: PhysAddr(0x1000)
|
||||
|
||||
So the currently active level 4 page table is stored at address `0x1000` in _physical_ memory, as indicated by the [`PhysAddr`] wrapper type. The question now is: how can we access this table from our kernel?
|
||||
|
||||
[`PhysAddr`]: https://docs.rs/x86_64/0.12.1/x86_64/addr/struct.PhysAddr.html
|
||||
[`PhysAddr`]: https://docs.rs/x86_64/0.13.2/x86_64/addr/struct.PhysAddr.html
|
||||
|
||||
Accessing physical memory directly is not possible when paging is active, since programs could easily circumvent memory protection and access memory of other programs otherwise. So the only way to access the table is through some virtual page that is mapped to the physical frame at address `0x1000`. This problem of creating mappings for page table frames is a general problem, since the kernel needs to access the page tables regularly, for example when allocating a stack for a new thread.
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ The above code assumes that the last level 4 entry with index `0o777` (511) is r
|
||||
|
||||
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:
|
||||
|
||||
[`RecursivePageTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.RecursivePageTable.html
|
||||
[`RecursivePageTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html
|
||||
|
||||
```rust
|
||||
// in src/memory.rs
|
||||
@@ -437,7 +437,7 @@ 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.
|
||||
|
||||
[`VirtAddr`]: https://docs.rs/x86_64/0.12.1/x86_64/addr/struct.VirtAddr.html
|
||||
[`VirtAddr`]: https://docs.rs/x86_64/0.13.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:
|
||||
@@ -550,7 +550,7 @@ The `VirtAddr` struct already provides methods to compute the indexes into the p
|
||||
|
||||
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.
|
||||
|
||||
[`PageTableEntry::frame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page_table/struct.PageTableEntry.html#method.frame
|
||||
[`PageTableEntry::frame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html#method.frame
|
||||
|
||||
Let's test our translation function by translating some addresses:
|
||||
|
||||
@@ -606,18 +606,18 @@ The base of the abstraction are two traits that define various page table mappin
|
||||
- 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 [`MapperAllSizes`] trait implies that the implementor implements `Mapper` for all pages sizes. In addition, it provides functions that work with multiple page sizes such as [`translate_addr`] or the general [`translate`].
|
||||
|
||||
[`Mapper`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.Mapper.html
|
||||
[`translate_page`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.Mapper.html#tymethod.translate_page
|
||||
[`map_to`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to
|
||||
[`MapperAllSizes`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.MapperAllSizes.html
|
||||
[`translate_addr`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.MapperAllSizes.html#method.translate_addr
|
||||
[`translate`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.MapperAllSizes.html#tymethod.translate
|
||||
[`Mapper`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html
|
||||
[`translate_page`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html#tymethod.translate_page
|
||||
[`map_to`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to
|
||||
[`MapperAllSizes`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.MapperAllSizes.html
|
||||
[`translate_addr`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.MapperAllSizes.html#method.translate_addr
|
||||
[`translate`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.MapperAllSizes.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).
|
||||
|
||||
[`OffsetPageTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.OffsetPageTable.html
|
||||
[`MappedPageTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.MappedPageTable.html
|
||||
[`RecursivePageTable`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.RecursivePageTable.html
|
||||
[`OffsetPageTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.OffsetPageTable.html
|
||||
[`MappedPageTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MappedPageTable.html
|
||||
[`RecursivePageTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.RecursivePageTable.html
|
||||
|
||||
In our case, the bootloader maps the complete physical memory at a virtual address specfied 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:
|
||||
|
||||
@@ -643,7 +643,7 @@ unsafe fn active_level_4_table(physical_memory_offset: VirtAddr)
|
||||
|
||||
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.
|
||||
|
||||
[`OffsetPageTable::new`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.OffsetPageTable.html#method.new
|
||||
[`OffsetPageTable::new`]: https://docs.rs/x86_64/0.13.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.
|
||||
|
||||
@@ -694,8 +694,8 @@ Until now we only looked at the page tables without modifying anything. Let's ch
|
||||
|
||||
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.
|
||||
|
||||
[`map_to`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/trait.Mapper.html#tymethod.map_to
|
||||
[`Mapper`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/trait.Mapper.html
|
||||
[`map_to`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.Mapper.html#tymethod.map_to
|
||||
[`Mapper`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.Mapper.html
|
||||
|
||||
#### A `create_example_mapping` Function
|
||||
|
||||
@@ -734,8 +734,8 @@ In addition to the `page` that should be mapped, the function expects a mutable
|
||||
|
||||
[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.12.1/x86_64/structures/paging/trait.FrameAllocator.html
|
||||
[`PageSize`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page/trait.PageSize.html
|
||||
[`FrameAllocator`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.FrameAllocator.html
|
||||
[`PageSize`]: https://docs.rs/x86_64/0.13.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.
|
||||
|
||||
@@ -747,8 +747,8 @@ The [`map_to`] function can fail, so it returns a [`Result`]. Since this is just
|
||||
|
||||
[`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html
|
||||
[`expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect
|
||||
[`MapperFlush`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.MapperFlush.html
|
||||
[`flush`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush
|
||||
[`MapperFlush`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MapperFlush.html
|
||||
[`flush`]: https://docs.rs/x86_64/0.13.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`
|
||||
|
||||
@@ -445,12 +445,12 @@ pub fn init_heap(
|
||||
|
||||
The function takes mutable references to a [`Mapper`] and a [`FrameAllocator`] instance, both limited to 4KiB pages by using [`Size4KiB`] as generic parameter. The return value of the function is a [`Result`] with the unit type `()` as success variant and a [`MapToError`] as error variant, which is the error type returned by the [`Mapper::map_to`] method. Reusing the error type makes sense here because the `map_to` method is the main source of errors in this function.
|
||||
|
||||
[`Mapper`]:https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.Mapper.html
|
||||
[`FrameAllocator`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/trait.FrameAllocator.html
|
||||
[`Size4KiB`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page/enum.Size4KiB.html
|
||||
[`Mapper`]:https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html
|
||||
[`FrameAllocator`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.FrameAllocator.html
|
||||
[`Size4KiB`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/enum.Size4KiB.html
|
||||
[`Result`]: https://doc.rust-lang.org/core/result/enum.Result.html
|
||||
[`MapToError`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/enum.MapToError.html
|
||||
[`Mapper::map_to`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to
|
||||
[`MapToError`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/enum.MapToError.html
|
||||
[`Mapper::map_to`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to
|
||||
|
||||
The implementation can be broken down into two parts:
|
||||
|
||||
@@ -464,18 +464,18 @@ The implementation can be broken down into two parts:
|
||||
|
||||
- We use the [`Mapper::map_to`] method for creating the mapping in the active page table. The method can fail, therefore we use the [question mark operator] again to forward the error to the caller. On success, the method returns a [`MapperFlush`] instance that we can use to update the [_translation lookaside buffer_] using the [`flush`] method.
|
||||
|
||||
[`VirtAddr`]: https://docs.rs/x86_64/0.12.1/x86_64/addr/struct.VirtAddr.html
|
||||
[`Page`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page/struct.Page.html
|
||||
[`containing_address`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page/struct.Page.html#method.containing_address
|
||||
[`Page::range_inclusive`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/page/struct.Page.html#method.range_inclusive
|
||||
[`FrameAllocator::allocate_frame`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/trait.FrameAllocator.html#tymethod.allocate_frame
|
||||
[`VirtAddr`]: https://docs.rs/x86_64/0.13.2/x86_64/addr/struct.VirtAddr.html
|
||||
[`Page`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/struct.Page.html
|
||||
[`containing_address`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/struct.Page.html#method.containing_address
|
||||
[`Page::range_inclusive`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/page/struct.Page.html#method.range_inclusive
|
||||
[`FrameAllocator::allocate_frame`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/trait.FrameAllocator.html#tymethod.allocate_frame
|
||||
[`None`]: https://doc.rust-lang.org/core/option/enum.Option.html#variant.None
|
||||
[`MapToError::FrameAllocationFailed`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/enum.MapToError.html#variant.FrameAllocationFailed
|
||||
[`MapToError::FrameAllocationFailed`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/enum.MapToError.html#variant.FrameAllocationFailed
|
||||
[`Option::ok_or`]: https://doc.rust-lang.org/core/option/enum.Option.html#method.ok_or
|
||||
[question mark operator]: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html
|
||||
[`MapperFlush`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.MapperFlush.html
|
||||
[`MapperFlush`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MapperFlush.html
|
||||
[_translation lookaside buffer_]: @/edition-2/posts/08-paging-introduction/index.md#the-translation-lookaside-buffer
|
||||
[`flush`]: https://docs.rs/x86_64/0.12.1/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush
|
||||
[`flush`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush
|
||||
|
||||
The final step is to call this function from our `kernel_main`:
|
||||
|
||||
|
||||
@@ -1744,8 +1744,8 @@ impl Executor {
|
||||
|
||||
Since we call `sleep_if_idle` directly after `run_ready_tasks`, which loops until the `task_queue` becomes empty, checking the queue again might seem unnecessary. However, a hardware interrupt might occur directly after `run_ready_tasks` returns, so there might be a new task in the queue at the time the `sleep_if_idle` function is called. Only if the queue is still empty, we put the CPU to sleep by executing the `hlt` instruction through the [`instructions::hlt`] wrapper function provided by the [`x86_64`] crate.
|
||||
|
||||
[`instructions::hlt`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/fn.hlt.html
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.12.1/x86_64/index.html
|
||||
[`instructions::hlt`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/fn.hlt.html
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.13.2/x86_64/index.html
|
||||
|
||||
Unfortunately, there is still a subtle race condition in this implementation. Since interrupts are asynchronous and can happen at any time, it is possible that an interrupt happens right between the `is_empty` check and the call to `hlt`:
|
||||
|
||||
@@ -1760,7 +1760,7 @@ In case this interrupt pushes to the `task_queue`, we put the CPU to sleep even
|
||||
|
||||
The answer is to disable interrupts on the CPU before the check and atomically enable them again together with the `hlt` instruction. This way, all interrupts that happen in between are delayed after the `hlt` instruction so that no wake-ups are missed. To implement this approach, we can use the [`enable_interrupts_and_hlt`] function provided by the [`x86_64`] crate. This function is only available since version 0.9.6, so you might need to update your `x86_64` dependency to use it.
|
||||
|
||||
[`enable_interrupts_and_hlt`]: https://docs.rs/x86_64/0.12.1/x86_64/instructions/interrupts/fn.enable_interrupts_and_hlt.html
|
||||
[`enable_interrupts_and_hlt`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/interrupts/fn.enable_interrupts_and_hlt.html
|
||||
|
||||
The updated implementation of our `sleep_if_idle` function looks like this:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user