diff --git a/.github/workflows/build-site.yml b/.github/workflows/build-site.yml
index 5af9b263..56a70214 100644
--- a/.github/workflows/build-site.yml
+++ b/.github/workflows/build-site.yml
@@ -21,8 +21,6 @@ jobs:
- name: 'Download Zola'
run: curl -sL https://github.com/getzola/zola/releases/download/v0.12.1/zola-v0.12.1-x86_64-unknown-linux-gnu.tar.gz | tar zxv
- - name: "Install Python Tools"
- run: python -m pip install --upgrade pip setuptools wheel
- name: 'Install Python Libraries'
run: python -m pip install --user -r requirements.txt
working-directory: "blog"
diff --git a/.github/workflows/scheduled-builds.yml b/.github/workflows/scheduled-builds.yml
index e7dc9328..39c6a0d5 100644
--- a/.github/workflows/scheduled-builds.yml
+++ b/.github/workflows/scheduled-builds.yml
@@ -28,6 +28,6 @@ jobs:
- name: Invoke workflow
uses: benc-uk/workflow-dispatch@v1.1
with:
- workflow: Build Code
+ workflow: Code
token: ${{ secrets.SCHEDULED_BUILDS_TOKEN }}
ref: ${{ matrix.branch }}
diff --git a/blog/before_build.py b/blog/before_build.py
index 8e79b6d3..458fed76 100644
--- a/blog/before_build.py
+++ b/blog/before_build.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import io
-import urllib2
+import urllib
import datetime
from github import Github
@@ -23,7 +23,7 @@ with io.open("templates/auto/recent-updates.html", 'w', encoding='utf8') as rece
recent_updates.truncate()
relnotes_issues = g.search_issues("is:merged", repo="phil-opp/blog_os", type="pr", label="relnotes")[:100]
- recent_relnotes_issues = filter(filter_date, relnotes_issues)
+ recent_relnotes_issues = list(filter(filter_date, relnotes_issues))
if len(recent_relnotes_issues) == 0:
recent_updates.write(u"No notable updates recently.")
@@ -58,8 +58,8 @@ month = 4
while True:
url = "https://rust-osdev.com/this-month/" + str(year) + "-" + str(month).zfill(2) + "/"
try:
- urllib2.urlopen(url)
- except urllib2.HTTPError as e:
+ urllib.request.urlopen(url)
+ except urllib.error.HTTPError as e:
break
month_str = datetime.date(1900, month, 1).strftime('%B')
@@ -69,7 +69,7 @@ while True:
month = month + 1
if month > 12:
- month = 0
+ month = 1
year = year + 1
lines.reverse()
diff --git a/blog/config.toml b/blog/config.toml
index b2c39a38..bdb4a06f 100644
--- a/blog/config.toml
+++ b/blog/config.toml
@@ -36,15 +36,65 @@ author = { name = "Philipp Oppermann" }
[translations.en]
lang_name = "English"
+toc = "Table of Contents"
+all_posts = "« All Posts"
+comments = "Comments"
+comments_notice = "Please leave your comments in English if possible."
+readmore = "read more »"
+not_translated = "(This post is not translated yet.)"
+translated_content = "Translated Content:"
+translated_content_notice = "This is a community translation of the _original.title_ post. It might be incomplete, outdated or contain errors. Please report any issues!"
+translated_by = "Translation by"
+word_separator = "and"
[translations.zh-CN]
lang_name = "Chinese (simplified)"
+toc = "Table of Contents"
+all_posts = "« All Posts"
+comments = "Comments"
+comments_notice = "Please leave your comments in English if possible."
+readmore = "read more »"
+not_translated = "(This post is not translated yet.)"
+translated_content = "Translated Content:"
+translated_content_notice = "This is a community translation of the _original.title_ post. It might be incomplete, outdated or contain errors. Please report any issues!"
+translated_by = "Translation by"
+word_separator = "and"
[translations.zh-TW]
lang_name = "Chinese (traditional)"
+toc = "Table of Contents"
+all_posts = "« All Posts"
+comments = "Comments"
+comments_notice = "Please leave your comments in English if possible."
+readmore = "read more »"
+not_translated = "(This post is not translated yet.)"
+translated_content = "Translated Content:"
+translated_content_notice = "This is a community translation of the _original.title_ post. It might be incomplete, outdated or contain errors. Please report any issues!"
+translated_by = "Translation by"
+word_separator = "and"
[translations.ja]
lang_name = "Japanese"
+toc = "目次"
+all_posts = "« すべての記事へ"
+comments = "コメント"
+comments_notice = "可能な限りコメントは英語で残すようにしてください。"
+readmore = "もっと読む »"
+not_translated = "(この記事はまだ翻訳されていません。)"
+translated_content = "この記事は翻訳されたものです:"
+translated_content_notice = "この記事は_original.title_をコミュニティの手により翻訳したものです。そのため、翻訳が完全・最新でなかったり、原文にない誤りを含んでいる可能性があります。問題があればこのissue上で報告してください!"
+translated_by = "翻訳者:"
+word_separator = "及び"
[translations.fa]
lang_name = "Persian"
+toc = "فهرست مطالب"
+all_posts = "« همه پستها"
+comments = "نظرات"
+comments_notice = "لطفا نظرات خود را در صورت امکان به انگلیسی بنویسید."
+readmore = "ادامهمطلب»"
+not_translated = "(.این پست هنوز ترجمه نشده است)"
+translated_content = "محتوای ترجمه شده:"
+translated_content_notice = "این یک ترجمه از جامعه کاربران برای پست _original.title_ است. ممکن است ناقص، منسوخ شده یا دارای خطا باشد. لطفا هر گونه مشکل را در این ایشو گزارش دهید!"
+translated_by = "ترجمه توسط"
+word_separator = "و"
diff --git a/blog/content/_index.fa.md b/blog/content/_index.fa.md
index 493e0c96..5e1e57b1 100644
--- a/blog/content/_index.fa.md
+++ b/blog/content/_index.fa.md
@@ -2,12 +2,12 @@
template = "edition-2/index.html"
+++
-
Writing an OS in Rust
+
نوشتن یک سیستم عامل با راست
-
+
-This blog series creates a small operating system in the [Rust programming language](https://www.rust-lang.org/). Each post is a small tutorial and includes all needed code, so you can follow along if you like. The source code is also available in the corresponding [Github repository](https://github.com/phil-opp/blog_os).
+این مجموعه بلاگ یک سیستم عامل کوچک در [زبان برنامه نویسی Rust](https://www.rust-lang.org/) ایجاد می کند. هر پست یک آموزش کوچک است و شامل تمام کدهای مورد نیاز است ، بنابراین اگر دوست دارید می توانید آن را دنبال کنید. کد منبع نیز در [مخزن گیتهاب](https://github.com/phil-opp/blog_os) مربوطه موجود است.
-Latest post:
+اخرین پست:
-This blog series creates a small operating system in the [Rust programming language](https://www.rust-lang.org/). Each post is a small tutorial and includes all needed code, so you can follow along if you like. The source code is also available in the corresponding [Github repository](https://github.com/phil-opp/blog_os).
+このブログシリーズでは、ちょっとしたオペレーティングシステムを[Rustプログラミング言語](https://www.rust-lang.org/)を使って作ります。それぞれの記事が小さなチュートリアルになっており、必要なコードも全て記事内に記されているので、一つずつ読み進めて行けば理解できるでしょう。対応した[Githubリポジトリ](https://github.com/phil-opp/blog_os)でソースコードを見ることもできます。
-Latest post:
+最新記事:
diff --git a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.ja.md b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.ja.md
index 847196aa..5cbbfafd 100644
--- a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.ja.md
+++ b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.ja.md
@@ -300,7 +300,7 @@ Rustコンパイラは、すべてのシステムにおいて、特定の組み
[`IntoIterator::into_iter`]: https://doc.rust-lang.org/stable/core/iter/trait.IntoIterator.html#tymethod.into_iter
-ありがたいことに、`compiler_builtins`クレートにはこれらの必要な関数すべての実装が含まれており、標準ではCライブラリの実装と競合しないように無効化されているだけなのです。これはcargoの[`build-std-features`]フラグを`["computer-builtins-mem"]`に設定することで有効化できます。`build-std`フラグと同じように、このフラグはコマンドラインで`-Z`フラグとして渡すこともできれば、`.cargo/config.toml`ファイルの`unstable`テーブルで設定することもできます。ビルド時は常にこのフラグをセットしたいので、設定ファイルを使う方が良いでしょう:
+ありがたいことに、`compiler_builtins`クレートにはこれらの必要な関数すべての実装が含まれており、標準ではCライブラリの実装と競合しないように無効化されているだけなのです。これはcargoの[`build-std-features`]フラグを`["compiler-builtins-mem"]`に設定することで有効化できます。`build-std`フラグと同じように、このフラグはコマンドラインで`-Z`フラグとして渡すこともできれば、`.cargo/config.toml`ファイルの`unstable`テーブルで設定することもできます。ビルド時は常にこのフラグをセットしたいので、設定ファイルを使う方が良いでしょう:
[`build-std-features`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std-features
@@ -313,12 +313,10 @@ build-std-features = ["compiler-builtins-mem"]
(`compiler-builtins-mem`機能のサポートが追加されたのは[つい最近](https://github.com/rust-lang/rust/pull/77284)なので、`2019-09-30`以降のRust nightlyが必要です。)
-このとき、裏で`compiler_builtins`クレートの[`mem`機能][`mem` feature]が有効化されています。これにより、このクレートの[`memcpy`などの実装][`memcpy` etc. implementations]に`#[no_mangle]`アトリビュートが適用され、リンカがこれらを利用できるようになっています。これらの関数は今のところ[最適化されておらず][not optimized]、性能は最高ではないかもしれないものの、少なくとも正しい実装ではあるということは知っておく価値があるでしょう。`x86_64`については、[これらの関数を特殊なアセンブリ命令を使って最適化する][memcpy rep movsb]プルリクエストが提出されています。
+このとき、裏で`compiler_builtins`クレートの[`mem`機能][`mem` feature]が有効化されています。これにより、このクレートの[`memcpy`などの実装][`memcpy` etc. implementations]に`#[no_mangle]`アトリビュートが適用され、リンカがこれらを利用できるようになっています。
[`mem` feature]: https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/Cargo.toml#L51-L52
[`memcpy` etc. implementations]: (https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/src/mem.rs#L12-L69)
-[not optimized]: https://github.com/rust-lang/compiler-builtins/issues/339
-[memcpy rep movsb]: https://github.com/rust-lang/compiler-builtins/pull/365
この変更をもって、私達のカーネルはコンパイラに必要とされているすべての関数の有効な実装を手に入れたので、コードがもっと複雑になっても変わらずコンパイルできるでしょう。
diff --git a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md
index 4819ecc8..7567da92 100644
--- a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md
+++ b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.md
@@ -310,12 +310,10 @@ build-std-features = ["compiler-builtins-mem"]
(Support for the `compiler-builtins-mem` feature was only [added very recently](https://github.com/rust-lang/rust/pull/77284), so you need at least Rust nightly `2020-09-30` for it.)
-Behind the scenes, this flag enables the [`mem` feature] of the `compiler_builtins` crate. The effect of this is that the `#[no_mangle]` attribute is applied to the [`memcpy` etc. implementations] of the crate, which makes them available to the linker. It's worth noting that these functions are [not optimized] right now, so their performance might not be the best, but at least they are correct. For `x86_64`, there is an open pull request to [optimize these functions using special assembly instructions][memcpy rep movsb].
+Behind the scenes, this flag enables the [`mem` feature] of the `compiler_builtins` crate. The effect of this is that the `#[no_mangle]` attribute is applied to the [`memcpy` etc. implementations] of the crate, which makes them available to the linker.
[`mem` feature]: https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/Cargo.toml#L54-L55
[`memcpy` etc. implementations]: https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/src/mem.rs#L12-L69
-[not optimized]: https://github.com/rust-lang/compiler-builtins/issues/339
-[memcpy rep movsb]: https://github.com/rust-lang/compiler-builtins/pull/365
With this change, our kernel has valid implementations for all compiler-required functions, so it will continue to compile even if our code gets more complex.
diff --git a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.zh-CN.md b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.zh-CN.md
index 26840f64..72f8f23f 100644
--- a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.zh-CN.md
+++ b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.zh-CN.md
@@ -109,7 +109,7 @@ Nightly 版本的编译器允许我们在源码的开头插入**特性标签**
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
- "executables": true,
+ "executables": true
}
```
diff --git a/blog/content/edition-2/posts/03-vga-text-buffer/index.md b/blog/content/edition-2/posts/03-vga-text-buffer/index.md
index 0bbf92c7..0029e751 100644
--- a/blog/content/edition-2/posts/03-vga-text-buffer/index.md
+++ b/blog/content/edition-2/posts/03-vga-text-buffer/index.md
@@ -305,6 +305,7 @@ We can add a dependency on the `volatile` crate by adding it to the `dependencie
volatile = "0.2.6"
```
+Make sure to specify `volatile` version `0.2.6`. Newer versions of the crate are not compatible with this post.
The `0.2.6` is the [semantic] version number. For more information, see the [Specifying Dependencies] guide of the cargo documentation.
[semantic]: https://semver.org/
diff --git a/blog/content/edition-2/posts/04-testing/index.fa.md b/blog/content/edition-2/posts/04-testing/index.fa.md
index 891d9ed6..3e119cd8 100644
--- a/blog/content/edition-2/posts/04-testing/index.fa.md
+++ b/blog/content/edition-2/posts/04-testing/index.fa.md
@@ -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
diff --git a/blog/content/edition-2/posts/04-testing/index.ja.md b/blog/content/edition-2/posts/04-testing/index.ja.md
new file mode 100644
index 00000000..de61e1ac
--- /dev/null
+++ b/blog/content/edition-2/posts/04-testing/index.ja.md
@@ -0,0 +1,1032 @@
++++
+title = "テスト"
+weight = 4
+path = "ja/testing"
+date = 2019-04-27
+
+[extra]
+chapter = "Bare Bones"
+# Please update this when updating the translation
+translation_based_on_commit = "dce5c9825bd4e7ea6c9530e999c9d58f80c585cc"
+# GitHub usernames of the people that translated this post
+translators = ["woodyZootopia", "JohnTitor"]
++++
+
+この記事では、`no_std`な実行環境における単体テストと結合テストについて学びます。Rustではカスタムテストフレームワークがサポートされているので、これを使ってカーネルの中でテスト関数を実行します。QEMUの外へとテストの結果を通知するため、QEMUと`bootimage`の様々な機能を使います。
+
+
+
+このブログの内容は [GitHub] 上で公開・開発されています。何か問題や質問などがあれば issue をたててください (訳注: リンクは原文(英語)のものになります)。また[こちら][at the bottom]にコメントを残すこともできます。この記事の完全なソースコードは[`post-04` ブランチ][post branch]にあります。
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[at the bottom]: #comments
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-04
+
+
+
+## この記事を読む前に
+
+この記事は、(古い版の)[単体テスト][_Unit Testing_]と[結合テスト][_Integration Tests_]の記事を置き換えるものです。この記事は、あなたが[最小のカーネル][_A Minimal Rust Kernel_]の記事を2019-04-27以降に読んだことを前提にしています。主に、あなたの`.cargo/config.toml`ファイルが[標準のターゲットを設定して][sets a default target]おり、[ランナー実行ファイルを定義している][defines a runner executable]ことが条件となります。
+
+
+
+(私達の`should_panic`テストのように)一つしかテスト関数を持たない結合テストでは、テストランナーは必ずしも必要というわけではありません。このような場合、テストランナーは完全に無効化してしまって、`_start`関数からテストを直接実行することができます。
+
+このためには、`Cargo.toml`でこのテストの`harness`フラグを無効化することがカギとなります。これは、結合テストにテストランナーが使われるかを定義しています。これが`false`に設定されると、標準のテストランナーと独自のテストランナーの両方が無効化され、通常の実行ファイルのように扱われるようになります。
+
+`should_panic`テストの`harness`フラグを無効化してみましょう:
+
+```toml
+# in Cargo.toml
+
+[[test]]
+name = "should_panic"
+harness = false
+```
+
+これで、テストランナーに関係するコードを取り除いて、`should_panic`テストを大幅に簡略化することができます。結果として以下のようになります:
+
+```rust
+// in tests/should_panic.rs
+
+#![no_std]
+#![no_main]
+
+use core::panic::PanicInfo;
+use blog_os::{exit_qemu, serial_print, serial_println, QemuExitCode};
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ should_fail();
+ serial_println!("[test did not panic]");
+ exit_qemu(QemuExitCode::Failed);
+ loop{}
+}
+
+fn should_fail() {
+ serial_print!("should_panic::should_fail...\t");
+ assert_eq!(0, 1);
+}
+
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ serial_println!("[ok]");
+ exit_qemu(QemuExitCode::Success);
+ loop {}
+}
+```
+
+`should_fail`関数を`_start`関数から直接呼び出して、もしリターンしたら失敗の終了コードで終了するようにしました。今`cargo test --test should_panic`を実行しても、以前と全く同じように振る舞います。
+
+`should_panic`なテストを作るとき以外にも`harness`属性は有用なことがあります。例えば、それぞれのテスト関数が副作用を持っており、指定された順番で実行されないといけないときなどです。
+
+## まとめ
+
+テストは、ある要素が望み通りの振る舞いをしていることを保証するのにとても便利なテクニックです。バグが存在しないことを証明することはできないとはいえ、バグを発見したり、特にリグレッションを防ぐのに便利な方法であることは間違いありません。
+
+この記事では、私達のRust製カーネルでテストフレームワークを組み立てる方法を説明しました。Rustの独自テストフレームワーク機能を使って、私達のベアメタル環境における、シンプルな`#[test_case]`属性のサポートを実装しました。私達のテストランナーは、QEMUの`isa-debug-exit`デバイスを使うことで、QEMUをテスト実行後に終了し、テストステータスを報告することができます。エラーメッセージを、VGAバッファの代わりにコンソールに出力するために、シリアルポートの単純なドライバを作りました。
+
+`println`マクロのテストをいくつか作った後、記事の後半では結合テストについて見ました。結合テストは`tests`ディレクトリに置かれ、完全に独立した実行ファイルとして扱われることを学びました。結合テストから`exit_qemu`関数と`serial_println`マクロにアクセスできるようにするために、コードのほとんどをライブラリに移し、すべての実行ファイルと結合テストがインポートできるようにしました。結合テストはそれぞれ独自の環境で実行されるため、ハードウェアとの相互作用や、パニックするべきテストを作るといったことが可能になります。
+
+QEMU内で現実に近い環境で実行できるテストフレームワークを手に入れました。今後の記事でより多くのテストを作っていくことで、カーネルがより複雑になってもメンテナンスし続けられるでしょう。
+
+## 次は?
+
+次の記事では、**CPU例外**を見ていきます。この例外というのは、CPUによってなにか「不法行為」――例えば、ゼロ除算やマップされていないメモリページへのアクセス(いわゆる「ページフォルト」)――が行われたときに投げられます。これらの例外を捕捉してテストできるようにしておくことは、将来エラーをデバッグするときに非常に重要です。例外の処理はまた、キーボードをサポートするのに必要になる、ハードウェア割り込みの処理に非常に似てもいます。
diff --git a/blog/content/edition-2/posts/04-testing/index.md b/blog/content/edition-2/posts/04-testing/index.md
index 15cb1e5b..3b0e501c 100644
--- a/blog/content/edition-2/posts/04-testing/index.md
+++ b/blog/content/edition-2/posts/04-testing/index.md
@@ -34,7 +34,7 @@ This post replaces the (now deprecated) [_Unit Testing_] and [_Integration Tests
Rust has a [built-in test framework] that is capable of running unit tests without the need to set anything up. Just create a function that checks some results through assertions and add the `#[test]` attribute to the function header. Then `cargo test` will automatically find and execute all test functions of your crate.
-[built-in test framework]: https://doc.rust-lang.org/book/second-edition/ch11-00-testing.html
+[built-in test framework]: https://doc.rust-lang.org/book/ch11-00-testing.html
Unfortunately it's a bit more complicated for `no_std` applications such as our kernel. The problem is that Rust's test framework implicitly uses the built-in [`test`] library, which depends on the standard library. This means that we can't use the default test framework for our `#[no_std]` kernel.
@@ -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
@@ -836,7 +836,7 @@ At this point, `cargo run` and `cargo test` should work again. Of course, `cargo
### Completing the Integration Test
-Like our `src/main.rs`, our `tests/basic_boot.rs` executable can import types from our new library. This allows us to import the missing components to complete our test.
+Like our `src/main.rs`, our `tests/basic_boot.rs` executable can import types from our new library. This allows us to import the missing components to complete our test:
```rust
// in tests/basic_boot.rs
@@ -849,7 +849,7 @@ fn panic(info: &PanicInfo) -> ! {
}
```
-Instead of reimplementing the test runner, we use the `test_runner` function from our library. For our `panic` handler, we call the `blog_os::test_panic_handler` function like we did in our `main.rs`.
+Instead of reimplementing the test runner, we use the `test_runner` function from our library by changing the `#![test_runner(crate::test_runner)]` attribute to `#![test_runner(blog_os::test_runner)]`. We then don't need the `test_runner` stub function in `basic_boot.rs` anymore, so we can remove it. For our `panic` handler, we call the `blog_os::test_panic_handler` function like we did in our `main.rs`.
Now `cargo test` exits normally again. When you run it, you see that it builds and runs the tests for our `lib.rs`, `main.rs`, and `basic_boot.rs` separately after each other. For the `main.rs` and the `basic_boot` integration test, it reports "Running 0 tests" since these files don't have any functions annotated with `#[test_case]`.
diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.fa.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.fa.md
new file mode 100644
index 00000000..85480d19
--- /dev/null
+++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.fa.md
@@ -0,0 +1,472 @@
++++
+title = "استثناهای پردازنده"
+weight = 5
+path = "fa/cpu-exceptions"
+date = 2018-06-17
+
+[extra]
+chapter = "Interrupts"
+# Please update this when updating the translation
+translation_based_on_commit = "a081faf3cced9aeb0521052ba91b74a1c408dcff"
+# GitHub usernames of the people that translated this post
+translators = ["hamidrezakp", "MHBahrampour"]
+rtl = true
++++
+
+استثناهای پردازنده در موقعیت های مختلف دارای خطا رخ می دهد ، به عنوان مثال هنگام دسترسی به آدرس حافظه نامعتبر یا تقسیم بر صفر. برای واکنش به آنها ، باید یک _جدول توصیف کننده وقفه_ تنظیم کنیم که توابع کنترل کننده را فراهم کند. در انتهای این پست ، هسته ما قادر به گرفتن [استثناهای breakpoint] و ادامه اجرای طبیعی پس از آن خواهد بود.
+
+[استثناهای breakpoint]: https://wiki.osdev.org/Exceptions#Breakpoint
+
+
+
+این بلاگ بصورت آزاد روی [گیتهاب] توسعه داده شده است. اگر مشکل یا سوالی دارید، لطفاً آنجا یک ایشو باز کنید. همچنین میتوانید [در زیر] این پست کامنت بگذارید. سورس کد کامل این پست را میتوانید در بِرَنچ [`post-05`][post branch] پیدا کنید.
+
+[گیتهاب]: https://github.com/phil-opp/blog_os
+[در زیر]: #comments
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-05
+
+
+
+## بررسی اجمالی
+یک استثنا نشان می دهد که مشکلی در دستورالعمل فعلی وجود دارد. به عنوان مثال ، اگر دستورالعمل فعلی بخواهد تقسیم بر 0 کند ، پردازنده یک استثنا صادر می کند. وقتی یک استثنا اتفاق می افتد ، پردازنده کار فعلی خود را رها کرده و بسته به نوع استثنا ، بلافاصله یک تابع خاص کنترل کننده استثنا را فراخوانی می کند.
+
+در x86 حدود 20 نوع مختلف استثنا پردازنده وجود دارد. مهمترین آنها در زیر آمده اند:
+
+- **خطای صفحه**: خطای صفحه در دسترسی غیرقانونی به حافظه رخ می دهد. به عنوان مثال ، اگر دستورالعمل فعلی بخواهد از یک صفحه نگاشت نشده بخواند یا بخواهد در یک صفحه فقط خواندنی بنویسد.
+- **کد نامعتبر**: این استثنا وقتی رخ می دهد که دستورالعمل فعلی نامعتبر است ، به عنوان مثال وقتی می خواهیم از [دستورالعمل های SSE] جدیدتر بر روی یک پردازنده قدیمی استفاده کنیم که آنها را پشتیبانی نمی کند.
+- **خطای محافظت عمومی**: این استثنا دارای بیشترین دامنه علل است. این مورد در انواع مختلف نقض دسترسی مانند تلاش برای اجرای یک دستورالعمل ممتاز در کد سطح کاربر یا نوشتن فیلدهای رزرو شده در ثبات های پیکربندی رخ می دهد.
+- **خطای دوگانه**: هنگامی که یک استثنا رخ می دهد ، پردازنده سعی می کند تابع کنترل کننده مربوطه را اجرا کند. اگر یک استثنا دیگر رخ دهد _هنگام فراخوانی تابع کنترل کننده استثنا_ ، پردازنده یک استثنای خطای دوگانه ایجاد می کند. این استثنا همچنین زمانی اتفاق می افتد که هیچ تابع کنترل کننده ای برای یک استثنا ثبت نشده باشد.
+- **خطای سهگانه**: اگر در حالی که پردازنده سعی می کند تابع کنترل کننده خطای دوگانه را فراخوانی کند استثنایی رخ دهد ، این یک خطای سهگانه است. ما نمی توانیم یک خطای سه گانه را بگیریم یا آن را کنترل کنیم. بیشتر پردازنده ها ریست کردن خود و راه اندازی مجدد سیستم عامل واکنش نشان می دهند.
+
+[دستورالعمل های SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
+
+برای مشاهده لیست کامل استثناها ، [ویکی OSDev][exceptions] را بررسی کنید.
+
+[exceptions]: https://wiki.osdev.org/Exceptions
+
+### جدول توصیف کننده وقفه
+برای گرفتن و رسیدگی به استثناها ، باید اصطلاحاً _جدول توصیفگر وقفه_ (IDT) را تنظیم کنیم. در این جدول می توانیم برای هر استثنا پردازنده یک عملکرد تابع کننده مشخص کنیم. سخت افزار به طور مستقیم از این جدول استفاده می کند ، بنابراین باید از یک قالب از پیش تعریف شده پیروی کنیم. هر ورودی جدول باید ساختار 16 بایتی زیر را داشته باشد:
+
+Type| Name | Description
+----|--------------------------|-----------------------------------
+u16 | Function Pointer [0:15] | The lower bits of the pointer to the handler function.
+u16 | GDT selector | Selector of a code segment in the [global descriptor table].
+u16 | Options | (see below)
+u16 | Function Pointer [16:31] | The middle bits of the pointer to the handler function.
+u32 | Function Pointer [32:63] | The remaining bits of the pointer to the handler function.
+u32 | Reserved |
+
+[global descriptor table]: https://en.wikipedia.org/wiki/Global_Descriptor_Table
+
+قسمت گزینه ها (Options) دارای قالب زیر است:
+
+Bits | Name | Description
+------|-----------------------------------|-----------------------------------
+0-2 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called.
+3-7 | Reserved |
+8 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called.
+9-11 | must be one |
+12 | must be zero |
+13‑14 | Descriptor Privilege Level (DPL) | The minimal privilege level required for calling this handler.
+15 | Present |
+
+هر استثنا دارای یک اندیس از پیش تعریف شده در IDT است. به عنوان مثال استثنا کد نامعتبر دارای اندیس 6 و استثنا خطای صفحه دارای اندیس 14 است. بنابراین ، سخت افزار می تواند به طور خودکار عنصر مربوطه را برای هر استثنا بارگذاری کند. [جدول استثناها][exceptions] در ویکی OSDev ، اندیس های IDT کلیه استثناها را در ستون “Vector nr.” نشان داده است.
+
+هنگامی که یک استثنا رخ می دهد ، پردازنده تقریباً موارد زیر را انجام می دهد:
+
+1. برخی از ثباتها را به پشته وارد میکند، از جمله اشاره گر دستورالعمل و ثبات [RFLAGS]. (بعداً در این پست از این مقادیر استفاده خواهیم کرد.)
+2. عنصر مربوط به آن (استثنا) را از جدول توصیف کننده وقفه (IDT) میخواند. به عنوان مثال ، پردازنده هنگام رخ دادن خطای صفحه ، عنصر چهاردهم را می خواند.
+3. وجود عنصر را بررسی میکند. اگر اینگونه نباشد یک خطای دوگانه ایجاد میکند.
+4. اگر عنصر یک گیت وقفه است (بیت 40 تنظیم نشده است) وقفه های سخت افزاری را غیرفعال میکند.
+5. انتخابگر مشخص شده [GDT] را در سگمنت CS بارگذاری میکند.
+6. به تابع کنترل کننده مشخص شده میرود.
+
+[RFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register
+[GDT]: https://en.wikipedia.org/wiki/Global_Descriptor_Table
+
+در حال حاضر نگران مراحل 4 و 5 نباشید ، ما در مورد جدول توصیف کننده گلوبال و وقفه های سخت افزاری در پست های بعدی خواهیم آموخت.
+
+## یک نوع IDT
+
+به جای ایجاد نوع IDT خود ، از [ساختمان `InterruptDescriptorTable`] کرت `x86_64` استفاده خواهیم کرد که به این شکل است:
+
+[ساختمان `InterruptDescriptorTable`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
+
+``` rust
+#[repr(C)]
+pub struct InterruptDescriptorTable {
+ pub divide_by_zero: Entry,
+ pub debug: Entry,
+ pub non_maskable_interrupt: Entry,
+ pub breakpoint: Entry,
+ pub overflow: Entry,
+ pub bound_range_exceeded: Entry,
+ pub invalid_opcode: Entry,
+ pub device_not_available: Entry,
+ pub double_fault: Entry,
+ pub invalid_tss: Entry,
+ pub segment_not_present: Entry,
+ pub stack_segment_fault: Entry,
+ pub general_protection_fault: Entry,
+ pub page_fault: Entry,
+ pub x87_floating_point: Entry,
+ pub alignment_check: Entry,
+ pub machine_check: Entry,
+ pub simd_floating_point: Entry,
+ pub virtualization: Entry,
+ pub security_exception: Entry,
+ // some fields omitted
+}
+```
+
+فیلدها از نوع [` src/main.rs:53:1
+ |
+53 | / extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) {
+54 | | println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
+55 | | }
+ | |_^
+ |
+ = help: add #![feature(abi_x86_interrupt)] to the crate attributes to enable
+```
+
+این خطا به این دلیل رخ می دهد که قرارداد فراخوانی `x86-interrupt` هنوز ناپایدار است. به هر حال برای استفاده از آن ، باید صریحاً آن را با اضافه کردن `#![feature(abi_x86_interrupt)]` در بالای `lib.rs` فعال کنیم.
+
+### بارگیری IDT
+برای اینکه پردازنده از جدول توصیف کننده وقفه جدید ما استفاده کند ، باید آن را با استفاده از دستورالعمل [`lidt`] بارگیری کنیم. ساختمان `InterruptDescriptorTable` از کرت ` x86_64` متد [`load`][InterruptDescriptorTable::load] را برای این کار فراهم می کند. بیایید سعی کنیم از آن استفاده کنیم:
+
+[`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt
+[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
+
+```rust
+// in src/interrupts.rs
+
+pub fn init_idt() {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt.load();
+}
+```
+
+اکنون هنگامی که می خواهیم آن را کامپایل کنیم ، خطای زیر رخ می دهد:
+
+```
+error: `idt` does not live long enough
+ --> src/interrupts/mod.rs:43:5
+ |
+43 | idt.load();
+ | ^^^ does not live long enough
+44 | }
+ | - borrowed value only lives until here
+ |
+ = note: borrowed value must be valid for the static lifetime...
+```
+
+پس متد `load` انتظار دریافت یک `static self'&` را دارد، این مرجعی است که برای تمام مدت زمان اجرای برنامه معتبر است. دلیل این امر این است که پردازنده در هر وقفه به این جدول دسترسی پیدا می کند تا زمانی که IDT دیگری بارگیری کنیم. بنابراین استفاده از طول عمر کوتاه تر از `static'` می تواند منجر به باگ های استفاده-بعد-از-آزادسازی شود.
+
+در واقع ، این دقیقاً همان چیزی است که در اینجا اتفاق می افتد. `idt` ما روی پشته ایجاد می شود ، بنابراین فقط در داخل تابع `init` معتبر است. پس از آن حافظه پشته برای توابع دیگر مورد استفاده مجدد قرار می گیرد ، بنابراین پردازنده حافظه پشته تصادفی را به عنوان IDT تفسیر می کند. خوشبختانه ، متد `InterruptDescriptorTable::load` این نیاز به طول عمر را در تعریف تابع خود اجباری می کند، بنابراین کامپایلر راست قادر است از این مشکل احتمالی در زمان کامپایل جلوگیری کند.
+
+برای رفع این مشکل، باید `idt` را در مکانی ذخیره کنیم که طول عمر `static'` داشته باشد. برای رسیدن به این هدف می توانیم IDT را با استفاده از [`Box`] بر روی حافظه Heap ایجاد کنیم و سپس آن را به یک مرجع `static'` تبدیل کنیم، اما ما در حال نوشتن هسته سیستم عامل هستیم و بنابراین هنوز Heap نداریم.
+
+[`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html
+
+
+به عنوان یک گزینه دیگر، می توانیم IDT را به صورت `static` ذخیره کنیم:
+```rust
+static IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
+
+pub fn init_idt() {
+ IDT.breakpoint.set_handler_fn(breakpoint_handler);
+ IDT.load();
+}
+```
+
+با این وجود، یک مشکل وجود دارد: استاتیکها تغییرناپذیر هستند، پس نمی توانیم ورودی بریکپوینت را از تابع `init` تغییر دهیم. می توانیم این مشکل را با استفاده از [`static mut`] حل کنیم:
+
+[`static mut`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
+
+```rust
+static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
+
+pub fn init_idt() {
+ unsafe {
+ IDT.breakpoint.set_handler_fn(breakpoint_handler);
+ IDT.load();
+ }
+}
+```
+
+در این روش بدون خطا کامپایل می شود اما مشکلات دیگری به همراه دارد. `static mut` بسیار مستعد Data Race هستند، بنابراین در هر دسترسی به یک [بلوک `unsafe`] نیاز داریم.
+
+[بلوک `unsafe`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
+
+#### Lazy Statics به نجات ما میآیند
+خوشبختانه ماکرو `lazy_static` وجود دارد. ماکرو به جای ارزیابی یک `static` در زمان کامپایل ، مقداردهی اولیه آن را هنگام اولین ارجاع به آن انجام می دهد. بنابراین، می توانیم تقریباً همه کاری را در بلوک مقداردهی اولیه انجام دهیم و حتی قادر به خواندن مقادیر زمان اجرا هستیم.
+
+ما قبلاً کرت `lazy_static` را وارد کردیم وقتی [یک انتزاع برای بافر متن VGA ایجاد کردیم][vga text buffer lazy static]. بنابراین می توانیم مستقیماً از ماکرو `!lazy_static` برای ایجاد IDT استاتیک استفاده کنیم:
+
+[vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.md#lazy-statics
+
+```rust
+// in src/interrupts.rs
+
+use lazy_static::lazy_static;
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt
+ };
+}
+
+pub fn init_idt() {
+ IDT.load();
+}
+```
+
+توجه داشته باشید که چگونه این راه حل به هیچ بلوک `unsafe` نیاز ندارد. ماکرو `!lazy_static` از `unsafe` در پشت صحنه استفاده می کند ، اما در یک رابط امن به ما داده می شود.
+
+### اجرای آن
+
+آخرین مرحله برای کارکرد استثناها در هسته ما فراخوانی تابع `init_idt` از `main.rs` است. به جای فراخوانی مستقیم آن، یک تابع عمومی `init` را در `lib.rs` معرفی می کنیم:
+
+```rust
+// in src/lib.rs
+
+pub fn init() {
+ interrupts::init_idt();
+}
+```
+
+با استفاده از این تابع اکنون یک مکان اصلی برای روالهای اولیه داریم که می تواند بین توابع مختلف `start_` در `main.rs` ، `lib.rs` و تستهای یکپارچه به اشتراک گذاشته شود.
+
+اکنون می توانیم تابع `start_` در `main.rs` را به روز کنیم تا `init` را فراخوانی کرده و سپس یک استثنا بریکپوینت ایجاد کند:
+
+```rust
+// in src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ blog_os::init(); // new
+
+ // invoke a breakpoint exception
+ x86_64::instructions::interrupts::int3(); // new
+
+ // as before
+ #[cfg(test)]
+ test_main();
+
+ println!("It did not crash!");
+ loop {}
+}
+```
+
+اکنون هنگامی که آن را در QEMU اجرا می کنیم (با استفاده از `cargo run`) ، موارد زیر را مشاهده می کنیم:
+
+
+
+کار می کند! پردازنده با موفقیت تابع کنترل کننده بریکپوینت ما را فراخوانی می کند ، که پیام را چاپ می کند و سپس به تابع `start_` برمی گردد ، جایی که پیام `!It did not crash` چاپ شده است.
+
+می بینیم که قاب پشته وقفه، دستورالعمل و نشانگرهای پشته را در زمان وقوع استثنا به ما می گوید. این اطلاعات هنگام رفع اشکال استثناهای غیر منتظره بسیار مفید است.
+
+### افزودن یک تست
+
+بیایید یک تست ایجاد کنیم که از ادامه کار کد بالا اطمینان حاصل کند. ابتدا تابع `start_` را به روز می کنیم تا `init` را نیز فراخوانی کند:
+
+```rust
+// in src/lib.rs
+
+/// Entry point for `cargo test`
+#[cfg(test)]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ init(); // new
+ test_main();
+ loop {}
+}
+```
+
+بخاطر داشته باشید، این تابع `start_` هنگام اجرای`cargo test --lib` استفاده می شود، زیرا راست `lib.rs` را کاملاً مستقل از`main.rs` تست میکند. قبل از اجرای تستها باید برای راه اندازی IDT در اینجا `init` فراخوانی شود.
+
+اکنون می توانیم یک تست `test_breakpoint_exception` ایجاد کنیم:
+
+```rust
+// in src/interrupts.rs
+
+#[test_case]
+fn test_breakpoint_exception() {
+ // invoke a breakpoint exception
+ x86_64::instructions::interrupts::int3();
+}
+```
+
+این تست تابع `int3` را فراخوانی می کند تا یک استثنا بریکپوینت ایجاد کند. با بررسی اینکه اجرا پس از آن ادامه دارد ، تأیید می کنیم که کنترل کننده بریکپوینت ما به درستی کار می کند.
+
+شما می توانید این تست جدید را با اجرای `cargo test` (همه تستها) یا` cargo test --lib` (فقط تست های `lib.rs` و ماژول های آن) امتحان کنید. باید موارد زیر را در خروجی مشاهده کنید:
+
+```
+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.13.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
+[نسخه اول]: @/edition-1/_index.md
+
+## مرحله بعدی چیست؟
+ما اولین استثنای خود را با موفقیت گرفتیم و از آن بازگشتیم! گام بعدی اطمینان از این است که همه استثناها را می گیریم ، زیرا یک استثنا گرفته نشده باعث [خطای سهگانه] می شود که منجر به شروع مجدد سیستم می شود. پست بعدی توضیح می دهد که چگونه می توان با گرفتن صحیح [خطای دوگانه] از این امر جلوگیری کرد.
+
+[خطای سهگانه]: https://wiki.osdev.org/Triple_Fault
+[خطای دوگانه]: https://wiki.osdev.org/Double_Fault#Double_Fault
diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.md
index daf46fa0..5d68b59c 100644
--- a/blog/content/edition-2/posts/05-cpu-exceptions/index.md
+++ b/blog/content/edition-2/posts/05-cpu-exceptions/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`], 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`]: 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`]: 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?
diff --git a/blog/content/edition-2/posts/06-double-faults/index.fa.md b/blog/content/edition-2/posts/06-double-faults/index.fa.md
new file mode 100644
index 00000000..4ef866cd
--- /dev/null
+++ b/blog/content/edition-2/posts/06-double-faults/index.fa.md
@@ -0,0 +1,569 @@
++++
+title = "خطاهای دوگانه"
+weight = 6
+path = "fa/double-fault-exceptions"
+date = 2018-06-18
+
+[extra]
+chapter = "Interrupts"
+# Please update this when updating the translation
+translation_based_on_commit = "3ac829171218156c07ce9a27186fee58e3a5521e"
+# GitHub usernames of the people that translated this post
+translators = ["hamidrezakp", "MHBahrampour"]
+rtl = true
++++
+
+این پست به طور دقیق جزئیات استثنای خطای دوگانه (ترجمه: double fault exception) را بررسی میکند، این استثنا هنگامی رخ میدهد که CPU نتواند یک کنترل کننده استثنا را فراخوانی کند. با کنترل این استثنا، از بروز _خطاهای سه گانه_ (ترجمه: triple faults) کشنده که باعث ریست (کلمه: reset) شدن سیستم میشوند، جلوگیری میکنیم. برای جلوگیری از خطاهای سه گانه در همه موارد، ما همچنین یک _Interrupt Stack Table_ را تنظیم کردهایم تا خطاهای دوگانه را روی یک پشته هسته جداگانه بگیرد.
+
+
+
+این بلاگ بصورت آزاد روی [گیتهاب] توسعه داده شده است. اگر شما مشکل یا سوالی دارید، لطفاً آنجا یک ایشو باز کنید. شما همچنین میتوانید [در زیر] این پست کامنت بگذارید. منبع کد کامل این پست را میتوانید در بِرَنچ [`post-06`][post branch] پیدا کنید.
+
+[گیتهاب]: https://github.com/phil-opp/blog_os
+[در زیر]: #comments
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-06
+
+
+
+## خطای دوگانه چیست؟
+
+به عبارت ساده، خطای دوگانه یک استثنای به خصوص است و هنگامی رخ میدهد که CPU نتواند یک کنترل کننده استثنا را فراخوانی کند. به عنوان مثال، این اتفاق هنگامی رخ میدهد که یک page fault (ترجمه: خطای صفحه) رخ دهد اما هیچ کنترل کننده خطایی در [جدول توصیف کننده وقفه][IDT] (ترجمه: Interrupt Descriptor Table) ثبت نشده باشد. بنابراین به نوعی شبیه بلاکهای همه گیر در زبانهای برنامهنویسی با استثناها میباشد، به عنوان مثال `catch(...)` در ++C یا `catch(Exception e)` در جاوا و #C.
+
+[IDT]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-descriptor-table
+
+خطای دوگانه مانند یک استثنای عادی رفتار میکند. دارای شماره وکتور (کلمه: vector) `8` است و ما میتوانیم یک تابع طبیعی کنترل کننده برای آن در IDT تعریف کنیم. تهیه یک کنترل کننده خطای دوگانه بسیار مهم است، زیرا اگر یک خطای دوگانه کنترل نشود، یک خطای کشنده سه گانه رخ میدهد. خطاهای سه گانه قابل کشف نیستند و اکثر سخت افزارها با تنظیم مجدد سیستم واکنش نشان میدهند.
+
+### راهاندازی یک خطای دوگانه
+
+بیایید یک خطای دوگانه را با راهاندازی (ترجمه: triggering) یک استثنا برای آن ایجاد کنیم، ما هنوز یک تابع کنترل کننده تعریف نکردهایم:
+
+```rust
+// in src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ blog_os::init();
+
+ // trigger a page fault
+ unsafe {
+ *(0xdeadbeef as *mut u64) = 42;
+ };
+
+ // as before
+ #[cfg(test)]
+ test_main();
+
+ println!("It did not crash!");
+ loop {}
+}
+```
+
+برای نوشتن در آدرس نامعتبر `0xdeadbeef` از` unsafe` استفاده میکنیم. آدرس مجازی در جداول صفحه به آدرس فیزیکی مپ نمیشود، بنابراین خطای صفحه رخ میدهد. ما یک کنترل کننده خطای صفحه در [IDT] خود ثبت نکردهایم، بنابراین یک خطای دوگانه رخ میدهد.
+
+حال وقتی هسته را اجرا میکنیم، میبینیم که وارد یک حلقه بوت بیپایان میشود. دلایل حلقه بوت به شرح زیر است:
+
+۱. سیپییو سعی به نوشتن در `0xdeadbeef` دارد، که باعث خطای صفحه میشود.
+۲. سیپییو به ورودی مربوطه در IDT نگاه میکند و میبیند که هیچ تابع کنترل کنندهای مشخص نشده است. بنابراین، نمیتواند کنترل کننده خطای صفحه را فراخوانی کند و یک خطای دوگانه رخ میدهد.
+۳. سیپییو ورودی IDT کنترل کننده خطای دو گانه را بررسی میکند، اما این ورودی هم تابع کنترل کنندهای را مشخص نمیکند. بنابراین، یک خطای _سهگانه_ رخ میدهد.
+۴. خطای سه گانه کشنده است. QEMU مانند اکثر سخت افزارهای واقعی به آن واکنش نشان داده دستور ریست شدن سیستم را صادر میکند.
+
+بنابراین برای جلوگیری از این خطای سهگانه، باید یک تابع کنترل کننده برای خطاهای صفحه یا یک کنترل کننده خطای دوگانه ارائه دهیم. ما میخواهیم در همه موارد از خطاهای سه گانه جلوگیری کنیم، بنابراین بیایید با یک کنترل کننده خطای دوگانه شروع کنیم که برای همه انواع استثنا بدون کنترل فراخوانی میشود.
+
+## کنترل کننده خطای دوگانه
+
+خطای دوگانه یک استثنا عادی با کد خطا است، بنابراین میتوانیم یک تابع کنترل کننده مشابه کنترل کننده نقطه شکست (ترجمه: breakpoint) تعیین کنیم:
+
+```rust
+// in src/interrupts.rs
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt.double_fault.set_handler_fn(double_fault_handler); // new
+ idt
+ };
+}
+
+// new
+extern "x86-interrupt" fn double_fault_handler(
+ stack_frame: &mut InterruptStackFrame, _error_code: u64) -> !
+{
+ panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
+}
+```
+
+کنترل کننده ما یک پیام خطای کوتاه چاپ میکند و قاب پشته استثنا را تخلیه میکند. کد خطای کنترل کننده خطای دوگانه همیشه صفر است، بنابراین دلیلی برای چاپ آن وجود ندارد. یک تفاوت در کنترل کننده نقطه شکست این است که کنترل کننده خطای دوگانه [_diverging_] \(ترجمه: واگرا) است. چون معماری `x86_64` امکان بازگشت از یک استثنا خطای دوگانه را ندارد.
+
+[_diverging_]: https://doc.rust-lang.org/stable/rust-by-example/fn/diverging.html
+
+حال وقتی هسته را اجرا میکنیم، باید ببینیم که کنترل کننده خطای دوگانه فراخوانی میشود:
+
+
+
+کار کرد! آنچه این بار اتفاق میافتد بصورت زیر است:
+
+۱. سیپییو سعی به نوشتن در `0xdeadbeef` دارد، که باعث خطای صفحه میشود.
+۲. مانند قبل، سیپییو به ورودی مربوطه در IDT نگاه میکند و میبیند که هیچ تابع کنترل کنندهای مشخص نشده است. بنابراین، یک خطای دوگانه رخ میدهد.
+۳. سیپییو به کنترل کننده خطای دوگانه - که اکنون وجود دارد - میرود.
+
+خطای سه گانه (و حلقه بوت) دیگر رخ نمیدهد، زیرا اکنون CPU میتواند کنترل کننده خطای دوگانه را فراخوانی کند.
+
+این کاملاً ساده بود! پس چرا ما برای این موضوع به یک پست کامل نیاز داریم؟ خب، ما اکنون قادر به ردیابی _اکثر_ خطاهای دوگانه هستیم، اما مواردی وجود دارد که رویکرد فعلی ما کافی نیست.
+
+## علل رخ داد خطای دوگانه
+
+قبل از بررسی موارد خاص، باید علل دقیق خطاهای دوگانه را بدانیم. در بالا، ما از یک تعریف کاملا مبهم استفاده کردیم:
+
+> خطای دوگانه یک استثنای به خصوص است و هنگامی رخ میدهد که CPU نتواند یک کنترل کننده استثنا را فراخوانی کند.
+
+عبارت _“fails to invoke”_ دقیقا چه معنایی دارد؟ کنترل کننده وجود ندارد؟ کنترل کننده [خارج شده][swapped out] \(منظور این است که آیا صفحه مربوط به کنترل کننده از حافظه خارج شده)؟ و اگر کنترل کننده خودش باعث رخ دادن یک استثناها شود، چه اتفاقی رخ میدهد؟
+
+[swapped out]: http://pages.cs.wisc.edu/~remzi/OSTEP/vm-beyondphys.pdf
+
+به عنوان مثال، چه اتفاقی میافتد اگر:
+
+۱. یک استثنای نقطه شکست رخ میدهد، آیا تابع کنترل کننده مربوطه خارج شده است؟
+۲. یک خطای صفحه رخ میدهد، آیا کنترل کننده خطای صفحه خارج شده است؟
+۳. کنترل کنندهی «تقسیم بر صفر» باعث رخ دادن یک استثنای نقطه شکست میشود، آیا کنترل کننده نقطه شکست خارج شده است؟
+۴. هسته ما پشته خود را سرریز میکند و آیا _صفحه محافظ_ (ترجمه: guard page) ضربه میخورد؟
+
+خوشبختانه، کتابچه راهنمای AMD64 ([PDF][AMD64 manual]) یک تعریف دقیق دارد (در بخش 8.2.9). مطابق آن، "یک استثنای خطای دوگانه _میتواند_ زمانی اتفاق بیفتد که یک استثنا دوم هنگام کار با یک کنترل کننده استثنا قبلی (اول) رخ دهد". _"می تواند"_ مهم است: فقط ترکیبی بسیار خاص از استثناها منجر به خطای دوگانه میشود. این ترکیبات عبارتند از:
+
+استثنای اول | استثنای دوم
+----------------|-----------------
+[Divide-by-zero], [Invalid TSS], [Segment Not Present], [Stack-Segment Fault], [General Protection Fault] | [Invalid TSS], [Segment Not Present], [Stack-Segment Fault], [General Protection Fault]
+[Page Fault] | [Page Fault], [Invalid TSS], [Segment Not Present], [Stack-Segment Fault], [General Protection Fault]
+
+[Divide-by-zero]: https://wiki.osdev.org/Exceptions#Divide-by-zero_Error
+[Invalid TSS]: https://wiki.osdev.org/Exceptions#Invalid_TSS
+[Segment Not Present]: https://wiki.osdev.org/Exceptions#Segment_Not_Present
+[Stack-Segment Fault]: https://wiki.osdev.org/Exceptions#Stack-Segment_Fault
+[General Protection Fault]: https://wiki.osdev.org/Exceptions#General_Protection_Fault
+[Page Fault]: https://wiki.osdev.org/Exceptions#Page_Fault
+
+
+[AMD64 manual]: https://www.amd.com/system/files/TechDocs/24593.pdf
+
+بنابراین به عنوان مثال، یک خطای تقسیم بر صفر (ترجمه: Divide-by-zero) و به دنبال آن خطای صفحه (ترجمه: Page Fault)، خوب است (کنترل کننده خطای صفحه فراخوانی میشود)، اما خطای تقسیم بر صفر و به دنبال آن یک خطای محافظت عمومی (ترجمه: General Protection) منجر به خطای دوگانه می شود.
+
+با کمک این جدول میتوانیم به سه مورد اول از سوالهای بالا پاسخ دهیم:
+
+۱. اگر یک استثنای نقطه شکست اتفاق بیفتد و تابع مربوط به کنترل کننده آن خارج شده باشد، یک _خطای صفحه_ رخ میدهد و _کنترل کننده خطای صفحه_ فراخوانی میشود.
+۲. اگر خطای صفحه رخ دهد و کنترل کننده خطای صفحه خارج شده باشد، یک _خطای دوگانه_ رخ میدهد و _کنترل کننده خطای دوگانه_ فراخوانی میشود.
+۳. اگر یک کنترل کننده تقسیم بر صفر باعث استثنای نقطه شکست شود، CPU سعی میکند تا کنترل کننده نقطه شکست را فراخوانی کند. اگر کنترل کننده نقطه شکست خارج شده باشد، یک _خطای صفحه_ رخ میدهد و _کنترل کننده خطای صفحه_ فراخوانی میشود.
+
+در حقیقت، حتی موارد استثنا بدون تابع کنترل کننده در IDT نیز از این طرح پیروی میکند: وقتی استثنا رخ میدهد، CPU سعی میکند ورودی IDT مربوطه را بخواند. از آنجا که ورودی 0 است، که یک ورودی IDT معتبر نیست، یک _خطای محافظت کلی_ رخ میدهد. ما یک تابع کنترل کننده برای خطای محافظت عمومی نیز تعریف نکردیم، بنابراین یک خطای محافظت عمومی دیگر رخ میدهد. طبق جدول، این منجر به یک خطای دوگانه میشود.
+
+### سرریزِ پشتهی هسته
+
+بیایید به سوال چهارم نگاه کنیم:
+
+> چه اتفاقی میافتد اگر هسته ما پشته خود را سرریز کند و صفحه محافظ ضربه بخورد؟
+
+یک صفحه محافظ یک صفحه حافظه ویژه در پایین پشته است که امکان تشخیصِ سرریز پشته را فراهم میکند. صفحه به هیچ قاب فیزیکی مپ نشده است، بنابراین دسترسی به آن باعث خطای صفحه میشود به جای اینکه بی صدا حافظه دیگر را خراب کند. بوتلودر یک صفحه محافظ برای پشته هسته ما تنظیم میکند، بنابراین سرریز پشته باعث _خطای صفحه_ میشود.
+
+هنگامی که خطای صفحه رخ میدهد، پردازنده به دنبال کنترل کننده خطای صفحه در IDT است و سعی میکند تا [قاب پشته وقفه][interrupt stack frame] را به پشته پوش میکند. با این حال، اشارهگر پشته فعلی هنوز به صفحه محافظی اشاره میکند که موجود نیست. بنابراین، خطای صفحه دوم رخ میدهد، که باعث خطای دوگانه میشود (مطابق جدول فوق).
+
+[interrupt stack frame]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-stack-frame
+
+بنابراین حالا پردازنده سعی میکند _کنترل کننده خطای دوگانه_ را فراخوانی کند. با این حال، هنگام رخ دادن خطای دوگانه پردازنده سعی میکند تا قاب پشته استثنا را نیز پوش کند. اشارهگر پشته هنوز به سمت صفحه محافظ است، بنابراین یک خطای صفحه _سوم_ رخ میهد که باعث یک _خطای سهگانه_ و راه اندازی مجدد سیستم میشود. بنابراین کنترل کننده خطای دوگانه فعلی ما نمیتواند از خطای سهگانه در این مورد جلوگیری کند.
+
+بیایید خودمان امتحان کنیم! ما میتوانیم با فراخوانی تابعی که به طور بیوقفه بازگشت مییابد، به راحتی سرریز پشته هسته را تحریک کنیم (باعث رخ دادن یک سرریز پشته هسته شویم):
+
+```rust
+// in src/main.rs
+
+#[no_mangle] // don't mangle the name of this function
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ blog_os::init();
+
+ fn stack_overflow() {
+ stack_overflow(); // for each recursion, the return address is pushed
+ }
+
+ // trigger a stack overflow
+ stack_overflow();
+
+ […] // test_main(), println(…), and loop {}
+}
+```
+
+وقتی این کد را در QEMU امتحان میکنیم، میبینیم که سیستم دوباره وارد یک حلقه بوت میشود.
+
+بنابراین چگونه میتوانیم از بروز این مشکل جلوگیری کنیم؟ ما نمیتوانیم پوش کردن قاب پشته استثنا را حذف کنیم، زیرا پردازنده خود این کار را انجام میدهد. بنابراین ما باید به نحوی اطمینان حاصل کنیم که وقتی یک استثنای خطای دوگانه رخ میدهد، پشته همیشه معتبر است. خوشبختانه، معماری x86_64 راه حلی برای این مشکل دارد.
+
+## تعویض پشتهها
+
+معماری x86_64 قادر است در صورت وقوع یک استثنا به یک پشته از پیش تعریف شده و شناخته شده تعویض شود. این تعویض در سطح سخت افزاری اتفاق میافتد، بنابراین میتوان آن را قبل از اینکه پردازنده قاب پشته استثنا را پوش کند، انجام داد.
+
+مکانیزم تعویض به عنوان _Interrupt Stack Table_ (IST) پیادهسازی میشود. IST جدولی است با 7 اشارهگر برای دسته های معروف. در شبه کد شبیه Rust:
+
+```rust
+struct InterruptStackTable {
+ stack_pointers: [Option; 7],
+}
+```
+
+برای هر کنترل کننده استثنا، میتوانیم یک پشته از IST از طریق فیلد `stack_pointers` مربوط به [IDT entry] انتخاب کنیم. به عنوان مثال، ما میتوانیم از اولین پشته در IST برای کنترل کننده خطای دوگانه استفاده کنیم. هرگاه خطای دوگانه رخ دهد، پردازنده به طور خودکار به این پشته تغییر میکند. این تعویض قبل از پوش کردن هر چیزی اتفاق میافتد، بنابراین از خطای سهگانه جلوگیری میکند.
+
+[IDT entry]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-descriptor-table
+
+### IST و TSS
+
+جدول پشته وقفه (ترجمه: Interrupt Stack Table: IST) بخشی از یک ساختار قدیمی است که به آن _[سگمنت وضعیت پروسه]_ \(Task State Segment: TSS) گفته میشود. TSS برای نگهداری اطلاعات مختلف (به عنوان مثال وضعیت ثبات پردازنده) در مورد یک پروسه در حالت 32 بیتی استفاده میشد و به عنوان مثال برای [تعویض سختافزاری context] \(ترجمه: hardware context switching) استفاده میشد. با این حال، تعویض سختافزاری context دیگر در حالت 64 بیتی پشتیبانی نمیشود و قالب TSS کاملاً تغییر کرده است.
+
+[سگمنت وضعیت پروسه]: https://en.wikipedia.org/wiki/Task_state_segment
+[تعویض سختافزاری context]: https://wiki.osdev.org/Context_Switching#Hardware_Context_Switching
+
+در x86_64، دیگر TSS هیچ اطلاعات خاصی برای پرسهها ندارد. در عوض، دو جدول پشته را در خود جای داده است (IST یکی از آنهاست). تنها فیلد مشترک بین TSS 32-bit و TSS 64-bit اشارهگر به [بیتمپ مجوزهای پورت I/O] است.
+
+[بیتمپ مجوزهای پورت I/O]: https://en.wikipedia.org/wiki/Task_state_segment#I.2FO_port_permissions
+
+فرمت TSS 64-bit مانند زیر است:
+
+فیلد | نوع
+------ | ----------------
+(reserved) | `u32`
+Privilege Stack Table | `[u64; 3]`
+(reserved) | `u64`
+Interrupt Stack Table | `[u64; 7]`
+(reserved) | `u64`
+(reserved) | `u16`
+I/O Map Base Address | `u16`
+
+وقتی سطح ممتاز تغییر میکند، پردازنده از _Privilege Stack Table_ استفاده میکند. به عنوان مثال، اگر یک استثنا در حالی که CPU در حالت کاربر است (سطح ممتاز 3) رخ دهد، CPU معمولاً قبل از فراخوانی کنترل کننده استثنا، به حالت هسته تغییر میکند (سطح امتیاز 0). در این حالت، CPU به پشته صفرم در جدول پشته ممتاز تغییر وضعیت می دهد (از آنجا که 0، سطح ممتاز هدف است). ما هنوز هیچ برنامه حالت کاربر نداریم، بنابراین اکنون این جدول را نادیده میگیریم.
+
+### ایجاد یک TSS
+
+بیایید یک TSS جدید ایجاد کنیم که شامل یک پشته خطای دوگانه جداگانه در جدول پشته وقفه خود باشد. برای این منظور ما به یک ساختار TSS نیاز داریم. خوشبختانه کریت `x86_64` از قبل حاوی [ساختار `TaskStateSegment`] است که میتوانیم از آن استفاده کنیم.
+
+[ساختار `TaskStateSegment`]: https://docs.rs/x86_64/0.13.2/x86_64/structures/tss/struct.TaskStateSegment.html
+
+ما TSS را در یک ماژول جدید به نام `gdt` ایجاد میکنیم (نام این ماژول بعداً برایتان معنا پیدا میکند):
+
+```rust
+// in src/lib.rs
+
+pub mod gdt;
+
+// in src/gdt.rs
+
+use x86_64::VirtAddr;
+use x86_64::structures::tss::TaskStateSegment;
+use lazy_static::lazy_static;
+
+pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
+
+lazy_static! {
+ static ref TSS: TaskStateSegment = {
+ let mut tss = TaskStateSegment::new();
+ tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
+ const STACK_SIZE: usize = 4096 * 5;
+ static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
+
+ let stack_start = VirtAddr::from_ptr(unsafe { &STACK });
+ let stack_end = stack_start + STACK_SIZE;
+ stack_end
+ };
+ tss
+ };
+}
+```
+
+ما از `lazy_static` استفاده میکنیم زیرا ارزیابی کننده ثابت راست هنوز آنقدر توانمند نیست که بتواند این مقداردهی اولیه را در زمان کامپایل انجام دهد. ما تعریف میکنیم که ورودی صفرم IST پشته خطای دوگانه است (هر اندیس دیگری از IST نیز قابل استفاده است). سپس آدرس بالای یک پشته خطای دوگانه را در ورودی صفرم مینویسیم. ما آدرس بالایی را مینویسیم زیرا پشتههای x86 به سمت پایین رشد میکنند، یعنی از آدرسهای بالا به آدرسهای پایین میآیند.
+
+ما هنوز مدیریت حافظه را پیاده سازی نکردهایم، بنابراین روش مناسبی برای اختصاص پشته جدید نداریم. در عوض، فعلاً از یک آرایه `static mut` به عنوان حافظه پشته استفاده میکنیم. `unsafe` لازم است زیرا هنگام دسترسی به استاتیکهای تغییرپذیر (ترجمه: mutable)، کامپایلر نمیتواند عدم وجود رقابت بین داده ها را تضمین کند. مهم است که یک `static mut` باشد و نه یک استاتیک تغییرناپذیر (ترجمه: immutable)، زیرا در غیر این صورت bootloader آن را به یک صفحه فقط خواندنی نگاشت میکند. ما در پست بعدی این را با یک تخصیص پشته مناسب جایگزین خواهیم کرد، سپس `unsafe` دیگر در اینجا مورد نیاز نخواهد بود.
+
+توجه داشته باشید که این پشته خطای دوگانه فاقد صفحه محافظ در برابر سرریز پشته است. یعنی ما نباید هیچ کاری که اضافه شدن ایتمی در پشته شود را انجام دهیم زیرا سرریز پشته ممکن است حافظه زیر پشته را خراب کند.
+
+#### بارگذاری TSS
+
+اکنون که TSS جدیدی ایجاد کردیم، به روشی نیاز داریم که به CPU بگوییم باید از آن استفاده کند. متأسفانه این کمی دشوار است، زیرا TSS به دلایل تاریخی از سیستم سگمنتبندی (ترجمه: segmentation) استفاده میکند. به جای بارگذاری مستقیم جدول، باید توصیفگر سگمنت جدیدی را به [جدول توصیفگر سراسری] \(Global Descriptor Table: GDT) اضافه کنیم. سپس میتوانیم TSS خود را با فراخوانی [دستور `ltr`] با اندیس GDT مربوطه بارگذاری کنیم. (دلیل اینکه نام ماژول را `gdt` گذاشتیم نیز همین بود).
+
+[جدول توصیفگر سراسری]: https://web.archive.org/web/20190217233448/https://www.flingos.co.uk/docs/reference/Global-Descriptor-Table/
+[دستور `ltr`]: https://www.felixcloutier.com/x86/ltr
+
+### جدول توصیفگر سراسری
+
+جدول توصیفگر سراسری (GDT) یک یادگاری است که قبل از اینکه صفحهبندی به صورت استاندارد تبدیل شود، برای [تقسیمبندی حافظه] استفاده میشد. این مورد همچنان در حالت 64 بیتی برای موارد مختلف مانند پیکربندی هسته/کاربر یا بارگذاری TSS مورد نیاز است.
+
+[تقسیمبندی حافظه]: https://en.wikipedia.org/wiki/X86_memory_segmentation
+
+جدول توصیفگر سراسری، ساختاری است که شامل _بخشهای_ برنامه است. قبل از اینکه صفحهبندی به استاندارد تبدیل شود، از آن در معماریهای قدیمی استفاده میشد تا برنامه ها را از یکدیگر جدا کند. برای کسب اطلاعات بیشتر در مورد سگمنتبندی، فصل مربوط به این موضوع در [کتاب “Three Easy Pieces”] را مطالعه کنید. در حالی که سگمنتبندی در حالت 64 بیتی دیگر پشتیبانی نمیشود، GDT هنوز وجود دارد. بیشتر برای دو چیز استفاده میشود: جابجایی بین فضای هسته و فضای کاربر، و بارگذاری ساختار TSS.
+
+[کتاب “Three Easy Pieces”]: http://pages.cs.wisc.edu/~remzi/OSTEP/
+
+#### ایجاد یک GDT
+
+بیایید یک `GDT` استاتیک ایجاد کنیم که شامل یک بخش برای TSS استاتیک ما باشد:
+
+```rust
+// in src/gdt.rs
+
+use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor};
+
+lazy_static! {
+ static ref GDT: GlobalDescriptorTable = {
+ let mut gdt = GlobalDescriptorTable::new();
+ gdt.add_entry(Descriptor::kernel_code_segment());
+ gdt.add_entry(Descriptor::tss_segment(&TSS));
+ gdt
+ };
+}
+```
+
+ما دوباره از `lazy_static` استفاده میکنیم، زیرا ارزیابی کننده ثابت راست هنوز آنقدر توانمند نیست. ما یک GDT جدید با یک کد سگمنت و یک بخش TSS ایجاد میکنیم.
+
+#### بارگذاری GDT
+
+برای بارگذاری GDT، یک تابع جدید `gdt::init` ایجاد میکنیم که آن را از تابع `init` فراخوانی میکنیم:
+
+```rust
+// in src/gdt.rs
+
+pub fn init() {
+ GDT.load();
+}
+
+// in src/lib.rs
+
+pub fn init() {
+ gdt::init();
+ interrupts::init_idt();
+}
+```
+
+اکنون GDT ما بارگذاری شده است (از آنجا که تابع `start_`، تابع `init` را فراخوانی میکند)، اما هنوز حلقه بوت را هنگامِ سرریز پشته مشاهده میکنیم.
+
+### مراحل پایانی
+
+مشکل این است که سگمنتهای GDT هنوز فعال نیستند زیرا سگمنت و ثباتهای TSS هنوز حاوی مقادیر GDT قدیمی هستند. ما همچنین باید ورودی خطای دوگانه IDT را اصلاح کنیم تا از پشته جدید استفاده کند.
+
+به طور خلاصه، باید موارد زیر را انجام دهیم:
+
+۱. **بارگذاری مجدد ثبات کد سگمنت**: ما GDT خود را تغییر دادیم، بنابراین باید `cs`، ثبات کد سگمنت را بارگذاری مجدد کنیم. این مورد الزامی است زیرا انتخابگر سگمنت قدیمی میتواند اکنون توصیفگر دیگری از GDT را نشان دهد (به عنوان مثال توصیف کننده TSS).
+۲. **بارگذاری TSS**: ما یک GDT بارگذاری کردیم که شامل یک انتخابگر TSS است، اما هنوز باید به CPU بگوییم که باید از آن TSS استفاده کند.
+۳. **بروزرسانی ورودی IDT**: به محض اینکه TSS بارگذاری شد، CPU به یک جدول پشته وقفه معتبر (IST) دسترسی دارد. سپس میتوانیم به CPU بگوییم که باید با تغییر در ورودی IDT خطای دوگانه از پشته خطای دوگانه جدید استفاده کند.
+
+برای دو مرحله اول، ما نیاز به دسترسی به متغیرهای` code_selector` و `tss_selector` در تابع `gdt::init` داریم. میتوانیم با تبدیل آنها به بخشی از استاتیک از طریق ساختار جدید `Selectors` به این هدف برسیم:
+
+```rust
+// in src/gdt.rs
+
+use x86_64::structures::gdt::SegmentSelector;
+
+lazy_static! {
+ static ref GDT: (GlobalDescriptorTable, Selectors) = {
+ let mut gdt = GlobalDescriptorTable::new();
+ let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
+ let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
+ (gdt, Selectors { code_selector, tss_selector })
+ };
+}
+
+struct Selectors {
+ code_selector: SegmentSelector,
+ tss_selector: SegmentSelector,
+}
+```
+
+اکنون میتوانیم با استفاده از انتخابگرها، ثبات بخش `cs` را بارگذاری مجدد کرده و `TSS` را بارگذاری کنیم:
+
+```rust
+// in src/gdt.rs
+
+pub fn init() {
+ use x86_64::instructions::segmentation::set_cs;
+ use x86_64::instructions::tables::load_tss;
+
+ GDT.0.load();
+ unsafe {
+ set_cs(GDT.1.code_selector);
+ load_tss(GDT.1.tss_selector);
+ }
+}
+```
+
+ما با استفاده از [`set_cs`] ثبات کد سگمنت را بارگذاری مجدد میکنیم و برای بارگذاری TSS با از [`load_tss`] استفاده میکنیم. توابع به عنوان `unsafe` علامت گذاری شدهاند، بنابراین برای فراخوانی آنها به یک بلوک `unsafe` نیاز داریم. چون ممکن است با بارگذاری انتخابگرهای نامعتبر، ایمنی حافظه از بین برود.
+
+[`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 تنظیم کنیم:
+
+```rust
+// in src/interrupts.rs
+
+use crate::gdt;
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ unsafe {
+ idt.double_fault.set_handler_fn(double_fault_handler)
+ .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); // new
+ }
+
+ idt
+ };
+}
+```
+
+روش `set_stack_index` ایمن نیست زیرا فراخوان (ترجمه: caller) باید اطمینان حاصل کند که اندیس استفاده شده معتبر است و قبلاً برای استثنای دیگری استفاده نشده است.
+
+همین! اکنون CPU باید هر زمان که خطای دوگانه رخ داد، به پشته خطای دوگانه برود. بنابراین، ما میتوانیم _همه_ خطاهای دوگانه، از جمله سرریزهای پشته هسته را بگیریم:
+
+
+
+از این به بعد هرگز نباید شاهد خطای سهگانه باشیم! برای اطمینان از اینکه موارد بالا را به طور تصادفی نقض نمیکنیم، باید یک تست برای این کار اضافه کنیم.
+
+## تست سرریز پشته
+
+برای آزمایش ماژول `gdt` جدید و اطمینان از اینکه مدیر خطای دوگانه به درستی هنگام سرریز پشته فراخوانی شده است، میتوانیم یک تست یکپارچه اضافه کنیم. ایده این است که یک خطای دوگانه در تابع تست ایجاد کنید و تأیید کنید که مدیر خطای دوگانه فراخوانی میشود.
+
+بیایید با یک طرح مینیمال شروع کنیم:
+
+
+```rust
+// in tests/stack_overflow.rs
+
+#![no_std]
+#![no_main]
+
+use core::panic::PanicInfo;
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ unimplemented!();
+}
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ blog_os::test_panic_handler(info)
+}
+```
+
+مانند تست `panic_handler`، تست [بدون یک test harness] اجرا خواهد شد. زیرا پس از یک خطای دوگانه نمیتوانیم اجرا را ادامه دهیم، بنابراین بیش از یک تست منطقی نیست. برای غیرفعال کردن test harness برای این تست، موارد زیر را به `Cargo.toml` اضافه میکنیم:
+
+```toml
+# in Cargo.toml
+
+[[test]]
+name = "stack_overflow"
+harness = false
+```
+
+[بدون یک test harness]: @/edition-2/posts/04-testing/index.md#no-harness-tests
+
+حال باید `cargo test --test stack_overflow` بصورت موفقیتآمیز کامپایل شود. البته این تست با شکست مواجه میشود، زیرا ماکروی `unimplemented` پنیک میکند.
+
+### پیادهسازی `start_`
+
+پیادهسازی تابع `start_` مانند این است:
+
+```rust
+// in tests/stack_overflow.rs
+
+use blog_os::serial_print;
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ serial_print!("stack_overflow::stack_overflow...\t");
+
+ blog_os::gdt::init();
+ init_test_idt();
+
+ // trigger a stack overflow
+ stack_overflow();
+
+ panic!("Execution continued after stack overflow");
+}
+
+#[allow(unconditional_recursion)]
+fn stack_overflow() {
+ stack_overflow(); // for each recursion, the return address is pushed
+ volatile::Volatile::new(0).read(); // prevent tail recursion optimizations
+}
+```
+
+برای راهاندازی یک GDT جدید، تابع `gdt::init` را فراخوانی میکنیم. به جای فراخوانی تابع `interrupts::init_idt`، تابع `init_test_idt` را فراخوانی میکنیم که بزودی توضیح داده میشود. زیرا ما میخواهیم یک مدیر خطای دوگانه سفارشی ثبت کنیم که به جای پنیک کردن، دستور `exit_qemu(QemuExitCode::Success)` را انجام میدهد.
+
+تابع `stack_overflow` تقریباً مشابه تابع موجود در `main.rs` است. تنها تفاوت این است که برای جلوگیری از بهینهسازی کامپایلر موسوم به [_tail call elimination_]، در پایان تابع، یک خواندنِ [فرارِ] \(ترجمه: volatile) اضافه به وسیله نوع [`Volatile`] انجام میدهیم. از جمله، این بهینهسازی به کامپایلر اجازه میدهد تابعی را که آخرین عبارت آن فراخوانی تابع بازگشتی است، به یک حلقه طبیعی تبدیل کند. بنابراین، هیچ قاب پشته اضافی برای فراخوانی تابع ایجاد نمیشود، پس استفاده از پشته ثابت میماند.
+
+[volatile]: https://en.wikipedia.org/wiki/Volatile_(computer_programming)
+[`Volatile`]: https://docs.rs/volatile/0.2.6/volatile/struct.Volatile.html
+[_tail call elimination_]: https://en.wikipedia.org/wiki/Tail_call
+
+با این حال، در مورد ما، ما میخواهیم که سرریز پشته اتفاق بیفتد، بنابراین در انتهای تابع یک دستور خواندن فرار ساختگی اضافه میکنیم، که کامپایلر مجاز به حذف آن نیست. بنابراین، تابع دیگر _tail recursive_ نیست و از تبدیل به یک حلقه جلوگیری میشود. ما همچنین صفت `allow(unconditional_recursion)` را اضافه میکنیم تا اخطار کامپایلر را در مورد تکرار بیوقفه تابع خاموش نگه دارد.
+
+### تست IDT
+
+همانطور که در بالا ذکر شد، این تست به IDT مخصوص خود با یک مدیر خطای دوگانه سفارشی نیاز دارد. پیادهسازی به این شکل است:
+
+```rust
+// in tests/stack_overflow.rs
+
+use lazy_static::lazy_static;
+use x86_64::structures::idt::InterruptDescriptorTable;
+
+lazy_static! {
+ static ref TEST_IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ unsafe {
+ idt.double_fault
+ .set_handler_fn(test_double_fault_handler)
+ .set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX);
+ }
+
+ idt
+ };
+}
+
+pub fn init_test_idt() {
+ TEST_IDT.load();
+}
+```
+
+پیادهسازی بسیار شبیه IDT طبیعی ما در `interrupts.rs` است. مانند IDT عادی، برای مدیر خطای دوگانه به منظور جابجایی به پشتهای جداگانه، یک اندیس پشته را در IST تنظیم میکنیم. تابع `init_test_idt` با استفاده از روش `load`، آیدیتی را بر روی پردازنده بارگذاری میکند.
+
+### مدیر خطای دوگانه
+
+تنها قسمت جامانده، مدیر خطای دوگانه است که به این شکل پیادهسازی میشود:
+
+```rust
+// in tests/stack_overflow.rs
+
+use blog_os::{exit_qemu, QemuExitCode, serial_println};
+use x86_64::structures::idt::InterruptStackFrame;
+
+extern "x86-interrupt" fn test_double_fault_handler(
+ _stack_frame: &mut InterruptStackFrame,
+ _error_code: u64,
+) -> ! {
+ serial_println!("[ok]");
+ exit_qemu(QemuExitCode::Success);
+ loop {}
+}
+```
+
+هنگامی که مدیر خطای دوگانه فراخوانی میشود، از QEMU با یک کد خروج موفقیتآمیز خارج میشویم، که تست را بعنوان «قبول شده» علامتگذاری میداند. از آنجا که تستهای یکپارچه اجراییهای کاملاً مجزایی هستند، باید صفت `[feature(abi_x86_interrupt)]!#` را در بالای فایل تست تنظیم کنیم.
+
+اکنون میتوانیم تست را از طریق `cargo test --test stack_overflow` (یا `cargo test` برای اجرای همه تستها) انجام دهیم. همانطور که انتظار میرفت، خروجی `stack_overflow... [ok ]` را در کنسول مشاهده میکنیم. خط `set_stack_index` را کامنت کنید: این امر باعث میشود تست از کار بیفتد.
+
+## خلاصه
+
+در این پست یاد گرفتیم که خطای دوگانه چیست و در چه شرایطی رخ میدهد. ما یک مدیر خطای دوگانه پایه اضافه کردیم که پیام خطا را چاپ میکند و یک تست یکپارچه برای آن اضافه کردیم.
+
+ما همچنین تعویض پشته پشتیبانی شده سختافزاری را در استثناهای خطای دوگانه فعال کردیم تا در سرریز پشته نیز کار کند. در حین پیادهسازی آن، ما با سگمنت وضعیت پروسه (TSS)، جدول پشته وقفه (IST) و جدول توصیف کننده سراسری (GDT) آشنا شدیم، که برای سگمنتبندی در معماریهای قدیمی استفاده میشد.
+
+## بعدی چیست؟
+
+پست بعدی نحوه مدیریت وقفههای دستگاههای خارجی مانند تایمر، صفحه کلید یا کنترل کنندههای شبکه را توضیح میدهد. این وقفههای سختافزاری بسیار شبیه به استثناها هستند، به عنوان مثال آنها هم از طریق IDT ارسال میشوند. با این حال، برخلاف استثناها، مستقیماً روی پردازنده رخ نمیدهند. در عوض، یک _interrupt controller_ این وقفهها را جمع کرده و بسته به اولویت، آنها را به CPU میفرستد. در بخش بعدی، مدیر وقفه [Intel 8259] \("PIC") را بررسی خواهیم کرد و نحوه پیادهسازی پشتیبانی صفحه کلید را یاد خواهیم گرفت.
+
+[Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259
diff --git a/blog/content/edition-2/posts/06-double-faults/index.ja.md b/blog/content/edition-2/posts/06-double-faults/index.ja.md
new file mode 100644
index 00000000..4e78ad82
--- /dev/null
+++ b/blog/content/edition-2/posts/06-double-faults/index.ja.md
@@ -0,0 +1,554 @@
++++
+title = "Double Faults"
+weight = 6
+path = "ja/double-fault-exceptions"
+date = 2018-06-18
+
+[extra]
+chapter = "Interrupts"
+# Please update this when updating the translation
+translation_based_on_commit = "27ac0e1acc36f640d7045b427da2ed65b945756b"
+# GitHub usernames of the people that translated this post
+translators = ["garasubo"]
++++
+
+この記事ではCPUが例外ハンドラの呼び出しに失敗したときに起きる、ダブルフォルト例外について詳細に見ていきます。この例外を処理することによって、システムリセットを起こす重大な**トリプルフォルト**を避けることができます。あらゆる場合においてトリプルフォルトを防ぐために、ダブルフォルトを異なるカーネルスタック上でキャッチするための**割り込みスタックテーブル**をセットアップしていきます。
+
+
+
+このブログの内容は [GitHub] 上で公開・開発されています。何か問題や質問などがあれば issue をたててください(訳注: リンクは原文(英語)のものになります)。また[こちら][at the bottom]にコメントを残すこともできます。この記事の完全なソースコードは[`post-06` ブランチ][post branch]にあります。
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[at the bottom]: #comments
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-06
+
+
+
+## ダブルフォルトとは
+簡単に言うとダブルフォルトとはCPUが例外ハンドラを呼び出すことに失敗したときに起きる特別な例外です。例えば、ページフォルトが起きたが、ページフォルトハンドラが[割り込みディスクリプタテーブル][IDT](IDT: Interrupt Descriptor Table)(訳注: 翻訳当時、リンク先未訳)に登録されていないときに発生します。つまり、C++での`catch(...)`や、JavaやC#の`catch(Exception e)`ような、例外のあるプログラミング言語のcatch-allブロックのようなものです。
+
+[IDT]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-descriptor-table
+
+ダブルフォルトは通常の例外のように振る舞います。ベクター番号`8`を持ち、IDTに通常のハンドラ関数として定義できます。ダブルフォルトがうまく処理されないと、より重大な例外である**トリプルフォルト**が起きてしまうため、ダブルフォルトハンドラを設定することはとても重要です。トリプルフォルトはキャッチできず、ほとんどのハードウェアはシステムリセットを起こします。
+
+### ダブルフォルトを起こす
+ハンドラ関数を定義していない例外を発生させることでダブルフォルトを起こしてみましょう。
+
+```rust
+// in src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ blog_os::init();
+
+ // ページフォルトを起こす
+ unsafe {
+ *(0xdeadbeef as *mut u64) = 42;
+ };
+
+ // 前回同様
+ #[cfg(test)]
+ test_main();
+
+ println!("It did not crash!");
+ loop {}
+}
+```
+
+不正なアドレスである`0xdeadbeef`に書き込みを行うため`unsafe`を使います。この仮想アドレスはページテーブル上で物理アドレスにマップされていないため、ページフォルトが発生します。私達の[IDT]にはページフォルトが登録されていないため、ダブルフォルトが発生します。
+
+今、私達のカーネルを起動すると、ブートループが発生します。この理由は以下の通りです:
+
+1. CPUが`0xdeadbeef`に書き込みを試みページフォルトを起こします。
+2. CPUはIDTに対応するエントリを探しに行き、ハンドラ関数が指定されていないことを発見します。結果、ページフォルトハンドラが呼び出せず、ダブルフォルトが発生します。
+3. CPUはダブルフォルトハンドラのIDTエントリを見にいきますが、このエントリもハンドラ関数を指定していません。結果、**トリプルフォルト**が発生します。
+4. トリプルフォルトは重大なエラーなので、QEMUはほとんどの実際のハードウェアと同様にシステムリセットを行います。
+
+このトリプルフォルトを防ぐためには、ページフォルトかダブルフォルトのハンドラ関数を定義しないといけません。私達はすべての場合におけるトリプルフォルトを防ぎたいので、適切に処理できなかったすべての例外において呼び出されることになるダブルフォルトハンドラを定義するところからはじめましょう。
+
+## ダブルフォルトハンドラ
+ダブルフォルトは通常のエラーコードのある例外なので、ブレークポイントハンドラと同じようにハンドラ関数を指定することができます。
+
+```rust
+// in src/interrupts.rs
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt.double_fault.set_handler_fn(double_fault_handler); // new
+ idt
+ };
+}
+
+// new
+extern "x86-interrupt" fn double_fault_handler(
+ stack_frame: &mut InterruptStackFrame, _error_code: u64) -> !
+{
+ panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
+}
+```
+
+私達のハンドラは短いエラーメッセージを出力して、例外スタックフレームをダンプします。ダブルフォルトハンドラのエラーコードは常に`0`なので、出力する必要はないでしょう。ブレークポイントハンドラとの違いの一つは、ダブルフォルトハンドラは[発散する](diverging)(訳注: 翻訳当時、リンク先未訳)ということです。`x86_64`アーキテクチャではダブルフォルト例外から復帰することができないためです。
+
+[発散する]: https://doc.rust-jp.rs/rust-by-example-ja/fn/diverging.html
+
+ここで私達のカーネルを起動すると、ダブルフォルトハンドラが呼び出されていることがわかることでしょう。
+
+
+
+動きました!ここで何が起きているかというと、
+
+1. CPUが`0xdeadbeef`に書き込みを試みページフォルトを起こします。
+2. 以前と同様に、CPUはIDT中の対応するエントリを見にいきますが、ハンドラ関数が定義されていないことを発見し、結果、ダブルフォルトが起きます。
+3. 今回はダブルフォルトハンドラが指定されているので、CPUはそれを適切に呼び出せます。
+
+CPUはダブルフォルトハンドラを呼べるようになったので、トリプルフォルト(とブートループ)はもう起こりません。
+
+ここまでは簡単です。ではなぜこの例外のために丸々一つの記事を用意したのでしょうか?実は、私達は**ほとんどの**ダブルフォルトをキャッチすることはできますが、このアプローチでは十分でないケースがいくつか存在するのです。
+
+## ダブルフォルトの原因
+特別なケースを見ていく前に、ダブルフォルトの正確な原因を知る必要があります。ここまで、私達はとてもあいまいな定義を使ってきました。
+
+> ダブルフォルトとはCPUが例外ハンドラを呼び出すことに失敗したときに起きる特別な例外です。
+
+**「呼び出すことに失敗する」** とは正確には何を意味するのでしょうか?ハンドラが存在しない?ハンドラが[スワップアウト]された?また、ハンドラ自身が例外を発生させたらどうなるのでしょうか?
+
+[スワップアウト]: http://pages.cs.wisc.edu/~remzi/OSTEP/vm-beyondphys.pdf
+
+例えば以下のようなことが起こるとどうなるでしょう?
+
+1. ブレークポイント例外が発生したが、対応するハンドラがスワップアウトされていたら?
+2. ページフォルトが発生したが、ページフォルトハンドラがスワップアウトされていたら?
+3. ゼロ除算ハンドラがブレークポイント例外を起こしたが、ブレークポイントハンドラがスワップアウトされていたら?
+4. カーネルがスタックをオーバーフローさせて**ガードページ**にヒットしたら?
+
+幸いにもAMD64のマニュアル([PDF][AMD64 manual])には正確な定義が書かれています(8.2.9章)。それによると「ダブルフォルト例外は直前の(一度目の)例外ハンドラの処理中に二度目の例外が発生したとき**起きうる** (can occur)」と書かれています。**起きうる**というのが重要で、とても特別な例外の組み合わせでのみダブルフォルトとなります。この組み合わせは以下のようになっています。
+
+最初の例外 | 二度目の例外
+----------------|-----------------
+[ゼロ除算], [無効TSS], [セグメント不在], [スタックセグメントフォルト], [一般保護違反] | [無効TSS], [セグメント不在], [スタックセグメントフォルト], [一般保護違反]
+[ページフォルト] | [ページフォルト], [無効TSS], [セグメント不在], [スタックセグメントフォルト], [一般保護違反]
+
+[ゼロ除算]: https://wiki.osdev.org/Exceptions#Divide-by-zero_Error
+[無効TSS]: https://wiki.osdev.org/Exceptions#Invalid_TSS
+[セグメント不在]: https://wiki.osdev.org/Exceptions#Segment_Not_Present
+[スタックセグメントフォルト]: https://wiki.osdev.org/Exceptions#Stack-Segment_Fault
+[一般保護違反]: https://wiki.osdev.org/Exceptions#General_Protection_Fault
+[ページフォルト]: https://wiki.osdev.org/Exceptions#Page_Fault
+
+
+[AMD64 manual]: https://www.amd.com/system/files/TechDocs/24593.pdf
+
+例えばページフォルトに続いてゼロ除算例外が起きた場合は問題ありません(ページフォルトハンドラが呼び出される)が、一般保護違反に続いてゼロ除算例外が起きた場合はダブルフォルトが発生します。
+
+この表を見れば、先程の質問のうち最初の3つに答えることができます:
+
+1. ブレークポイント例外が発生して、対応するハンドラ関数がスワップアウトされている場合、**ページフォルト**が発生して**ページフォルトハンドラ**が呼び出される
+2. ページフォルトが発生してページフォルトハンドラがスワップアウトされている場合、**ダブルフォルト**が発生して**ダブルフォルトハンドラ**が呼び出されます。
+3. ゼロ除算ハンドラがブレークポイント例外を発生させた場合、CPUはブレークポイントハンドラを呼び出そうとします。もしブレークポイントハンドラがスワップアウトされている場合、**ページフォルト**が発生して**ページフォルトハンドラ**が呼び出されます。
+
+実際、IDTにハンドラ関数が指定されていない例外のケースでもこの体系に従っています。つまり、例外が発生したとき、CPUは対応するIDTエントリを読み込みにいきます。このエントリは0であり正しいIDTエントリではないので、**一般保護違反**が発生します。私達は一般保護違反のハンドラも定義していないので、新たな一般保護違反が発生します。表によるとこれはダブルフォルトを起こします。
+
+### カーネルスタックオーバーフロー
+4つ目の質問を見てみましょう。
+
+> カーネルがスタックをオーバーフローさせてガードページにヒットしたら?
+
+ガードページはスタックの底にある特別なメモリページで、これによってスタックオーバーフローを検出することができます。このページはどの物理メモリにもマップされていないので、アクセスすることで警告なく他のメモリを破壊する代わりにページフォルトが発生します。ブートローダーはカーネルスタックのためにガードページをセットアップするので、スタックオーバーフローは**ページフォルト**を発生させることになります。
+
+ページフォルトが起きるとCPUはIDT内のページフォルトハンドラを探しにいき、[割り込みスタックフレーム](訳注: 翻訳当時、リンク先未訳)をスタック上にプッシュしようと試みます。しかし、このスタックポインタは存在しないガードページを指しています。結果、二度目のページフォルトが発生して、ダブルフォルトが起きます(上の表によれば)。
+
+[割り込みスタックフレーム]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-stack-frame
+
+そして、ここでCPUは**ダブルフォルトハンドラ**を呼びにいきます。しかし、ダブルフォルト例外においてもCPUは例外スタックフレームをプッシュしようと試みます。スタックポインタはまだガードページを指しているので、**三度目の**ページフォルトが起きて、**トリプルフォルト**を発生させシステムは再起動します。そのため、私達の今のダブルフォルトハンドラではこの場合でのトリプルフォルトを避けることができません。
+
+実際にやってみましょう。カーネルスタックオーバーフローは無限に再帰する関数を呼び出すことによって簡単に引き起こせます:
+
+```rust
+// in src/main.rs
+
+#[no_mangle] // この関数の名前修飾をしない
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ blog_os::init();
+
+ fn stack_overflow() {
+ stack_overflow(); // 再帰呼び出しのために、リターンアドレスがプッシュされる
+ }
+
+ // スタックオーバーフローを起こす
+ stack_overflow();
+
+ […] // test_main(), println(…), and loop {}
+}
+```
+
+これをQEMUで試すと、再びブートループに入るのがわかります。
+
+では、私達はどうすればこの問題を避けられるでしょうか?例外スタックフレームをプッシュすることは、CPU自身が行ってしまうので、省略できません。つまりどうにかしてダブルフォルト例外が発生したときスタックが常に正常であることを保証する必要があります。幸いにもx86_64アーキテクチャにはこの問題の解決策があります。
+
+## スタックを切り替える
+x86_64アーキテクチャは例外発生時に予め定義されている既知の正常なスタックに切り替えることができます。この切り替えはハードウェアレベルで発生するので、CPUが例外スタックフレームをプッシュする前に行うことができます。
+
+切り替えの仕組みは**割り込みスタックテーブル**(IST: Interrupt Stack Table)として実装されています。ISTは7つの既知の正常なポインタのテーブルです。Rust風の疑似コードで表すとこのようになります:
+
+```rust
+struct InterruptStackTable {
+ stack_pointers: [Option; 7],
+}
+```
+
+各例外ハンドラに対して、私達は対応する[IDTエントリ](訳注: 翻訳当時、リンク先未訳)の`stack_pointers`フィールドを通してISTからスタックを選ぶことができます。例えば、IST中の最初のスタックをダブルフォルトハンドラのために使うことができます。そうすると、CPUがダブルフォルトが発生したときは必ず、このスタックに自動的に切り替えを行います。この切り替えは何かがプッシュされる前に起きるので、トリプルフォルトを防ぐことになります。
+
+[IDTエントリ]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-descriptor-table
+
+### ISTとTSS
+割り込みスタックテーブル(IST)は **[タスクステートセグメント]**(TSS)というレガシーな構造体の一部です。TSSはかつては様々な32ビットモードでのタスクに関する情報(例:プロセッサのレジスタの状態)を保持していて、例えば[ハードウェアコンテキストスイッチング]に使われていました。しかし、ハードウェアコンテキストスイッチングは64ビットモードではサポートされなくなり、TSSのフォーマットは完全に変わりました。
+
+[タスクステートセグメント]: https://ja.wikipedia.org/wiki/Task_state_segment
+[ハードウェアコンテキストスイッチング]: https://wiki.osdev.org/Context_Switching#Hardware_Context_Switching
+
+x86_64ではTSSはタスク固有の情報は全く持たなくなりました。代わりに、2つのスタックテーブル(ISTがその1つ)を持つようになりました。唯一32ビットと64ビットのTSSで共通のフィールドは[I/Oポート許可ビットマップ]へのポインタのみです。
+
+[I/Oポート許可ビットマップ]: https://ja.wikipedia.org/wiki/Task_state_segment#I/O許可ビットマップ
+
+64ビットのTSSは下記のようなフォーマットです:
+
+フィールド | 型
+------ | ----------------
+(予約済み) | `u32`
+特権スタックテーブル | `[u64; 3]`
+(予約済み) | `u64`
+割り込みスタックテーブル | `[u64; 7]`
+(予約済み) | `u64`
+(予約済み) | `u16`
+I/Oマップベースアドレス | `u16`
+
+**特権スタックテーブル**は特権レベルが変わった際にCPUが使用します。例えば、CPUがユーザーモード(特権レベル3)の時に例外が発生した場合、CPUは通常例外ハンドラを呼び出す前にカーネルモード(特権レベル0)に切り替わります。この場合、CPUは特権レベルスタックテーブルの0番目のスタックに切り替わります。ユーザーモードについてはまだ実装してないため、このテープルはとりあえず無視しておきましょう。
+
+### TSSをつくる
+割り込みスタックテーブルにダブルフォルト用のスタックを含めた新しいTSSをつくってみましょう。そのためにはTSS構造体が必要です。幸いにも、すでに`x86_64`クレートに[`TaskStateSegment`構造体]が含まれているので、これを使っていきます。
+
+[`TaskStateSegment`構造体]: https://docs.rs/x86_64/0.12.1/x86_64/structures/tss/struct.TaskStateSegment.html
+
+新しい`gdt`モジュール内でTSSをつくります(名前の意味は後でわかるでしょう):
+
+```rust
+// in src/lib.rs
+
+pub mod gdt;
+
+// in src/gdt.rs
+
+use x86_64::VirtAddr;
+use x86_64::structures::tss::TaskStateSegment;
+use lazy_static::lazy_static;
+
+pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
+
+lazy_static! {
+ static ref TSS: TaskStateSegment = {
+ let mut tss = TaskStateSegment::new();
+ tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
+ const STACK_SIZE: usize = 4096 * 5;
+ static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
+
+ let stack_start = VirtAddr::from_ptr(unsafe { &STACK });
+ let stack_end = stack_start + STACK_SIZE;
+ stack_end
+ };
+ tss
+ };
+}
+```
+
+Rustの定数評価機はこの初期化をコンパイル時に行うことがまだできないので`lazy_static`を使います。ここでは0番目のISTエントリをダブルフォルト用のスタックとして定義します(他のISTのインデックスでも動くでしょう)。そして、ダブルフォルト用スタックの先頭アドレスを0番目のエントリに書き込みます。先頭アドレスを書き込むのはx86のスタックは下、つまり高いアドレスから低いアドレスに向かって伸びていくからです。
+
+私達はまだメモリ管理を実装していません。そのため、新しいスタックを確保する適切な方法がありません。その代わり今回は、スタックのストレージとして`static mut`な配列を使います。コンパイラが変更可能な静的変数がアクセスされるとき競合がないことを保証できないため`unsafe`が必要となります。これが不変の`static`ではなく`static mut`であることは重要です。そうでなければブートローダーはこれをリードオンリーのページにマップしてしまうからです。私達は後の記事でこの部分を適切なスタック確保処理に置き換えます。そうすればこの部分での`unsafe`は必要なくなります。
+
+ちなみに、このダブルフォルトスタックはスタックオーバーフローに対する保護をするガードページを持ちません。つまり、スタックオーバーフローがスタックより下のメモリを破壊するかもしれないので、私達はダブルフォルトハンドラ内でスタックを多用すべきではないということです。
+
+#### TSSを読み込む
+新しいTSSをつくったので、CPUにそれを使うように教える方法が必要です。残念ながら、これはちょっと面倒くさいです。なぜならTSSは(歴史的な理由で)セグメンテーションシステムを使うためです。テーブルを直接読み込むのではなく、新しいセグメントディスクリプタを[グローバルディスクリプタテーブル](GDT: Global Descriptor Table)に追加する必要があります。そうすると各自のGDTインデックスで[`ltr`命令]を呼び出すことで私達のTSSを読み込むことができます。
+
+[グローバルディスクリプタテーブル]: https://web.archive.org/web/20190217233448/https://www.flingos.co.uk/docs/reference/Global-Descriptor-Table/
+[`ltr`命令]: https://www.felixcloutier.com/x86/ltr
+
+### グローバルディスクリプタテーブル
+グローバルディスクリプタテーブル(GDT)はページングがデファクトスタンダードになる以前は、[メモリセグメンテーション]のため使われていた古い仕組みです。カーネル・ユーザーモードの設定やTSSの読み込みなど、様々なことを行うために64ビットモードでも未だに必要です。
+
+[メモリセグメンテーション]: https://ja.wikipedia.org/wiki/セグメント方式
+
+GDTはプログラムの**セグメント**を含む構造です。ページングが標準になる以前に、プログラム同士を独立させるためにより古いアーキテクチャで使われていました。セグメンテーションに関するより詳しい情報は無料の[「Three Easy Pieces」]という本の同じ名前の章を見てください。セグメンテーションは64ビットモードではもうサポートされていませんが、GDTはまだ存在しています。GDTはカーネル空間とユーザー空間の切り替えと、TSS構造体の読み込みという主に2つのことに使われています。
+
+[「Three Easy Pieces」]: http://pages.cs.wisc.edu/~remzi/OSTEP/
+
+#### GDTをつくる
+`TSS`の静的変数のセグメントを含む静的`GDT`をつくりましょう:
+
+```rust
+// in src/gdt.rs
+
+use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor};
+
+lazy_static! {
+ static ref GDT: GlobalDescriptorTable = {
+ let mut gdt = GlobalDescriptorTable::new();
+ gdt.add_entry(Descriptor::kernel_code_segment());
+ gdt.add_entry(Descriptor::tss_segment(&TSS));
+ gdt
+ };
+}
+```
+
+先に紹介したコードと同様に、再び`lazy_static`を使います。コードセグメントとTSSセグメントを持つ新しいGDTを作成します。
+
+#### GDTを読み込む
+
+GDTを読み込むために新しく`gdt::init`関数をつくり、これを`init`関数から呼び出します:
+
+```rust
+// in src/gdt.rs
+
+pub fn init() {
+ GDT.load();
+}
+
+// in src/lib.rs
+
+pub fn init() {
+ gdt::init();
+ interrupts::init_idt();
+}
+```
+
+これでGDTが読み込まれます(`_start`関数は`init`を呼び出すため)が、これではまだスタックオーバーフローでブートループが起きてしまいます。
+
+### 最後のステップ
+
+問題はGDTセグメントとTSSレジスタが古いGDTからの値を含んでいるため、GDTセグメントがまだ有効になっていないことです。ダブルフォルト用のIDTエントリが新しいスタックを使うように変更する必要もあります。
+
+まとめると、私達は次のようなことをする必要があります:
+
+1. **コードセグメントレジスタを再読み込みする**:GDTを変更したので、コードセグメントレジスタ`cs`を再読み込みする必要があります。これは、古いセグメントセレクタが異なるGDTディスクリプタ(例:TSSディスクリプタ)を指す可能性があるためです。
+2. **TSSをロードする**:TSSセレクタを含むGDTをロードしましたが、CPUにこのTSSを使うよう教えてあげる必要があります。
+3. **IDTエントリを更新する**:TSSがロードされると同時に、CPUは正常な割り込みスタックテーブル(IST)へアクセスできるようになります。そうしたら、ダブルフォルトIDTエントリを変更することで、CPUに新しいダブルフォルトスタックを使うよう教えてあげることができます。
+
+最初の2つのステップのために、私達は`gdt::init`関数の中で`code_selector`と`tss_selector`変数にアクセスする必要があります。これは、その変数たちを新しい`Selectors`構造体を使い静的変数にすることで実装できます:
+
+```rust
+// in src/gdt.rs
+
+use x86_64::structures::gdt::SegmentSelector;
+
+lazy_static! {
+ static ref GDT: (GlobalDescriptorTable, Selectors) = {
+ let mut gdt = GlobalDescriptorTable::new();
+ let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
+ let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
+ (gdt, Selectors { code_selector, tss_selector })
+ };
+}
+
+struct Selectors {
+ code_selector: SegmentSelector,
+ tss_selector: SegmentSelector,
+}
+```
+
+これで私達は`cs`セグメントレジスタを再読み込みし`TSS`を読み込むためにセレクタを使うことができます:
+
+```rust
+// in src/gdt.rs
+
+pub fn init() {
+ use x86_64::instructions::segmentation::set_cs;
+ use x86_64::instructions::tables::load_tss;
+
+ GDT.0.load();
+ unsafe {
+ set_cs(GDT.1.code_selector);
+ load_tss(GDT.1.tss_selector);
+ }
+}
+```
+
+[`set_cs`]を使ってコードセグメントレジスタを再読み込みして、[`load_tss`]を使ってTSSを読み込んでいます。これらの関数は`unsafe`とマークされているので、呼び出すには`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
+
+これで正常なTSSと割り込みスタックテーブルを読み込みこんだので、私達はIDT内のダブルフォルトハンドラにスタックインデックスをセットすることができます:
+
+```rust
+// in src/interrupts.rs
+
+use crate::gdt;
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ unsafe {
+ idt.double_fault.set_handler_fn(double_fault_handler)
+ .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); // new
+ }
+
+ idt
+ };
+}
+```
+
+`set_stack_index`メソッドは呼び出し側が、使われているインデックスが正しいものであり、かつ他の例外で使われていないかを確かめる必要があるため、`unsafe`です。
+
+これで全部です。CPUはダブルフォルトが発生したら常にダブルフォルトスタックに切り替えるでしょう。よって、私達はカーネルスタックオーバーフローを含む**すべての**ダブルフォルトをキャッチすることができます。
+
+
+
+これからはトリプルフォルトを見ることは二度とないでしょう。これらダブルフォルトのための実装を誤って壊していないことを保証するために、テストを追加しましょう。
+
+## スタックオーバーフローテスト
+
+新しい`gdt`モジュールをテストしダブルフォルトハンドラがスタックオーバーフローで正しく呼ばれることを保証するために、結合テストを追加します。ここでの考えは、テスト関数内でダブルフォルトを引き起こしダブルフォルトハンドラが呼び出されていることを確かめる、というものです。
+
+最小の骨組みから始めましょう:
+
+```rust
+// in tests/stack_overflow.rs
+
+#![no_std]
+#![no_main]
+
+use core::panic::PanicInfo;
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ unimplemented!();
+}
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ blog_os::test_panic_handler(info)
+}
+```
+
+`panic_handler`のテストと同様、テストは[テストハーネスなし]で実行されます。理由は私達はダブルフォルト後に実行を続けることができず、2つ以上のテストは意味をなさないためです。テストハーネスを無効にするために、以下を`Cargo.toml`に追加します:
+
+```toml
+# in Cargo.toml
+
+[[test]]
+name = "stack_overflow"
+harness = false
+```
+
+[テストハーネスなし]: @/edition-2/posts/04-testing/index.ja.md#hanesu-harness-nonaitesuto
+
+これで`cargo test --test stack_overflow`でのコンパイルは成功するでしょう。`unimplemented`マクロがパニックを起こすため、テストはもちろん失敗します。
+
+### `_start`を実装する
+
+`_start`関数の実装はこのようになります:
+
+```rust
+// in tests/stack_overflow.rs
+
+use blog_os::serial_print;
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ serial_print!("stack_overflow::stack_overflow...\t");
+
+ blog_os::gdt::init();
+ init_test_idt();
+
+ // スタックオーバーフローを起こす
+ stack_overflow();
+
+ panic!("Execution continued after stack overflow");
+}
+
+#[allow(unconditional_recursion)]
+fn stack_overflow() {
+ stack_overflow(); // 再帰のたびにリターンアドレスがプッシュされる
+ volatile::Volatile::new(0).read(); // 末尾最適化を防ぐ
+}
+```
+
+新しいGDTを初期化するために`gdt::init`関数を呼びます。そして`interrupts::init_idt`関数を呼び出す代わりに、すぐ後で説明する`init_test_idt`関数を呼びます。なぜなら、私達はパニックの代わりに`exit_qemu(QemuExitCode::Success)`を実行するカスタムしたダブルフォルトハンドラを登録したいためです。
+
+`stack_overflow`関数は`main.rs`の中にある関数とほとんど同じです。唯一の違いは[**末尾呼び出し最適化**]と呼ばれるコンパイラの最適化を防ぐために[`Volatile`]タイプを使って関数の末尾で追加の[volatile]読み込みを行っていることです。この最適化の特徴として、コンパイラが、最後の文が再帰関数呼び出しである関数を通常のループに変換できるようになる、というものがあります。その結果として、追加のスタックフレームが関数呼び出しではつくられず、スタックの使用量が変わらないままとなります。
+
+[volatile]: https://en.wikipedia.org/wiki/Volatile_(computer_programming)
+[`Volatile`]: https://docs.rs/volatile/0.2.6/volatile/struct.Volatile.html
+[**末尾呼び出し最適化**]: https://ja.wikipedia.org/wiki/末尾再帰#末尾呼出し最適化
+
+しかし、ここではスタックオーバーフローを起こしたいので、コンパイラに削除されない、ダミーのvolatile読み込み文を関数の末尾に追加します。その結果、関数は**末尾再帰**ではなくなり、ループへの変換は防がれます。更に関数が無限に再帰することに対するコンパイラの警告をなくすために`allow(unconditional_recursion)`属性を追加します。
+
+### IDTのテスト
+
+上で述べたように、テストはカスタムしたダブルフォルトハンドラを含む専用のIDTが必要です。実装はこのようになります:
+
+```rust
+// in tests/stack_overflow.rs
+
+use lazy_static::lazy_static;
+use x86_64::structures::idt::InterruptDescriptorTable;
+
+lazy_static! {
+ static ref TEST_IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ unsafe {
+ idt.double_fault
+ .set_handler_fn(test_double_fault_handler)
+ .set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX);
+ }
+
+ idt
+ };
+}
+
+pub fn init_test_idt() {
+ TEST_IDT.load();
+}
+```
+
+実装は`interrupts.rs`内の通常のIDTと非常に似ています。通常のIDT同様、分離されたスタックに切り替えるようダブルフォルトハンドラ用のISTにスタックインデックスをセットします。`init_test_idt`関数は`load`メソッドによりCPU上にIDTを読み込みます。
+
+### ダブルフォルトハンドラ
+
+唯一欠けているのはダブルフォルトハンドラです。このようになります:
+
+```rust
+// in tests/stack_overflow.rs
+
+use blog_os::{exit_qemu, QemuExitCode, serial_println};
+use x86_64::structures::idt::InterruptStackFrame;
+
+extern "x86-interrupt" fn test_double_fault_handler(
+ _stack_frame: &mut InterruptStackFrame,
+ _error_code: u64,
+) -> ! {
+ serial_println!("[ok]");
+ exit_qemu(QemuExitCode::Success);
+ loop {}
+}
+```
+
+ダブルフォルトハンドラが呼ばれるとき、私達はQEMUを正常な終了コードで終了し、テストを成功とマークします。結合テストは完全に分けられた実行ファイルなので、私達はテストファイルの先頭で`#![feature(abi_x86_interrupt)]`属性を再びセットする必要があります。
+
+これで私達は`cargo test --test stack_overflow`(もしくは全部のテストを走らせるよう`cargo test`)でテストを走らせることができます。期待していたとおり、`stack_overflow... [ok]`とコンソールに出力されるのがわかります。`set_stack_index`の行をコメントアウトすると、テストは失敗するでしょう。
+
+## まとめ
+この記事では私達はダブルフォルトが何であるかとどういう条件下で発生するかを学びました。エラーメッセージを出力する基本的なダブルフォルトハンドラと、そのための結合テストを追加しました。
+
+また、私達はスタックオーバーフロー下でも動くよう、ダブルフォルト発生時にハードウェアがサポートするスタック切り替えを行うようにしました。実装していく中で、古いアーキテクチャでのセグメンテーションで使われていたタスクステートセグメント(TSS)、割り込みスタックテーブル(IST)、グローバルディスクリプタテーブル(GDT)についても学びました。
+
+## 次は?
+次の記事ではタイマーやキーボード、ネットワークコントローラのような、外部デバイスからの割り込みをどのように処理するかを説明します。これらのハードウェア割り込みは例外によく似ています。例えば、これらもIDTからディスパッチされます。しかしながら、例外とは違い、それらはCPU上で直接発生するものではありません。代わりに、**割り込みコントローラ**がこれらの割り込みを集めて、優先度によってそれらをCPUに送ります。次回、私達は[Intel 8259](PIC)割り込みコントローラを調べ、どのようにキーボードのサポートを実装するかを学びます。
+
+[Intel 8259]: https://ja.wikipedia.org/wiki/Intel_8259
diff --git a/blog/content/edition-2/posts/06-double-faults/index.md b/blog/content/edition-2/posts/06-double-faults/index.md
index 45a90bc8..54542733 100644
--- a/blog/content/edition-2/posts/06-double-faults/index.md
+++ b/blog/content/edition-2/posts/06-double-faults/index.md
@@ -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):
@@ -373,10 +373,10 @@ 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.
+We reload the code segment register using [`set_cs`] and 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:
@@ -432,7 +432,7 @@ fn panic(info: &PanicInfo) -> ! {
}
```
-Like our `panic_handler` test, the test will run [without a test harness]. The reason is that we can't continue execution after a double fault, so more than one test doesn't make sense. To disable, the test harness for the test, we add the following to our `Cargo.toml`:
+Like our `panic_handler` test, the test will run [without a test harness]. The reason is that we can't continue execution after a double fault, so more than one test doesn't make sense. To disable the test harness for the test, we add the following to our `Cargo.toml`:
```toml
# in Cargo.toml
diff --git a/blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md b/blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md
new file mode 100644
index 00000000..faeb8959
--- /dev/null
+++ b/blog/content/edition-2/posts/07-hardware-interrupts/index.fa.md
@@ -0,0 +1,738 @@
++++
+title = "وقفههای سختافزاری"
+weight = 7
+path = "fa/hardware-interrupts"
+date = 2018-10-22
+
+[extra]
+chapter = "Interrupts"
+# Please update this when updating the translation
+translation_based_on_commit = "b6ff79ac3290ea92c86763d49cc6c0ff4fb0ea30"
+# GitHub usernames of the people that translated this post
+translators = ["hamidrezakp", "MHBahrampour"]
+rtl = true
++++
+
+در این پست ما کنترل کننده قابل برنامه ریزی وقفه را تنظیم می کنیم تا وقفه های سخت افزاری را به درستی به پردازنده منتقل کند. برای مدیریت این وقفهها ، موارد جدیدی به جدول توصیف کننده وقفه اضافه می کنیم ، دقیقاً مانند کارهایی که برای کنترل کننده های استثنا انجام دادیم. ما یاد خواهیم گرفت که چگونه وقفه های متناوب تایمر را گرفته و چگونه از صفحه کلید ورودی بگیریم.
+
+
+
+این بلاگ بصورت آزاد بر روی [گیتهاب] توسعه داده شده. اگر مشکل یا سوالی دارید، لطفاً آنجا یک ایشو باز کنید. همچنین میتوانید [در زیر] این پست کامنت بگذارید. سورس کد کامل این پست را میتوانید در بِرَنچ [`post-07`][post branch] پیدا کنید.
+
+[گیتهاب]: https://github.com/phil-opp/blog_os
+[در زیر]: #comments
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-07
+
+
+
+## مقدمه
+
+وقفهها راهی برای اطلاع به پردازنده از دستگاه های سخت افزاری متصل ارائه می دهند. بنابراین به جای اینکه پردازنده به طور دورهای صفحه کلید را برای کاراکترهای جدید بررسی کند(فرآیندی به نام [_polling_]) ، صفحه کلید میتواند هسته را برای هر فشردن کلید مطلع کند. این بسیار کارآمدتر است زیرا هسته فقط زمانی که اتفاقی افتاده است باید عمل کند. همچنین زمان واکنش سریع تری را فراهم می کند ، زیرا هسته می تواند بلافاصله و نه تنها در پول(کلمه: poll) بعدی واکنش نشان دهد.
+
+[_polling_]: https://en.wikipedia.org/wiki/Polling_(computer_science)
+
+اتصال مستقیم تمام دستگاه های سخت افزاری به پردازنده امکان پذیر نیست. در عوض ، یک _کنترل کننده وقفه_ جداگانه ، وقفهها را از همه دستگاهها جمع کرده و سپس پردازنده را مطلع می کند:
+
+```
+ ____________ _____
+ Timer ------------> | | | |
+ Keyboard ---------> | Interrupt |---------> | CPU |
+ Other Hardware ---> | Controller | |_____|
+ Etc. -------------> |____________|
+
+```
+
+بیشتر کنترل کننده های وقفه قابل برنامه ریزی هستند ، به این معنی که آنها از اولویت های مختلف برای وقفهها پشتیبانی می کنند. به عنوان مثال ، این اجازه را می دهند تا به وقفه های تایمر اولویت بیشتری نسبت به وقفه های صفحه کلید داد تا از زمان بندی دقیق اطمینان حاصل شود.
+
+بر خلاف استثناها ، وقفه های سخت افزاری _به صورت نا هم زمان_ اتفاق می افتند. این بدان معنی است که آنها کاملاً از کد اجرا شده مستقل هستند و در هر زمان ممکن است رخ دهند. بنابراین ما ناگهان شکلی از همروندی در هسته خود با تمام اشکالات احتمالی مرتبط با همروندی داریم. مدل مالکیت دقیق راست در اینجا به ما کمک می کند زیرا مانع حالت تغییر پذیری گلوبال است(mutable global state). با این حال، همچنان احتمال بن بست وجود دارد، همانطور که بعداً در این پست خواهیم دید.
+
+## The 8259 PIC
+
+[Intel 8259] یک کنترل کننده وقفه قابل برنامه ریزی (PIC) است که در سال 1976 معرفی شد. مدت طولانی است که با [APIC] جدید جایگزین شده است ، اما رابط آن هنوز به دلایل سازگاری در سیستم های فعلی پشتیبانی می شود. 8259 PIC به طور قابل ملاحظه ای آسان تر از APIC است ، بنابراین ما قبل از مهاجرت و استفاده از APIC در آینده، از آن برای معرفی وقفه استفاده خواهیم کرد.
+
+[APIC]: https://en.wikipedia.org/wiki/Intel_APIC_Architecture
+
+8259 دارای 8 خط وقفه و چندین خط برای برقراری ارتباط با پردازنده است. سیستم های معمولی در آن زمان به دو نمونه از 8259 PIC مجهز بودند ، یکی اصلی و دیگری PIC ثانویه که به یکی از خطوط وقفه اولیه متصل است:
+
+[Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259
+
+```
+ ____________ ____________
+Real Time Clock --> | | Timer -------------> | |
+ACPI -------------> | | Keyboard-----------> | | _____
+Available --------> | Secondary |----------------------> | Primary | | |
+Available --------> | Interrupt | Serial Port 2 -----> | Interrupt |---> | CPU |
+Mouse ------------> | Controller | Serial Port 1 -----> | Controller | |_____|
+Co-Processor -----> | | Parallel Port 2/3 -> | |
+Primary ATA ------> | | Floppy disk -------> | |
+Secondary ATA ----> |____________| Parallel Port 1----> |____________|
+
+```
+
+این نمودار نحوه اتصال معمول خطوط وقفه را نشان می دهد. می بینیم که بیشتر 15 خط دارای یک نگاشت ثابت هستند ، به عنوان مثال خط 4 PIC ثانویه به ماوس اختصاص داده شده است.
+
+هر کنترل کننده را می توان از طریق دو [پورت ورودی/خروجی] ، یک پورت "فرمان" و یک پورت "داده" پیکربندی کرد. برای کنترل کننده اصلی ، این پورتها `0x20` (فرمان) و`0x21` (داده) هستند. برای کنترل کننده ثانویه آنها `0xa0` (فرمان) و `0xa1` (داده) هستند. برای اطلاعات بیشتر در مورد نحوه پیکربندی PIC ها ، به [مقالهای در osdev.org] مراجعه کنید.
+
+[پورت ورودی/خروجی]: @/edition-2/posts/04-testing/index.md#i-o-ports
+[مقالهای در osdev.org]: https://wiki.osdev.org/8259_PIC
+
+### پیاده سازی
+
+پیکربندی پیش فرض PIC ها قابل استفاده نیست، زیرا اعداد بردار وقفه را در محدوده 15-0 به پردازنده می فرستد. این اعداد در حال حاضر توسط استثناهای پردازنده اشغال شدهاند ، به عنوان مثال شماره 8 مربوط به یک خطای دوگانه است. برای رفع این مشکل همپوشانی، باید وقفه های PIC را به اعداد دیگری تغییر دهیم. دامنه واقعی مهم نیست به شرطی که با استثناها همپوشانی نداشته باشد ، اما معمولاً محدوده 47-32 انتخاب می شود، زیرا اینها اولین شماره های آزاد پس از 32 اسلات استثنا هستند.
+
+پیکربندی با نوشتن مقادیر ویژه در پورت های فرمان و داده PIC ها اتفاق می افتد. خوشبختانه قبلا کرتای به نام [`pic8259_simple`] وجود دارد، بنابراین نیازی نیست که توالی راه اندازی اولیه را خودمان بنویسیم. در صورت علاقهمند بودن به چگونگی عملکرد آن، [کد منبع آن][pic crate source] را بررسی کنید، نسبتاً کوچک و دارای مستند خوبی است.
+
+[pic crate source]: https://docs.rs/crate/pic8259_simple/0.2.0/source/src/lib.rs
+
+برای افزودن کرت به عنوان وابستگی ، موارد زیر را به پروژه خود اضافه می کنیم:
+
+[`pic8259_simple`]: https://docs.rs/pic8259_simple/0.2.0/pic8259_simple/
+
+```toml
+# in Cargo.toml
+
+[dependencies]
+pic8259_simple = "0.2.0"
+```
+
+انتزاع اصلی ارائه شده توسط کرت، ساختمان [`ChainedPics`] است که نمایانگر طرح اولیه/ثانویه PIC است که در بالا دیدیم. برای استفاده به روش زیر طراحی شده است:
+
+[`ChainedPics`]: https://docs.rs/pic8259_simple/0.2.0/pic8259_simple/struct.ChainedPics.html
+
+```rust
+// in src/interrupts.rs
+
+use pic8259_simple::ChainedPics;
+use spin;
+
+pub const PIC_1_OFFSET: u8 = 32;
+pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
+
+pub static PICS: spin::Mutex =
+ spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
+```
+
+همانطور که در بالا اشاره کردیم، افست PIC ها را در محدوده 47-32 تنظیم می کنیم. با بسته بندی ساختمان `ChainedPics` در `Mutex` می توانیم دسترسی قابل تغییر و ایمن (از طریق [متد lock][spin mutex lock]) به آن داشته باشیم، که در مرحله بعدی به آن نیاز داریم. تابع `ChainedPics::new` ناامن است زیرا افست اشتباه ممکن است باعث رفتار نامشخص شود.
+
+[spin mutex lock]: https://docs.rs/spin/0.5.2/spin/struct.Mutex.html#method.lock
+
+اکنون می توانیم 8259 PIC را در تابع `init` خود مقدار دهی اولیه کنیم:
+
+```rust
+// in src/lib.rs
+
+pub fn init() {
+ gdt::init();
+ interrupts::init_idt();
+ unsafe { interrupts::PICS.lock().initialize() }; // new
+}
+```
+
+ما از تابع [`initialize`] برای انجام مقداردهی اولیه PIC استفاده می کنیم. مانند تابع `ChainedPics::new`، این تابع نیز ایمن نیست زیرا در صورت عدم پیکربندی صحیح PIC می تواند باعث رفتار نامشخص شود.
+
+[`initialize`]: https://docs.rs/pic8259_simple/0.2.0/pic8259_simple/struct.ChainedPics.html#method.initialize
+
+اگر همه چیز خوب پیش برود ، باید هنگام اجرای `cargo run` پیام "It did not crash" را ببینیم.
+
+## فعالسازی وقفهها
+
+تاکنون هیچ اتفاقی نیفتاده است زیرا وقفهها همچنان در تنظیمات پردازنده غیرفعال هستند. این بدان معناست که پردازنده به هیچ وجه به کنترل کننده وقفه گوش نمی دهد، بنابراین هیچ وقفه ای نمی تواند به پردازنده برسد. بیایید این را تغییر دهیم:
+
+```rust
+// in src/lib.rs
+
+pub fn init() {
+ gdt::init();
+ interrupts::init_idt();
+ unsafe { interrupts::PICS.lock().initialize() };
+ x86_64::instructions::interrupts::enable(); // new
+}
+```
+
+تابع `interrupts::enable` از کرت `x86_64` دستورالعمل خاص `sti` را اجرا می کند (“set interrupts”) تا وقفه های خارجی را فعال کند. اکنون وقتی `cargo run` را امتحان می کنیم ، می بینیم که یک خطای دوگانه رخ میدهد:
+
+
+
+دلیل این خطای دوگانه این است که تایمر سخت افزاری (به طور دقیق تر [Intel 8253]) به طور پیش فرض فعال است، بنابراین به محض فعال کردن وقفهها ، شروع به دریافت وقفه های تایمر می کنیم. از آنجا که هنوز یک تابع کنترل کننده برای آن تعریف نکردهایم ، کنترل کننده خطای دوگانه فراخوانی می شود.
+
+[Intel 8253]: https://en.wikipedia.org/wiki/Intel_8253
+
+## مدیریت وقفههای تایمر
+
+همانطور که در شکل [بالا](#the-8259-pic) می بینیم، تایمر از خط 0 از PIC اصلی استفاده می کند. این به این معنی است که به صورت وقفه 32 (0 + افست 32) به پردازنده می رسد. به جای هارد-کد(Hardcode) کردن 32، آن را در یک اینام(enum) به نام `InterruptIndex` ذخیره می کنیم:
+
+```rust
+// in src/interrupts.rs
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u8)]
+pub enum InterruptIndex {
+ Timer = PIC_1_OFFSET,
+}
+
+impl InterruptIndex {
+ fn as_u8(self) -> u8 {
+ self as u8
+ }
+
+ fn as_usize(self) -> usize {
+ usize::from(self.as_u8())
+ }
+}
+```
+
+اینام یک [اینام C مانند] است بنابراین ما می توانیم ایندکس را برای هر نوع به طور مستقیم مشخص کنیم. ویژگی `repr(u8)` مشخص می کند که هر نوع به عنوان `u8` نشان داده می شود. در آینده انواع بیشتری برای وقفه های دیگر اضافه خواهیم کرد.
+
+[اینام C مانند]: https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-fieldless-enumerations
+
+اکنون می توانیم یک تابع کنترل کننده برای وقفه تایمر اضافه کنیم:
+
+```rust
+// in src/interrupts.rs
+
+use crate::print;
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ […]
+ idt[InterruptIndex::Timer.as_usize()]
+ .set_handler_fn(timer_interrupt_handler); // new
+
+ idt
+ };
+}
+
+extern "x86-interrupt" fn timer_interrupt_handler(
+ _stack_frame: &mut InterruptStackFrame)
+{
+ print!(".");
+}
+```
+
+`timer_interrupt_handler` ما دارای امضای مشابه کنترل کننده های استثنای ما است ، زیرا پردازنده به طور یکسان به استثناها و وقفه های خارجی واکنش نشان می دهد (تنها تفاوت این است که برخی از استثناها کد خطا را در پشته ذخیره میکنند). ساختمان [`InterruptDescriptorTable`] تریت [`IndexMut`] را پیاده سازی می کند، بنابراین می توانیم از طریق سینتکس ایندکسدهی آرایه، به ایتم های جداگانه دسترسی پیدا کنیم.
+
+[`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
+
+در کنترل کننده وقفه تایمر، یک نقطه را روی صفحه چاپ می کنیم. همانطور که وقفه تایمر به صورت دوره ای اتفاق می افتد ، انتظار داریم که در هر تیک تایمر یک نقطه ظاهر شود. با این حال، هنگامی که آن را اجرا می کنیم می بینیم که فقط یک نقطه چاپ می شود:
+
+
+
+### پایان وقفه
+
+دلیل این امر این است که PIC انتظار دارد یک سیگنال صریح "پایان وقفه" (EOI) از کنترل کننده وقفه ما دریافت کند. این سیگنال به PIC می گوید که وقفه پردازش شده و سیستم آماده دریافت وقفه بعدی است. بنابراین PIC فکر می کند ما هنوز مشغول پردازش وقفه تایمر اول هستیم و قبل از ارسال سیگنال بعدی با صبر و حوصله منتظر سیگنال EOI از ما هست.
+
+برای ارسال EOI ، ما دوباره از ساختمان ثابت `PICS` خود استفاده می کنیم:
+
+```rust
+// in src/interrupts.rs
+
+extern "x86-interrupt" fn timer_interrupt_handler(
+ _stack_frame: &mut InterruptStackFrame)
+{
+ print!(".");
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
+ }
+}
+```
+
+`notify_end_of_interrupt` تشخیص میدهد که PIC اصلی یا ثانویه وقفه را ارسال کرده است و سپس از پورت های `command` و `data` برای ارسال سیگنال EOI به PIC های مربوطه استفاده می کند. اگر PIC ثانویه وقفه را ارسال کرد ، هر دو PIC باید مطلع شوند زیرا PIC ثانویه به یک خط ورودی از PIC اصلی متصل است.
+
+ما باید مراقب باشیم که از شماره بردار وقفه صحیح استفاده کنیم، در غیر این صورت می توانیم به طور تصادفی یک وقفه مهم ارسال نشده را حذف کنیم یا باعث هنگ سیستم خود شویم. این دلیل آن است که تابع ناامن است.
+
+اکنون هنگامی که `cargo run` را اجرا می کنیم، نقاطی را می بینیم که به صورت دوره ای روی صفحه ظاهر می شوند:
+
+
+
+### پیکربندی تایمر
+
+تایمر سخت افزاری که ما از آن استفاده می کنیم ، _Progammable Interval Timer_ یا به اختصار PIT نامیده می شود. همانطور که از نام آن مشخص است ، می توان فاصله بین دو وقفه را پیکربندی کرد. ما در اینجا به جزئیات نمی پردازیم زیرا به زودی به [تایمر APIC] سوییچ خواهیم کرد، اما ویکی OSDev مقاله مفصلی درباره [پیکربندی PIT] دارد.
+
+[تایمر APIC]: https://wiki.osdev.org/APIC_timer
+[پیکربندی PIT]: https://wiki.osdev.org/Programmable_Interval_Timer
+
+## بنبست ها
+
+اکنون نوعی همروندی در هسته خود داریم: وقفه های تایمر به صورت ناهمزمان اتفاق می افتند ، بنابراین می توانند تابع `start_` را در هر زمان قطع کنند. خوشبختانه سیستم مالکیت راست از بسیاری از مشکلات مربوط به همروندی در زمان کامپایل جلوگیری می کند. یک استثنا قابل توجه بنبست است. درصورتی که نخ(Thread) بخواهد قفلی را بدست آورد که هرگز آزاد نخواهد شد، بنبست به وجود می آید. بنابراین نخ به طور نامحدود هنگ میکند.
+
+ما می توانیم در هسته خود بنبست ایجاد کنیم. اگر به یاد داشته باشید، ماکرو `println` ما تابع `vga_buffer::_print` را فراخوانی می کند، که با استفاده از spinlock یک [`WRITER` گلوبال را قفل میکند][vga spinlock].
+
+[vga spinlock]: @/edition-2/posts/03-vga-text-buffer/index.md#spinlocks
+
+```rust
+// in src/vga_buffer.rs
+
+[…]
+
+#[doc(hidden)]
+pub fn _print(args: fmt::Arguments) {
+ use core::fmt::Write;
+ WRITER.lock().write_fmt(args).unwrap();
+}
+```
+
+`WRITER` را قفل می کند، `write_fmt` را روی آن فراخوانی می کند و در انتهای تابع به طور ضمنی قفل آن را باز می کند. حال تصور کنید که در حالی که `WRITER` قفل شده است وقفه رخ دهد و کنترل کننده وقفه نیز سعی کند چیزی را چاپ کند:
+
+Timestep | _start | interrupt_handler
+---------|------|------------------
+0 | calls `println!` |
+1 | `print` locks `WRITER` |
+2 | | **interrupt occurs**, handler begins to run
+3 | | calls `println!` |
+4 | | `print` tries to lock `WRITER` (already locked)
+5 | | `print` tries to lock `WRITER` (already locked)
+… | | …
+_never_ | _unlock `WRITER`_ |
+
+`WRITER` قفل شده است ، بنابراین کنترل کننده وقفه منتظر می ماند تا آزاد شود. اما این هرگز اتفاق نمی افتد ، زیرا تابع `start_` فقط پس از بازگشت کنترل کننده وقفه ادامه می یابد. بنابراین کل سیستم هنگ است.
+
+### ایجاد بنبست
+
+ما می توانیم با چاپ چیزی در حلقه در انتهای تابع `start_` خود ، به راحتی چنین بنبستای در هسته خود ایجاد کنیم:
+
+```rust
+// in src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ […]
+ loop {
+ use blog_os::print;
+ print!("-"); // new
+ }
+}
+```
+
+وقتی آن را در QEMU اجرا می کنیم ، خروجی به حالت زیر دریافت میکنیم:
+
+
+
+می بینیم که فقط تعداد محدودی خط فاصله ، تا زمانی که وقفه تایمر اول اتفاق بیفتد، چاپ می شود. سپس سیستم هنگ میکند زیرا تایمر هنگام تلاش برای چاپ یک نقطه باعث بنبست میشود. به همین دلیل است که در خروجی فوق هیچ نقطهای مشاهده نمیکنیم.
+
+تعداد واقعی خط فاصله بین هر اجرا متفاوت است زیرا وقفه تایمر به صورت غیر همزمان انجام می شود. این عدم قطعیت، اشکال زدایی اشکالات مربوط به همروندی را بسیار دشوار می کند.
+
+### رفع بنبست
+
+برای جلوگیری از این بنبست ، تا زمانی که `Mutex` قفل شده باشد، می توانیم وقفهها را غیرفعال کنیم:
+
+```rust
+// in src/vga_buffer.rs
+
+/// Prints the given formatted string to the VGA text buffer
+/// through the global `WRITER` instance.
+#[doc(hidden)]
+pub fn _print(args: fmt::Arguments) {
+ use core::fmt::Write;
+ use x86_64::instructions::interrupts; // new
+
+ interrupts::without_interrupts(|| { // new
+ WRITER.lock().write_fmt(args).unwrap();
+ });
+}
+```
+
+تابع [`without_interrupts`] یک [کلوژر] را گرفته و آن را در یک محیط بدون وقفه اجرا می کند. ما از آن استفاده می کنیم تا اطمینان حاصل کنیم که تا زمانی که `Mutex` قفل شده است ، هیچ وقفه ای رخ نمی دهد. اکنون هنگامی که هسته را اجرا می کنیم ، می بینیم که آن بدون هنگ کردن به کار خود ادامه می دهد. (ما هنوز هیچ نقطه ای را مشاهده نمی کنیم ، اما این به این دلیل است که سرعت حرکت آنها بسیار سریع است. سعی کنید سرعت چاپ را کم کنید، مثلاً با قرار دادن `for _ in 0..10000 {}` در داخل حلقه.)
+
+[`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
+
+ما می توانیم همین تغییر را در تابع چاپ سریال نیز اعمال کنیم تا اطمینان حاصل کنیم که هیچ بنبستی در آن رخ نمی دهد:
+
+```rust
+// in src/serial.rs
+
+#[doc(hidden)]
+pub fn _print(args: ::core::fmt::Arguments) {
+ use core::fmt::Write;
+ use x86_64::instructions::interrupts; // new
+
+ interrupts::without_interrupts(|| { // new
+ SERIAL1
+ .lock()
+ .write_fmt(args)
+ .expect("Printing to serial failed");
+ });
+}
+```
+
+توجه داشته باشید که غیرفعال کردن وقفهها نباید یک راه حل کلی باشد. مشکل این است که بدترین حالت تأخیر در وقفه را افزایش می دهد ، یعنی زمانی که سیستم به وقفه واکنش نشان می دهد. بنابراین وقفهها باید فقط برای مدت زمان کوتاه غیرفعال شوند.
+
+## رفع وضعیت رقابتی
+
+اگر `cargo test` را اجرا کنید ، ممکن است ببینید تست `test_println_output` با شکست مواجه میشود:
+
+```
+> cargo test --lib
+[…]
+Running 4 tests
+test_breakpoint_exception...[ok]
+test_println... [ok]
+test_println_many... [ok]
+test_println_output... [failed]
+
+Error: panicked at 'assertion failed: `(left == right)`
+ left: `'.'`,
+ right: `'S'`', src/vga_buffer.rs:205:9
+```
+
+دلیل آن وجود یک _وضعیت رقابتی_ بین تست و کنترل کننده تایمر ماست. اگر به یاد داشته باشید ، تست به این شکل است:
+
+```rust
+// in src/vga_buffer.rs
+
+#[test_case]
+fn test_println_output() {
+ let s = "Some test string that fits on a single line";
+ println!("{}", s);
+ for (i, c) in s.chars().enumerate() {
+ let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read();
+ assert_eq!(char::from(screen_char.ascii_character), c);
+ }
+}
+```
+
+این تست یک رشته را در بافر VGA چاپ می کند و سپس با پیمایش دستی روی آرایه `buffer_chars` خروجی را بررسی می کند. وضعیت رقابتی رخ می دهد زیرا ممکن است کنترل کننده وقفه تایمر بین `println` و خواندن کاراکتر های صفحه اجرا شود. توجه داشته باشید که این یک رقابت داده(Data race) خطرناک نیست، که Rust در زمان کامپایل کاملاً از آن جلوگیری کند. برای جزئیات به [_Rustonomicon_][nomicon-races] مراجعه کنید.
+
+[nomicon-races]: https://doc.rust-lang.org/nomicon/races.html
+
+برای رفع این مشکل ، باید `WRITER` را برای مدت زمان کامل تست قفل نگه داریم ، به این ترتیب که کنترل کننده تایمر نمی تواند `.` را روی صفحه نمایش در میان کار تست بنویسد. تست اصلاح شده به این شکل است:
+
+```rust
+// in src/vga_buffer.rs
+
+#[test_case]
+fn test_println_output() {
+ use core::fmt::Write;
+ use x86_64::instructions::interrupts;
+
+ let s = "Some test string that fits on a single line";
+ interrupts::without_interrupts(|| {
+ let mut writer = WRITER.lock();
+ writeln!(writer, "\n{}", s).expect("writeln failed");
+ for (i, c) in s.chars().enumerate() {
+ let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read();
+ assert_eq!(char::from(screen_char.ascii_character), c);
+ }
+ });
+}
+```
+
+ما تغییرات زیر را انجام دادیم:
+
+- ما با استفاده صریح از متد `()lock` ، نویسنده را برای کل تست قفل می کنیم. به جای `println` ، از ماکرو [`writeln`] استفاده می کنیم که امکان چاپ بر روی نویسنده قبلاً قفل شده را فراهم می کند.
+- برای جلوگیری از یک بنبست دیگر ، وقفهها را برای مدت زمان تست غیرفعال می کنیم. در غیر این صورت ممکن است تست در حالی که نویسنده هنوز قفل است قطع شود.
+- از آنجا که کنترل کننده وقفه تایمر هنوز می تواند قبل از تست اجرا شود ، قبل از چاپ رشته `s` یک خط جدید `n\` اضافی چاپ می کنیم. به این ترتیب ، اگر که کنترل کننده تایمر تعدادی کاراکتر `.` را در خط فعلی چاپ کرده باشد، از شکست تست جلوگیری می کنیم.
+
+[`writeln`]: https://doc.rust-lang.org/core/macro.writeln.html
+
+اکنون با تغییرات فوق ، `cargo test` دوباره با قطعیت موفق می شود.
+
+این یک وضعیت رقابتی بسیار بی خطر بود که فقط باعث شکست تست میشد. همانطور که می توانید تصور کنید، اشکال زدایی سایر وضعیتهای رقابتی به دلیل ماهیت غیر قطعی بودن آنها بسیار دشوارتر است. خوشبختانه، راست مانع از رقابت دادهها می شود ، که جدیترین نوع وضعیت رقابتی است ، زیرا می تواند باعث انواع رفتارهای تعریف نشده ، از جمله کرش کردن سیستم و خراب شدن آرام و بی صدای حافظه شود.
+
+## دستورالعمل `hlt`
+
+تاکنون از یک حلقه خالی ساده در پایان توابع `start_` و` panic` استفاده می کردیم. این باعث می شود پردازنده به طور بی وقفه بچرخد و بنابراین مطابق انتظار عمل می کند. اما بسیار ناکارآمد است، زیرا پردازنده همچنان با سرعت کامل کار می کند حتی اگر کاری برای انجام نداشته باشد. هنگامی که هسته را اجرا می کنید می توانید این مشکل را در مدیر وظیفه خود مشاهده کنید: فرایند QEMU در کل مدت زمان نیاز به تقریباً 100٪ پردازنده دارد.
+
+کاری که واقعاً می خواهیم انجام دهیم این است که پردازنده را تا رسیدن وقفه بعدی متوقف کنیم. این اجازه می دهد پردازنده وارد حالت خواب شود که در آن انرژی بسیار کمتری مصرف می کند. [دستورالعمل `hlt`] دقیقاً همین کار را می کند. بیایید از این دستورالعمل برای ایجاد یک حلقه بی پایان با مصرف انرژی پایین استفاده کنیم:
+
+[دستورالعمل `hlt`]: https://en.wikipedia.org/wiki/HLT_(x86_instruction)
+
+```rust
+// in src/lib.rs
+
+pub fn hlt_loop() -> ! {
+ loop {
+ x86_64::instructions::hlt();
+ }
+}
+```
+
+تابع `instructions::hlt` فقط یک [پوشش نازک] بر روی دستورالعمل اسمبلی است. این بی خطر است زیرا به هیچ وجه نمی تواند ایمنی حافظه را به خطر بیندازد.
+
+[پوشش نازک]: https://github.com/rust-osdev/x86_64/blob/5e8e218381c5205f5777cb50da3ecac5d7e3b1ab/src/instructions/mod.rs#L16-L22
+
+اکنون می توانیم از این `hlt_loop` به جای حلقه های بی پایان در توابع` start_` و `panic` استفاده کنیم:
+
+```rust
+// in src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ […]
+
+ println!("It did not crash!");
+ blog_os::hlt_loop(); // new
+}
+
+
+#[cfg(not(test))]
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ println!("{}", info);
+ blog_os::hlt_loop(); // new
+}
+
+```
+
+بیایید `lib.rs` را نیز به روز کنیم:
+
+```rust
+// in src/lib.rs
+
+/// Entry point for `cargo test`
+#[cfg(test)]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ init();
+ test_main();
+ hlt_loop(); // new
+}
+
+pub fn test_panic_handler(info: &PanicInfo) -> ! {
+ serial_println!("[failed]\n");
+ serial_println!("Error: {}\n", info);
+ exit_qemu(QemuExitCode::Failed);
+ hlt_loop(); // new
+}
+```
+
+اکنون وقتی هسته خود را در QEMU اجرا می کنیم ، شاهد استفاده بسیار کمتری از پردازنده هستیم.
+
+## ورودی صفحه کلید
+
+اکنون که قادر به مدیریت وقفه های دستگاه های خارجی هستیم ، سرانجام قادر به پشتیبانی از ورودی صفحه کلید هستیم. این به ما امکان می دهد برای اولین بار با هسته خود تعامل داشته باشیم.
+
+
+
+[PS/2]: https://en.wikipedia.org/wiki/PS/2_port
+
+مانند تایمر سخت افزاری ، کنترل کننده صفحه کلید نیز به طور پیش فرض از قبل فعال شده است. بنابراین با فشار دادن یک کلید ، کنترل کننده صفحه کلید وقفه را به PIC ارسال می کند و آن را به پردازنده منتقل می کند. پردازنده به دنبال یک تابع کنترل کننده در IDT میگردد ، اما ایتم مربوطه خالی است. بنابراین یک خطای دوگانه رخ می دهد.
+
+پس بیایید یک تایع کنترل کننده برای وقفه صفحه کلید اضافه کنیم. این کاملاً مشابه نحوه تعریف کنترل کننده برای وقفه تایمر است ، فقط از یک شماره وقفه متفاوت استفاده می کند:
+
+```rust
+// in src/interrupts.rs
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u8)]
+pub enum InterruptIndex {
+ Timer = PIC_1_OFFSET,
+ Keyboard, // new
+}
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ […]
+ // new
+ idt[InterruptIndex::Keyboard.as_usize()]
+ .set_handler_fn(keyboard_interrupt_handler);
+
+ idt
+ };
+}
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: &mut InterruptStackFrame)
+{
+ print!("k");
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
+ }
+}
+```
+
+همانطور که در شکل [بالا](#the-8259-pic) مشاهده می کنیم ، صفحه کلید از خط 1 در PIC اصلی استفاده می کند. این به این معنی است که به صورت وقفه 33 (1 + افست 32) به پردازنده می رسد. ما این ایندکس را به عنوان یک نوع جدید `Keyboard` به اینام `InterruptIndex` اضافه می کنیم. نیازی نیست که مقدار را صریحاً مشخص کنیم ، زیرا این مقدار به طور پیش فرض برابر مقدار قبلی بعلاوه یک که 33 نیز می باشد ، هست. در کنترل کننده وقفه ، ما یک `k` چاپ می کنیم و سیگنال پایان وقفه را به کنترل کننده وقفه می فرستیم.
+
+اکنون می بینیم که وقتی کلید را فشار می دهیم `k` بر روی صفحه ظاهر می شود. با این حال ، این فقط برای اولین کلیدی که فشار می دهیم کار می کند ، حتی اگر به فشار دادن کلیدها ادامه دهیم ، دیگر `k` بر روی صفحه نمایش ظاهر نمی شود. این امر به این دلیل است که کنترل کننده صفحه کلید تا زمانی که اصطلاحاً _scancode_ را نخوانیم ، وقفه دیگری ارسال نمی کند.
+
+### خواندن اسکنکد ها
+
+برای اینکه بفهمیم _کدام_ کلید فشار داده شده است ، باید کنترل کننده صفحه کلید را جستجو کنیم. ما این کار را با خواندن از پورت داده کنترل کننده PS/2 ، که [پورت ورودی/خروجی] با شماره `0x60` است ، انجام می دهیم:
+
+[پورت ورودی/خروجی]: @/edition-2/posts/04-testing/index.md#i-o-ports
+
+```rust
+// in src/interrupts.rs
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: &mut InterruptStackFrame)
+{
+ use x86_64::instructions::port::Port;
+
+ let mut port = Port::new(0x60);
+ let scancode: u8 = unsafe { port.read() };
+ print!("{}", scancode);
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
+ }
+}
+```
+
+ما برای خواندن یک بایت از پورت داده صفحه کلید از نوع [`Port`] کرت `x86_64` استفاده میکنیم. این بایت [_اسکن کد_] نامیده می شود و عددی است که کلید فشرده شده / رها شده را نشان می دهد. ما هنوز کاری با اسکن کد انجام نمی دهیم ، فقط آن را روی صفحه چاپ می کنیم:
+
+[`Port`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/port/struct.Port.html
+[_اسکن کد_]: https://en.wikipedia.org/wiki/Scancode
+
+
+
+تصویر بالا نشان می دهد که من آرام آرام "123" را تایپ می کنم. می بینیم که کلیدهای مجاور دارای اسکن کد مجاور هستند و فشار دادن یک کلید دارای اسکن کد متفاوت با رها کردن آن است. اما چگونه اسکنکدها را دقیقاً به کار اصلی آن کلید ترجمه کنیم؟
+
+### تفسیر اسکنکد ها
+سه استاندارد مختلف برای نگاشت بین اسکن کدها و کلیدها وجود دارد ، اصطلاحاً _مجموعه های اسکن کد_. هر سه به صفحه کلید رایانه های اولیه IBM برمی گردند: [IBM XT] ، [IBM 3270 PC] و [IBM AT]. خوشبختانه رایانه های بعدی روند تعریف مجموعه های جدید اسکن کد را ادامه ندادند ، بلکه مجموعه های موجود را تقلید و آنها را گسترش دادند. امروزه بیشتر صفحه کلیدها را می توان به گونه ای پیکربندی کرد که از هر کدام از سه مجموعه تقلید کند.
+
+[IBM XT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer_XT
+[IBM 3270 PC]: https://en.wikipedia.org/wiki/IBM_3270_PC
+[IBM AT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer/AT
+
+به طور پیش فرض ، صفحه کلیدهای PS/2 مجموعه شماره 1 ("XT") را تقلید می کنند. در این مجموعه ، 7 بیت پایین بایت اسکنکد، کلید را تعریف می کند و مهمترین بیت فشردن ("0") یا رها کردن ("1") را مشخص می کند. کلیدهایی که در صفحه کلید اصلی [IBM XT] وجود نداشتند ، مانند کلید enter روی کیپد ، دو اسکن کد به طور متوالی ایجاد می کنند: یک بایت فرار(escape) `0xe0` و سپس یک بایت نمایانگر کلید. برای مشاهده لیست تمام اسکنکدهای مجموعه 1 و کلیدهای مربوط به آنها ، [ویکی OSDev][scancode set 1] را مشاهده کنید.
+
+[scancode set 1]: https://wiki.osdev.org/Keyboard#Scan_Code_Set_1
+
+برای ترجمه اسکن کدها به کلیدها ، می توانیم از عبارت match استفاده کنیم:
+
+```rust
+// in src/interrupts.rs
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: &mut InterruptStackFrame)
+{
+ use x86_64::instructions::port::Port;
+
+ let mut port = Port::new(0x60);
+ let scancode: u8 = unsafe { port.read() };
+
+ // new
+ let key = match scancode {
+ 0x02 => Some('1'),
+ 0x03 => Some('2'),
+ 0x04 => Some('3'),
+ 0x05 => Some('4'),
+ 0x06 => Some('5'),
+ 0x07 => Some('6'),
+ 0x08 => Some('7'),
+ 0x09 => Some('8'),
+ 0x0a => Some('9'),
+ 0x0b => Some('0'),
+ _ => None,
+ };
+ if let Some(key) = key {
+ print!("{}", key);
+ }
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
+ }
+}
+```
+
+کد بالا فشردن کلیدهای عددی 9-0 را ترجمه کرده و کلیه کلیدهای دیگر را نادیده می گیرد. از عبارت [match] برای اختصاص یک کاراکتر یا `None` به هر اسکن کد استفاده می کند. سپس با استفاده از [`if let`] اپشن `key` را از بین می برد. با استفاده از همان نام متغیر `key` در الگو که یک روش معمول برای از بین بردن انواع`Option` در راست است تعریف قبلی را [سایه می زنیم].
+
+[match]: https://doc.rust-lang.org/book/ch06-02-match.html
+[`if let`]: https://doc.rust-lang.org/book/ch18-01-all-the-places-for-patterns.html#conditional-if-let-expressions
+[سایه می زنیم]: https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing
+
+اکنون می توانیم اعداد را بنویسیم:
+
+
+
+ترجمه کلیدهای دیگر نیز به همین روش کار می کند. خوشبختانه کرت ای با نام [`pc-keyboard`] برای ترجمه اسکنکد مجموعه های اسکنکد 1 و 2 وجود دارد ، بنابراین لازم نیست که خودمان این را پیاده سازی کنیم. برای استفاده از کرت ، آن را به `Cargo.toml` اضافه کرده و در`lib.rs` خود وارد می کنیم:
+
+[`pc-keyboard`]: https://docs.rs/pc-keyboard/0.5.0/pc_keyboard/
+
+```toml
+# in Cargo.toml
+
+[dependencies]
+pc-keyboard = "0.5.0"
+```
+
+اکنون میتوانیم از این کرت برای باز نویسی `keyboard_interrupt_handler` استفاده کنیم:
+
+```rust
+// in/src/interrupts.rs
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: &mut InterruptStackFrame)
+{
+ use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
+ use spin::Mutex;
+ use x86_64::instructions::port::Port;
+
+ lazy_static! {
+ static ref KEYBOARD: Mutex> =
+ Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1,
+ HandleControl::Ignore)
+ );
+ }
+
+ let mut keyboard = KEYBOARD.lock();
+ let mut port = Port::new(0x60);
+
+ let scancode: u8 = unsafe { port.read() };
+ if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
+ if let Some(key) = keyboard.process_keyevent(key_event) {
+ match key {
+ DecodedKey::Unicode(character) => print!("{}", character),
+ DecodedKey::RawKey(key) => print!("{:?}", key),
+ }
+ }
+ }
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
+ }
+}
+```
+
+ما از ماکرو `lazy_static` برای ایجاد یک شی ثابت [`Keyboard`] محافظت شده توسط Mutex استفاده می کنیم. `Keyboard` را با طرح صفحه کلید ایالات متحده و مجموعه اسکن کد 1 مقداردهی می کنیم. پارامتر [`HandleControl`] اجازه می دهد تا `ctrl+[a-z]` را به کاراکتر های `U+0001` تا `U+001A` نگاشت کنیم. ما نمی خواهیم چنین کاری انجام دهیم ، بنابراین از گزینه `Ignore` برای برخورد با `ctrl` مانند کلیدهای عادی استفاده می کنیم.
+
+[`HandleControl`]: https://docs.rs/pc-keyboard/0.5.0/pc_keyboard/enum.HandleControl.html
+
+در هر وقفه ، Mutex را قفل می کنیم ، اسکن کد را از کنترل کننده صفحه کلید می خوانیم و آن را به متد [`add_byte`] منتقل می کنیم ، که اسکن کد را به یک `
{% endif %}
+
{%- if page.lang != "en" %}
-
+
{% set translations = page.translations | filter(attribute="lang", value="en") %}
{% set original = translations.0 %}
- Translated Content:
- This is a community translation of the {{ original.title }} post. It might be incomplete, outdated or contain errors. Please report any issues!
+ {{ trans(key="translated_content", lang=lang) }}
+ {{ trans(key="translated_content_notice", lang=lang) |
+ replace(from="_original.permalink_", to=original.permalink) |
+ replace(from="_original.title_", to=original.title) | safe }}
{%- if page.extra.translators %}
- Translation by {% for user in page.extra.translators -%}
+ {{ trans(key="translated_by", lang=lang) }} {% for user in page.extra.translators -%}
{%- if not loop.first -%}
- {%- if loop.last %}, and {% else %}, {% endif -%}
+ {%- if loop.last %} {{ trans(key="word_separator", lang=lang) }} {% else %}, {% endif -%}
{%- endif -%}
@{{user}}
{%- endfor %}.
@@ -70,11 +73,11 @@