diff --git a/blog/content/edition-2/posts/10-heap-allocation/index.ja.md b/blog/content/edition-2/posts/10-heap-allocation/index.ja.md index be9a8344..cd72a2f2 100644 --- a/blog/content/edition-2/posts/10-heap-allocation/index.ja.md +++ b/blog/content/edition-2/posts/10-heap-allocation/index.ja.md @@ -102,7 +102,7 @@ fn inner(i: usize) -> &'static u32 { [`ptr::write`]: https://doc.rust-lang.org/core/ptr/fn.write.html [`offset`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset -割り当てられたメモリは`deallocate`の呼び出しによって明示的に解放されるまで生存します。したがって、返されたポインタは`inner`がリターンし、コールスタックの対応する部分が破棄された後も有効です。スタティックメモリと比較したときのヒープメモリの長所は、解放後に再利用できると言うことです(`outer`内の`deallocate`呼び出しでまさにこれを行っています)。この呼び出しの後、状況は以下のようになります。 +割り当てられたメモリは`deallocate`の呼び出しによって明示的に解放されるまで生存します。したがって、返されたポインタは、`inner`がリターンしコールスタックの対応する部分が破棄された後も有効です。スタティックメモリと比較したときのヒープメモリの長所は、解放後に再利用できると言うことです(`outer`内の`deallocate`呼び出しでまさにこれを行っています)。この呼び出しの後、状況は以下のようになります。 ![The call stack contains the local variables of outer, the heap contains z[0] and z[2], but no longer z[1].](call-stack-heap-freed.svg) @@ -110,26 +110,26 @@ fn inner(i: usize) -> &'static u32 { ### Common Errors -Apart from memory leaks, which are unfortunate but don't make the program vulnerable to attackers, there are two common types of bugs with more severe consequences: +メモリリークは困りものですが、プログラムを攻撃者に対して脆弱にはしません。しかしこのほかに、より深刻な結果を招く二種類のバグが存在します: -- When we accidentally continue to use a variable after calling `deallocate` on it, we have a so-called **use-after-free** vulnerability. Such a bug causes undefined behavior and can often be exploited by attackers to execute arbitrary code. -- When we accidentally free a variable twice, we have a **double-free** vulnerability. This is problematic because it might free a different allocation that was allocated in the same spot after the first `deallocate` call. Thus, it can lead to an use-after-free vulnerability again. +- もし変数に対して`deallocate`を呼んだ後にも間違ってそれを使い続けたら、いわゆるuse-after-free (メモリ解放後に使用) 脆弱性が発生します。このようなバグは未定義動作を引き起こし、しばしば攻撃者が任意コードを実行するのに利用されます。 +- 間違ってある変数を二度解放したら、double-free (二重解放) 脆弱性が発生します。これが問題になるのは、最初の`deallocate`呼び出しの後に同じ場所にallocateされた別の割り当てを解放してしまうかもしれないからです。従って、これもまたuse-after-free脆弱性につながりかねません。 -These types of vulnerabilities are commonly known, so one might expect that people learned how to avoid them by now. But no, such vulnerabilities are still regularly found, for example this recent [use-after-free vulnerability in Linux][linux vulnerability] that allowed arbitrary code execution. This shows that even the best programmers are not always able to correctly handle dynamic memory in complex projects. +これらの種類の脆弱性は広く知られているため、回避する方法も分かっているはずだとお思いになるかもしれません。しかし答えはいいえで、このような脆弱性は未だ散見され、例えば最近でも任意コード実行を許す[Linuxのuse-after-free脆弱性][linux vulnerability]が存在しました。このことは、最高のプログラマーであっても、複雑なプロジェクトにおいて常に正しく動的メモリを扱えはしないと言うことを示しています。 [linux vulnerability]: https://securityboulevard.com/2019/02/linux-use-after-free-vulnerability-found-in-linux-2-6-through-4-20-11/ -To avoid these issues, many languages such as Java or Python manage dynamic memory automatically using a technique called [_garbage collection_]. The idea is that the programmer never invokes `deallocate` manually. Instead, the program is regularly paused and scanned for unused heap variables, which are then automatically deallocated. Thus, the above vulnerabilities can never occur. The drawbacks are the performance overhead of the regular scan and the probably long pause times. +これらの問題を回避するため、JavaやPythonといった多くの言語では[**ガベージコレクション**][_garbage collection_]という技法を使って自動的に動的メモリを管理しています。発想としては、プログラマが絶対に自分の手で`deallocate`を呼び出すことがないようにするというものです。代わりに、プログラムが定期的に一時停止されてスキャンされ、未使用のヒープ変数が見つかったら自動的にdeallocateされるのです。従って、上のような脆弱性は絶対に発生し得ません。欠点としては,定期的にスキャンすることによる性能のオーバーヘッドが発生することと、一時停止の時間が長くなりうるということがあります。 [_garbage collection_]: https://en.wikipedia.org/wiki/Garbage_collection_(computer_science) -Rust takes a different approach to the problem: It uses a concept called [_ownership_] that is able to check the correctness of dynamic memory operations at compile time. Thus no garbage collection is needed to avoid the mentioned vulnerabilities, which means that there is no performance overhead. Another advantage of this approach is that the programmer still has fine-grained control over the use of dynamic memory, just like with C or C++. +Rustはこの問題に対して別のアプローチを取ります:[**所有権**][_ownership_]と呼ばれる概念を使って、動的メモリの操作の正確性をコンパイル時にチェックするのです。従って前述の脆弱性を回避するためのガベージコレクションの必要がなく、性能のオーバーヘッドが存在しないと言うことです。このアプローチのもう一つの利点として、CやC++と同様、プログラマが動的メモリの使用に関して精緻な制御を行うことができるということが挙げられます。 [_ownership_]: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html -### Allocations in Rust +### Rustにおける割り当て -Instead of letting the programmer manually call `allocate` and `deallocate`, the Rust standard library provides abstraction types that call these functions implicitly. The most important type is [**`Box`**], which is an abstraction for a heap-allocated value. It provides a [`Box::new`] constructor function that takes a value, calls `allocate` with the size of the value, and then moves the value to the newly allocated slot on the heap. To free the heap memory again, the `Box` type implements the [`Drop` trait] to call `deallocate` when it goes out of scope: +プログラマーに自分の手で`allocate`と`deallocate`を呼ばせる代わりに、Rustの標準ライブラリはこれらの関数を暗黙の内に呼ぶ抽象型を提供しています。最も重要な型は[**`Box`**]で、これはヒープに割り当てられた値の抽象化です。これは[`Box::new`]コンストラクタ関数を提供しており、これは値を引数として、その値のサイズを引数に`allocate`を呼び出し、ヒープ上に新しく割り当てられたスロットにその値を移動 (ムーブ) します。ヒープメモリを再び解放するために、`Box`型は自身がスコープから出た際に`deallocate`を呼べるよう[`Drop`トレイト][`Drop` trait]を実装しています。 [**`Box`**]: https://doc.rust-lang.org/std/boxed/index.html [`Box::new`]: https://doc.rust-lang.org/alloc/boxed/struct.Box.html#method.new @@ -139,25 +139,25 @@ Instead of letting the programmer manually call `allocate` and `deallocate`, the { let z = Box::new([1,2,3]); […] -} // z goes out of scope and `deallocate` is called +} // zがスコープから出て`deallocate`が呼ばれる ``` -This pattern has the strange name [_resource acquisition is initialization_] (or _RAII_ for short). It originated in C++, where it is used to implement a similar abstraction type called [`std::unique_ptr`]. +このようなパターンは[リソース確保が初期化である][_resource acquisition is initialization_](resource acquisition is initialization、略してRAII)という奇妙な名前を持っています。このパターンは、C++で[`std::unique_ptr`]という似た抽象型を実装するのに使われたのが起源です。 [_resource acquisition is initialization_]: https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization [`std::unique_ptr`]: https://en.cppreference.com/w/cpp/memory/unique_ptr -Such a type alone does not suffice to prevent all use-after-free bugs since programmers can still hold on to references after the `Box` goes out of scope and the corresponding heap memory slot is deallocated: +このような型自体はすべてのuse-after-freeバグを防ぐのに十分ではありません。なぜなら、プログラマは、`Box`がスコープ外に出て対応するヒープメモリスロットがdeallocateされた後でも参照を利用し続けることができるからです: ```rust let x = { let z = Box::new([1,2,3]); &z[1] -}; // z goes out of scope and `deallocate` is called +}; // zがスコープから出て`deallocate`が呼ばれる println!("{}", x); ``` -This is where Rust's ownership comes in. It assigns an abstract [lifetime] to each reference, which is the scope in which the reference is valid. In the above example, the `x` reference is taken from the `z` array, so it becomes invalid after `z` goes out of scope. When you [run the above example on the playground][playground-2] you see that the Rust compiler indeed throws an error: +ここでRustの所有権の出番です。所有権システムは、参照が有効なスコープを表す抽象[ライフタイム][lifetime]をそれぞれの参照に指定します。上の例では、参照`x`は配列`z`から取られているので、`z`ガスコープ外に出ると無効になります。[上の例をplaygroundで実行する][playground-2]と、確かにRustコンパイラがエラーを投げるのが分かります: [lifetime]: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html [playground-2]: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=28180d8de7b62c6b4a681a7b1f745a48 @@ -175,31 +175,31 @@ error[E0597]: `z[_]` does not live long enough | - `z[_]` dropped here while still borrowed ``` -The terminology can be a bit confusing at first. Taking a reference to a value is called _borrowing_ the value since it's similar to a borrow in real life: You have temporary access to an object but need to return it sometime and you must not destroy it. By checking that all borrows end before an object is destroyed, the Rust compiler can guarantee that no use-after-free situation can occur. +ここで使われている用語は初見では少しわかりにくいかもしれません。値の参照を取ることは値を借用する (borrow) と呼ばれています。これは現実での借用と似ているためです:オブジェクトへの一時的なアクセスができるようになりますが、それをいつか返さなければならず、また破壊することも許されません。オブジェクトが破壊される前にすべての借用が終了することを確かめることにより、Rustコンパイラはuse-after-freeが起こりえないことを保証できるのです。 -Rust's ownership system goes even further and does not only prevent use-after-free bugs, but provides complete [_memory safety_] like garbage collected languages like Java or Python do. Additionally, it guarantees [_thread safety_] and is thus even safer than those languages in multi-threaded code. And most importantly, all these checks happen at compile time, so there is no runtime overhead compared to hand written memory management in C. +Rustの所有権システムはさらに突き詰められており、use-after-freeバグを防ぐだけでなく、JavaやPythonのようなガベージコレクション型言語と同じ完全な[メモリ安全性 (セーフティ) ][_memory safety_]を提供しています。さらに[スレッド安全性 (セーフティ) ][_thread safety_]も保証されており、マルチスレッドのプログラムにおいてはこれらの言語よりもさらに安全です。さらに最も重要なことに、これらのチェックは全てコンパイル時に行われるため、C言語で手書きされたメモリ管理と比べても、実行時のオーバーヘッドはありません。 [_memory safety_]: https://en.wikipedia.org/wiki/Memory_safety [_thread safety_]: https://en.wikipedia.org/wiki/Thread_safety -### Use Cases +### 使い方 -We now know the basics of dynamic memory allocation in Rust, but when should we use it? We've come really far with our kernel without dynamic memory allocation, so why do we need it now? +Rustにおける動的メモリ割り当ての基礎を学んだわけですが、これをいつ使えば良いのでしょうか?私たちのカーネルは動的メモリ割り当てなしにこれだけやってこられたのに、どうして今になってこれが必要なのでしょうか? -First, dynamic memory allocation always comes with a bit of performance overhead, since we need to find a free slot on the heap for every allocation. For this reason local variables are generally preferable, especially in performance sensitive kernel code. However, there are cases where dynamic memory allocation is the best choice. +まず覚えておいて欲しいのは、動的メモリ割り当てには、割り当てを行うたびにヒープから空いているスロットを探してこないといけないので、少しだけ性能オーバーヘッドがあるということです。このため、特に性能が重要となるカーネルのプログラムにおいては、一般にローカル変数の方が好ましいです。しかし、動的メモリ割り当てが最良の選択肢であるようなケースも存在するのです。 -As a basic rule, dynamic memory is required for variables that have a dynamic lifetime or a variable size. The most important type with a dynamic lifetime is [**`Rc`**], which counts the references to its wrapped value and deallocates it after all references went out of scope. Examples for types with a variable size are [**`Vec`**], [**`String`**], and other [collection types] that dynamically grow when more elements are added. These types work by allocating a larger amount of memory when they become full, copying all elements over, and then deallocating the old allocation. +基本的なルールとして、動的メモリは動的なライフタイムや可変サイズを持つような変数に必要とされます。動的なライフタイムを持つ最も重要な型は[**`Rc`**]で、これはラップされた値に対する参照を数えておき、すべての参照がスコープから外れたらそれをdeallocateするというものです。可変サイズを持つ型の例には、[**`Vec`**]、[**`String`**]、その他の[コレクション型][collection types]といった、要素が追加されたときに動的に大きくなるような型が挙げられます。これらの型は、容量が一杯になると、より大きい量のメモリを割り当て、すべての要素をコピーし、古い割り当てをdeallocateすることにより対処します。 [**`Rc`**]: https://doc.rust-lang.org/alloc/rc/index.html [**`Vec`**]: https://doc.rust-lang.org/alloc/vec/index.html [**`String`**]: https://doc.rust-lang.org/alloc/string/index.html [collection types]: https://doc.rust-lang.org/alloc/collections/index.html -For our kernel we will mostly need the collection types, for example for storing a list of active tasks when implementing multitasking in future posts. +私たちのカーネルでは主にコレクション型を必要とし、例えば、将来の記事でマルチタスキングを実行するときにアクティブなタスクのリストを格納するために使います。 -## The Allocator Interface +## アロケータのインターフェース -The first step in implementing a heap allocator is to add a dependency on the built-in [`alloc`] crate. Like the [`core`] crate, it is a subset of the standard library that additionally contains the allocation and collection types. To add the dependency on `alloc`, we add the following to our `lib.rs`: +ヒープアロケータを実装するための最初のステップは、組み込みの[`alloc`]クレートへの依存関係を津一過することです。[`core`]クレートと同様、これは標準ライブラリのサブセットであり、アロケーション型やコレクション型を含んでいます。`alloc`への依存関係を追加するために、以下を`lib.rs`に追加します: [`alloc`]: https://doc.rust-lang.org/alloc/ [`core`]: https://doc.rust-lang.org/core/ @@ -210,9 +210,15 @@ The first step in implementing a heap allocator is to add a dependency on the bu extern crate alloc; ``` -Contrary to normal dependencies, we don't need to modify the `Cargo.toml`. The reason is that the `alloc` crate ships with the Rust compiler as part of the standard library, so the compiler already knows about the crate. By adding this `extern crate` statement, we specify that the compiler should try to include it. (Historically, all dependencies needed an `extern crate` statement, which is now optional). +通常の依存関係と異なり`Cargo.toml`を修正する必要はありません。その理由は、`alloc`クレートは標準ライブラリの一部としてRustコンパイラに同梱されているため、コンパイラはすでにこのクレートのことを知っているためです。この`extern crate`宣言を追加することで、コンパイラにこれをインクルードしようと試みるよう指定しています(昔はすべての依存関係が`extern crate`宣言を必要としていたのですが、いまは任意です)。 -Since we are compiling for a custom target, we can't use the precompiled version of `alloc` that is shipped with the Rust installation. Instead, we have to tell cargo to recompile the crate from source. We can do that, by adding it to the `unstable.build-std` array in our `.cargo/config.toml` file: +
+ +**訳注:** もう少し詳しく解説します。Rust 2018から通常のクレートに関しては`extern crate`が必要なくなりましたが、Rust自体に同梱されるsysrootと呼ばれるクレートに関しては、特殊な状況でしか必要ないものがあるため明示的な`extern crate`がない限りリンクされません。詳しくは、[edition guideの対応するページ](https://doc.rust-jp.rs/edition-guide/rust-2018/path-changes.html#%E3%81%95%E3%82%88%E3%81%86%E3%81%AA%E3%82%89extern-crate)をご覧ください。 + +
+ +カスタムターゲット向けにコンパイルしようとしているので、Rustインストール時に同梱されていたコンパイル済みの`alloc`を使用することはできません。代わりにcargoにこのクレートをソースから再コンパイルするよう命令する必要があります。これは、配列`unstable.build-std`を`.cargo/config.toml`ファイルに追加することで行えます。 ```toml # in .cargo/config.toml @@ -221,9 +227,9 @@ Since we are compiling for a custom target, we can't use the precompiled version build-std = ["core", "compiler_builtins", "alloc"] ```` -Now the compiler will recompile and include the `alloc` crate in our kernel. +これでコンパイラは`alloc`クレートを再コンパイルして私たちのカーネルにインクルードしてくれます。 -The reason that the `alloc` crate is disabled by default in `#[no_std]` crates is that it has additional requirements. We can see these requirements as errors when we try to compile our project now: +`alloc`クレートが`#[no_std]`なクレートで標準では無効化されている理由は、これが追加の要件を持っているからです。今私たちのプロジェクトをコンパイルしようとすると、その要件が何なのか分かります: ``` error: no global memory allocator found but one is required; link to std or add @@ -232,19 +238,19 @@ error: no global memory allocator found but one is required; link to std or add error: `#[alloc_error_handler]` function required, but not found ``` -The first error occurs because the `alloc` crate requires an heap allocator, which is an object that provides the `allocate` and `deallocate` functions. In Rust, heap allocators are described by the [`GlobalAlloc`] trait, which is mentioned in the error message. To set the heap allocator for the crate, the `#[global_allocator]` attribute must be applied to a `static` variable that implements the `GlobalAlloc` trait. +最初のエラーは、`alloc`クレートが、ヒープアロケータという`allocate`と`deallocate`関数を提供するオブジェクトを必要とするために発生します。Rustにおいては、ヒープアロケータの性質は[`GlobalAlloc`]トレイトによって定義されており、エラーメッセージでもそのことについて触れられています。クレートにヒープアロケータを設定するためには、`#[global_allocator]`属性を`GlobalAlloc`トレイトを実装する何らかの`static`変数に適用する必要があります。 -The second error occurs because calls to `allocate` can fail, most commonly when there is no more memory available. Our program must be able to react to this case, which is what the `#[alloc_error_handler]` function is for. +二つ目のエラーは、(主にメモリが不足している場合)`allocate`への呼び出しが失敗しうるために発生します。私たちのプログラムがこのケースに対処できるようになっている必要があり、そのために使われる関数が`#[alloc_error_handler]`なのです。 [`GlobalAlloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html -We will describe these traits and attributes in detail in the following sections. +次のセクションでこのトレイトと属性について説明します。 -### The `GlobalAlloc` Trait +### `GlobalAlloc`トレイト -The [`GlobalAlloc`] trait defines the functions that a heap allocator must provide. The trait is special because it is almost never used directly by the programmer. Instead, the compiler will automatically insert the appropriate calls to the trait methods when using the allocation and collection types of `alloc`. +[`GlobalAlloc`]トレイトはヒープアロケータの提供しなければならない関数を定義します。このトレイトは、プログラマが絶対に直接使わないという点において特別です。代わりに、`alloc`のアロケーション・コレクション型を使うときに、コンパイラがトレイトメソッドへの適切な呼び出しを自動的に挿入します。 -Since we will need to implement the trait for all our allocator types, it is worth taking a closer look at its declaration: +このトレイトを私たちのアロケータ型全てに実装しなければならないので、その宣言は詳しく見ておく価値があるでしょう: ```rust pub unsafe trait GlobalAlloc { @@ -261,32 +267,32 @@ pub unsafe trait GlobalAlloc { } ``` -It defines the two required methods [`alloc`] and [`dealloc`], which correspond to the `allocate` and `deallocate` functions we used in our examples: -- The [`alloc`] method takes a [`Layout`] instance as argument, which describes the desired size and alignment that the allocated memory should have. It returns a [raw pointer] to the first byte of the allocated memory block. Instead of an explicit error value, the `alloc` method returns a null pointer to signal an allocation error. This is a bit non-idiomatic, but it has the advantage that wrapping existing system allocators is easy, since they use the same convention. -- The [`dealloc`] method is the counterpart and responsible for freeing a memory block again. It receives two arguments, the pointer returned by `alloc` and the `Layout` that was used for the allocation. +このトレイトは[`alloc`]と[`dealloc`]という必須メソッドを定義しており、これは上の例で使った`allocate`と`deallocate`関数に相当します: +- [`alloc`]メソッドは[`Layout`]インスタンス(割り当てられたメモリの持つべきサイズとアラインメントを記述する)を引数として取ります。メソッドは割り当てられたメモリブロックの最初のバイトへの[生ポインタ][raw pointer]を返します。割り当てエラーが起きたことを示す際は、明示的なエラー値を返す代わりにヌルポインタを返します。このやり方は(Rustの)慣習とはやや外れていますが、同じ慣習に従っている既存のシステムのアロケータをラップするのが簡単になるという利点があります。 +- [`dealloc`]はその対で、メモリブロックを開放する役割を持ちます。このメソッドは、`alloc`によって返されたポインタと割り当ての際に使われた`Layout`という二つの引数を取ります。 [`alloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#tymethod.alloc [`dealloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#tymethod.dealloc [`Layout`]: https://doc.rust-lang.org/alloc/alloc/struct.Layout.html -The trait additionally defines the two methods [`alloc_zeroed`] and [`realloc`] with default implementations: +このトレイトは[`alloc_zeroed`]と[`realloc`]という二つのデフォルト実装付きメソッドも定義しています。 -- The [`alloc_zeroed`] method is equivalent to calling `alloc` and then setting the allocated memory block to zero, which is exactly what the provided default implementation does. An allocator implementation can override the default implementations with a more efficient custom implementation if possible. -- The [`realloc`] method allows to grow or shrink an allocation. The default implementation allocates a new memory block with the desired size and copies over all the content from the previous allocation. Again, an allocator implementation can probably provide a more efficient implementation of this method, for example by growing/shrinking the allocation in-place if possible. +- [`alloc_zeroed`]メソッドは`alloc`を呼んでから割り当てられたメモリブロックの値を0にするのに等しく、デフォルト実装もまさに同じことをしています。より効率的なカスタム実装がもしあるならば、デフォルト実装を上書きすることもできます。 +- [`realloc`]メソッドは割り当てたメモリを拡大したり縮小したりすることができます。デフォルト実装では、要求されたサイズの新しいメモリブロックを割り当て、以前のアロケーションから中身を全てコピーします。同じく、アロケータの実装でこのメソッドをより効率的にすることができるかもしれません。例えば、可能な場合はその場でアロケーションを拡大・縮小するなど。 [`alloc_zeroed`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#method.alloc_zeroed [`realloc`]: https://doc.rust-lang.org/alloc/alloc/trait.GlobalAlloc.html#method.realloc -#### Unsafety +#### Unsafe -One thing to notice is that both the trait itself and all trait methods are declared as `unsafe`: +トレイト自体とすべてのトレイトメソッドが`unsafe`として宣言されていることに気をつけましょう: -- The reason for declaring the trait as `unsafe` is that the programmer must guarantee that the trait implementation for an allocator type is correct. For example, the `alloc` method must never return a memory block that is already used somewhere else because this would cause undefined behavior. -- Similarly, the reason that the methods are `unsafe` is that the caller must ensure various invariants when calling the methods, for example that the `Layout` passed to `alloc` specifies a non-zero size. This is not really relevant in practice since the methods are normally called directly by the compiler, which ensures that the requirements are met. +- トレイトを`unsafe`として宣言する理由は、プログラマがアロケータ型のトレイト実装が正しいことを保証しなければならないからです。例えば、`alloc`メソッドは他のどこかですでに使用されているメモリブロックを決して返してはならず、もしそうなると未定義動作が発生してしまいます。 +- 同様に、メソッドが`unsafe`である理由は、メソッドを呼び出す際に呼び出し元がいくつかの不変条件を保証しなければならないからです。例えば、`alloc`に渡される`Layout`が非ゼロサイズを指定していることなどです。実際にはこれは大して重要ではなく、というのもこれらのメソッドはコンパイラによって直接呼び出されるため、この要件が満たされていることは保証されているからです。 -### A `DummyAllocator` +### `DummyAllocator` -Now that we know what an allocator type should provide, we can create a simple dummy allocator. For that we create a new `allocator` module: +アロケータ型が何を提供しないといけないかを理解したので、シンプルなダミー (ハリボテ) のアロケータを作ることができます。そのためまず新しく`allocator`モジュールを作りましょう: ```rust // in src/lib.rs @@ -294,7 +300,7 @@ Now that we know what an allocator type should provide, we can create a simple d pub mod allocator; ``` -Our dummy allocator does the absolute minimum to implement the trait and always return an error when `alloc` is called. It looks like this: +私たちのダミーアロケータではトレイトを実装するための最小限のことしかせず、`alloc`が呼び出されたら常にエラーを返すようにします。以下のようになります: ```rust // in src/allocator.rs @@ -315,15 +321,15 @@ unsafe impl GlobalAlloc for Dummy { } ``` -The struct does not need any fields, so we create it as a [zero sized type]. As mentioned above, we always return the null pointer from `alloc`, which corresponds to an allocation error. Since the allocator never returns any memory, a call to `dealloc` should never occur. For this reason we simply panic in the `dealloc` method. The `alloc_zeroed` and `realloc` methods have default implementations, so we don't need to provide implementations for them. +この構造体はフィールドを必要としないので、[サイズがゼロの型][zero sized type]として作成します。上で述べたように、この`alloc`では常に割り当てエラーに相当するヌルポインタを返すようにします。アロケータがメモリを返すことは絶対に起きないのだから、`dealloc`の呼び出しも絶対に起きないはずです。このため`dealloc`メソッドでは単にpanicすることにします。`alloc_zeroed`と`realloc`メソッドにはデフォルト実装があるので、これらを実装する必要はありません。 [zero sized type]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts -We now have a simple allocator, but we still have to tell the Rust compiler that it should use this allocator. This is where the `#[global_allocator]` attribute comes in. +単純なアロケータを手に入れたわけですが、さらにRustコンパイラにこのアロケータを使うよう指示しないといけません。ここで`#[global_allocator]`属性の出番です。 -### The `#[global_allocator]` Attribute +### `#[global_allocator]`属性 -The `#[global_allocator]` attribute tells the Rust compiler which allocator instance it should use as the global heap allocator. The attribute is only applicable to a `static` that implements the `GlobalAlloc` trait. Let's register an instance of our `Dummy` allocator as the global allocator: +`#[global_allocator]`属性は、どのアロケータインスタンスをグローバルヒープアロケータとして使うべきかをRustコンパイラに指示します。この属性は`GlobalAlloc`トレイトを実装する`static`にのみ適用できます。私たちの`Dummy`アロケータのインスタンスをグローバルアロケータとして登録してみましょう: ```rust // in src/allocator.rs @@ -332,24 +338,24 @@ The `#[global_allocator]` attribute tells the Rust compiler which allocator inst static ALLOCATOR: Dummy = Dummy; ``` -Since the `Dummy` allocator is a [zero sized type], we don't need to specify any fields in the initialization expression. +`Dummy`アロケータは[サイズがゼロの型][zero sized type]なので、初期化式でフィールドを指定する必要はありません。 -When we now try to compile it, the first error should be gone. Let's fix the remaining second error: +これをコンパイルしようとすると、最初のエラーは消えているはずです。残っている二つ目のエラーを修正しましょう: ``` error: `#[alloc_error_handler]` function required, but not found ``` -### The `#[alloc_error_handler]` Attribute +### `#[alloc_error_handler]`属性 -As we learned when discussing the `GlobalAlloc` trait, the `alloc` function can signal an allocation error by returning a null pointer. The question is: how should the Rust runtime react to such an allocation failure? This is where the `#[alloc_error_handler]` attribute comes in. It specifies a function that is called when an allocation error occurs, similar to how our panic handler is called when a panic occurs. +`GlobalAlloc`トレイトについて議論したときに学んだように、`alloc`関数はヌルポインタを返すことによって割り当てエラーを示します。ここで生じる疑問は、そのように割り当てが失敗したときRustランタイムはどう対処するべきなのかということです。ここで`#[alloc_error_handler]`属性の出番です。この属性は、パニックが起こったときに私たちのパニックハンドラが呼ばれるのと同様に、割り当てエラーが起こったときに呼ばれる関数を指定するのです。 -Let's add such a function to fix the compilation error: +コンパイルエラーを修正するためにそんな関数を追加してみましょう: ```rust // in src/lib.rs -#![feature(alloc_error_handler)] // at the top of the file +#![feature(alloc_error_handler)] // ファイルの先頭に書く #[alloc_error_handler] fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { @@ -357,9 +363,9 @@ fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { } ``` -The `alloc_error_handler` function is still unstable, so we need a feature gate to enable it. The function receives a single argument: the `Layout` instance that was passed to `alloc` when the allocation failure occurred. There's nothing we can do to resolve the failure, so we just panic with a message that contains the `Layout` instance. +`alloc_error_handler`関数はまだunstableなので、この機能 (feature) を有効化する必要があります。この関数は引数を一つ取ります:割り当てエラーが起こったとき`alloc`関数に渡されていた`Layout`のインスタンスです。この失敗を解決するためにできることはないので、`Layout`インスタンスを含めたメッセージを表示してただpanicすることにしましょう。 -With this function, the compilation errors should be fixed. Now we can use the allocation and collection types of `alloc`, for example we can use a [`Box`] to allocate a value on the heap: +この関数を追加したことで、コンパイルエラーは修正されたはずです。これで`alloc`のアロケーション・コレクション型を使えるようになりました。例えば、[`Box`]を使ってヒープに値を割り当てることができます: [`Box`]: https://doc.rust-lang.org/alloc/boxed/struct.Box.html @@ -371,11 +377,11 @@ extern crate alloc; use alloc::boxed::Box; fn kernel_main(boot_info: &'static BootInfo) -> ! { - // […] print "Hello World!", call `init`, create `mapper` and `frame_allocator` + // […] "Hello World!"を表示, `init`の呼び出し, `mapper`と`frame_allocator`を作成 let x = Box::new(41); - // […] call `test_main` in test mode + // […] テストモードでは`test_main`を呼ぶ println!("It did not crash!"); blog_os::hlt_loop(); @@ -383,21 +389,21 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { ``` -Note that we need to specify the `extern crate alloc` statement in our `main.rs` too. This is required because the `lib.rs` and `main.rs` part are treated as separate crates. However, we don't need to create another `#[global_allocator]` static because the global allocator applies to all crates in the project. In fact, specifying an additional allocator in another crate would be an error. +`main.rs`においても`extern crate alloc`文を指定する必要があることに注意してください。`lib.rs`と`main.rs`は別のクレートとして取り扱われているためです。しかしながら、グローバルアロケータはプロジェクト内のすべてのクレートに適用されるため、`#[global_allocator]`静的変数をもう一つ作る必要はありません。実際、別のクレートで新しいアロケータを指定するとエラーになります。 -When we run the above code, we see that our `alloc_error_handler` function is called: +上のコードを実行すると、`alloc_error_handler`関数が呼ばれるのが分かります: ![QEMU printing "panicked at `allocation error: Layout { size_: 4, align_: 4 }, src/lib.rs:89:5"](qemu-dummy-output.png) -The error handler is called because the `Box::new` function implicitly calls the `alloc` function of the global allocator. Our dummy allocator always returns a null pointer, so every allocation fails. To fix this we need to create an allocator that actually returns usable memory. +`Box::new`関数は暗黙のうちにグローバルアロケータの`alloc`関数を呼び出すため、エラーハンドラが呼ばれました。私たちのダミーアロケータは常にヌルポインタを返すので、あらゆる割り当ては失敗します。これを修正するためには、使用可能なメモリを実際に返すアロケータを作る必要があります。 ## Creating a Kernel Heap -Before we can create a proper allocator, we first need to create a heap memory region from which the allocator can allocate memory. To do this, we need to define a virtual memory range for the heap region and then map this region to physical frames. See the [_"Introduction To Paging"_] post for an overview of virtual memory and page tables. +適当なアロケータを作る前にまず、そのアロケータがメモリを割り当てるためのヒープメモリ領域を作らないといけません。このために、ヒープ領域のための仮想メモリ範囲を定義し、その領域を物理フレームに対応付ける必要があります。仮想メモリとページテーブルの概要については、[ページング入門][_"Introduction To Paging"_]の記事を読んでください。 [_"Introduction To Paging"_]: @/edition-2/posts/08-paging-introduction/index.md -The first step is to define a virtual memory region for the heap. We can choose any virtual address range that we like, as long as it is not already used for a different memory region. Let's define it as the memory starting at address `0x_4444_4444_0000` so that we can easily recognize a heap pointer later: +最初のステップはヒープのための仮想メモリ領域を定義することです。他のメモリ領域に使われていない限り、どんな仮想アドレス範囲でも構いません。ここでは、あとからそこがヒープポインタだと簡単に分かるよう、`0x_4444_4444_0000`から始まるメモリとしましょう。 ```rust // in src/allocator.rs @@ -406,9 +412,9 @@ pub const HEAP_START: usize = 0x_4444_4444_0000; pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB ``` -We set the heap size to 100 KiB for now. If we need more space in the future, we can simply increase it. +今のところヒープの大きさは100 KiBとします。将来より多くの領域が必要になったら大きくすれば良いです。 -If we tried to use this heap region now, a page fault would occur since the virtual memory region is not mapped to physical memory yet. To resolve this, we create an `init_heap` function that maps the heap pages using the [`Mapper` API] that we introduced in the [_"Paging Implementation"_] post: +今このヒープ領域を使おうとすると、仮想メモリ領域が物理メモリにまだ対応付けられていないためページフォルトが発生します。これを解決するために、[ページング入門][_"Paging Implementation"_]の記事で導入した[`Mapper` API]を使ってヒープページを対応付ける関数`init_heap`を作ります: [`Mapper` API]: @/edition-2/posts/09-paging-implementation/index.md#using-offsetpagetable [_"Paging Implementation"_]: @/edition-2/posts/09-paging-implementation/index.md @@ -449,7 +455,7 @@ pub fn init_heap( } ``` -The function takes mutable references to a [`Mapper`] and a [`FrameAllocator`] instance, both limited to 4KiB pages by using [`Size4KiB`] as generic parameter. The return value of the function is a [`Result`] with the unit type `()` as success variant and a [`MapToError`] as error variant, which is the error type returned by the [`Mapper::map_to`] method. Reusing the error type makes sense here because the `map_to` method is the main source of errors in this function. +この関数は[`Mapper`]と[`FrameAllocator`]への可変参照を取ります。引数はどちらも[`Size4KiB`]をジェネリックパラメータとすることで4KiBページのみに制限しています。この関数の戻り値は[`Result`]で、成功ヴァリアントが`()`、失敗ヴァリアントが([`Mapper::map_to`]メソッドによって失敗時に返されるエラー型である)[`MapToError`]です。このエラー型を流用するのは理にかなっており、というのもこの関数における主なエラーの原因は`map_to`メソッドだからです。 [`Mapper`]:https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html [`FrameAllocator`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/trait.FrameAllocator.html @@ -458,17 +464,17 @@ The function takes mutable references to a [`Mapper`] and a [`FrameAllocator`] i [`MapToError`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/enum.MapToError.html [`Mapper::map_to`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/trait.Mapper.html#method.map_to -The implementation can be broken down into two parts: +実装内容は以下の二つに分けられます: -- **Creating the page range:**: To create a range of the pages that we want to map, we convert the `HEAP_START` pointer to a [`VirtAddr`] type. Then we calculate the heap end address from it by adding the `HEAP_SIZE`. We want an inclusive bound (the address of the last byte of the heap), so we subtract 1. Next, we convert the addresses into [`Page`] types using the [`containing_address`] function. Finally, we create a page range from the start and end pages using the [`Page::range_inclusive`] function. +- **ページ範囲の作成:** 対応付けたいページ領域を作成するために、ポインタ`HEAP_START`を[`VirtAddr`]型に変換します。つぎに`HEAP_SIZE`を足すことによってヒープの終端アドレスを計算します。端が含まれる境界 (インクルーシブレンジ) にしたい(ヒープの最後のバイトのアドレスが欲しい)ので1を引きます。次に、これらのアドレスを[`containing_address`]関数を使って[`Page`]型に変換します。最後に、[`Page::range_inclusive`]関数を使って最初と最後のページからページ範囲を作成します。 -- **Mapping the pages:** The second step is to map all pages of the page range we just created. For that we iterate over the pages in that range using a `for` loop. For each page, we do the following: +- **ページの対応付け (マッピング) :** 二つ目のステップは、今作ったページ範囲のすべてのページに対して対応付けを行うことです。これを行うため、`for`ループを使ってこのページ範囲に対して繰り返し処理を行います。それぞれのページに対して以下を行います: - - We allocate a physical frame that the page should be mapped to using the [`FrameAllocator::allocate_frame`] method. This method returns [`None`] when there are no more frames left. We deal with that case by mapping it to a [`MapToError::FrameAllocationFailed`] error through the [`Option::ok_or`] method and then apply the [question mark operator] to return early in the case of an error. + - [`FrameAllocator::allocate_frame`]メソッドを使って、ページのマップされるべき物理フレームを割り当てます。このメソッドはもうフレームが残っていないとき[`None`]を返します。このケースに対処するため、[`Option::ok_or`]メソッドを使ってこれを[`MapToError::FrameAllocationFailed`]に変換し、エラーの場合は[`?`演算子][question mark operator]を使って早期リターンしています。 - - We set the required `PRESENT` flag and the `WRITABLE` flag for the page. With these flags both read and write accesses are allowed, which makes sense for heap memory. + - このページに対し、必要となる`PRESENT`フラグと`WRITABLE`フラグをセットします。これらのフラグにより読み書きのアクセスが許可され、ヒープメモリとして理にかなっています。 - - We use the [`Mapper::map_to`] method for creating the mapping in the active page table. The method can fail, therefore we use the [question mark operator] again to forward the error to the caller. On success, the method returns a [`MapperFlush`] instance that we can use to update the [_translation lookaside buffer_] using the [`flush`] method. + - [`Mapper::map_to`]メソッドを使ってアクティブなページテーブルに対応付けを作成します。このメソッドは失敗しうるので、同様に[`?`演算子][question mark operator]を使ってエラーを呼び出し元に受け渡します。成功時には、このメソッドは[`MapperFlush`]インスタンスを返しますが、これを使って[`flush`]メソッドを呼ぶことで[**トランスレーション・ルックアサイド・バッファ**][_translation lookaside buffer_]を更新することができます。 [`VirtAddr`]: https://docs.rs/x86_64/0.14.2/x86_64/addr/struct.VirtAddr.html [`Page`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page/struct.Page.html @@ -483,13 +489,13 @@ The implementation can be broken down into two parts: [_translation lookaside buffer_]: @/edition-2/posts/08-paging-introduction/index.md#the-translation-lookaside-buffer [`flush`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/mapper/struct.MapperFlush.html#method.flush -The final step is to call this function from our `kernel_main`: +最後のステップは、この関数を`kernel_main`から呼び出すことです: ```rust // in src/main.rs fn kernel_main(boot_info: &'static BootInfo) -> ! { - use blog_os::allocator; // new import + use blog_os::allocator; // 新しいインポート use blog_os::memory::{self, BootInfoFrameAllocator}; println!("Hello World{}", "!"); @@ -501,32 +507,32 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { BootInfoFrameAllocator::init(&boot_info.memory_map) }; - // new + // ここを追加 allocator::init_heap(&mut mapper, &mut frame_allocator) .expect("heap initialization failed"); let x = Box::new(41); - // […] call `test_main` in test mode + // […] テストモードでは`test_main`を呼ぶ println!("It did not crash!"); blog_os::hlt_loop(); } ``` -We show the full function for context here. The only new lines are the `blog_os::allocator` import and the call to `allocator::init_heap` function. In case the `init_heap` function returns an error, we panic using the [`Result::expect`] method since there is currently no sensible way for us to handle this error. +ここで、文脈が分かるよう関数の全体を示しています。新しい行は`blog_os::allocator`のインポートと`allocator::init_heap`の呼び出しだけです。`init_heap`関数がエラーを返した場合、これを処理する賢い方法は今のところないため、[`Result::expect`]メソッドを使ってパニックします。 [`Result::expect`]: https://doc.rust-lang.org/core/result/enum.Result.html#method.expect -We now have a mapped heap memory region that is ready to be used. The `Box::new` call still uses our old `Dummy` allocator, so you will still see the "out of memory" error when you run it. Let's fix this by using a proper allocator. +これで、使用する準備のできた、対応付けられたヒープメモリ領域を手に入れました。`Box::new`の呼び出しはまだ私たちの古い`Dummy`を使っているので、実行しても依然として「メモリ切れ」のエラーを見ることになるでしょう。適切なアロケータを使うようにして、このエラーを修正してみましょう。 -## Using an Allocator Crate +## アロケータクレートを使う -Since implementing an allocator is somewhat complex, we start by using an external allocator crate. We will learn how to implement our own allocator in the next post. +アロケータを実装するのは少々複雑なので、まずは既製のアロケータを使うことにしましょう。自前のアロケータを実装する方法については次の記事で学びます。 -A simple allocator crate for `no_std` applications is the [`linked_list_allocator`] crate. It's name comes from the fact that it uses a linked list data structure to keep track of deallocated memory regions. See the next post for a more detailed explanation of this approach. +`no_std`のアプリケーションのためのシンプルなアロケータのひとつに[`linked_list_allocator`]クレートがあります。この名前は、割り当てられていないメモリ領域を連結リストを使って管理しているところから来ています。この手法のより詳しい説明については次の記事を読んでください。 -To use the crate, we first need to add a dependency on it in our `Cargo.toml`: +このクレートを使うためには、まず依存関係を`Cargo.toml`に追加する必要があります: [`linked_list_allocator`]: https://github.com/phil-opp/linked-list-allocator/ @@ -537,7 +543,7 @@ To use the crate, we first need to add a dependency on it in our `Cargo.toml`: linked_list_allocator = "0.9.0" ``` -Then we can replace our dummy allocator with the allocator provided by the crate: +次に私たちのダミーアロケータをこのクレートによって提供されるアロケータで置き換えます: ```rust // in src/allocator.rs @@ -548,11 +554,11 @@ use linked_list_allocator::LockedHeap; static ALLOCATOR: LockedHeap = LockedHeap::empty(); ``` -The struct is named `LockedHeap` because it uses the [`spinning_top::Spinlock`] type for synchronization. This is required because multiple threads could access the `ALLOCATOR` static at the same time. As always when using a spinlock or a mutex, we need to be careful to not accidentally cause a deadlock. This means that we shouldn't perform any allocations in interrupt handlers, since they can run at an arbitrary time and might interrupt an in-progress allocation. +この構造体は同期のために`spinning_top::Spinlock`型を使うため`LockedHeap`という名前が付いています。同期が必要なのは、静的変数`ALLOCATOR`に複数のスレッドが同時にアクセスすることがありえるからです。スピンロックやmutexを使うときはいつもそうであるように、誤ってデッドロックを起こさないように注意する必要があります。これが意味するのは、我々は割り込みハンドラ内で一切アロケーションを行ってはいけないと言うことです。なぜなら、割り込みハンドラはどんなタイミングでも走る可能性があるため、進行中のアロケーションに割り込んでいることがあるからです。 [`spinning_top::Spinlock`]: https://docs.rs/spinning_top/0.1.0/spinning_top/type.Spinlock.html -Setting the `LockedHeap` as global allocator is not enough. The reason is that we use the [`empty`] constructor function, which creates an allocator without any backing memory. Like our dummy allocator, it always returns an error on `alloc`. To fix this, we need to initialize the allocator after creating the heap: +`LockedHeap`をグローバルアロケータとして設定するだけでは十分ではありません。いま[`empty`]コンストラクタ関数を使っていますが、この関数はメモリを与えることなくアロケータを作るからです。私たちのダミーアロケータと同じく、これ(今の状態の`LockedHeap`)は`alloc`を行うと常にエラーを返します。この問題を修正するため、ヒープを作った後でアロケータを初期化する必要があります: [`empty`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.LockedHeap.html#method.empty @@ -574,13 +580,13 @@ pub fn init_heap( } ``` -We use the [`lock`] method on the inner spinlock of the `LockedHeap` type to get an exclusive reference to the wrapped [`Heap`] instance, on which we then call the [`init`] method with the heap bounds as arguments. It is important that we initialize the heap _after_ mapping the heap pages, since the [`init`] function already tries to write to the heap memory. +`LockedHeap`型の内部のスピンロックの[`lock`]メソッドを呼ぶことで、ラップされた[`Heap`]インスタンスへの排他的参照を得て、これの[`init`]メソッドをヒープの境界を引数として呼んでいます。`init`関数自体がヒープメモリに書き込もうとするので、ヒープページを対応付けた **後に** ヒープを初期化することが重要です。 [`lock`]: https://docs.rs/lock_api/0.3.3/lock_api/struct.Mutex.html#method.lock [`Heap`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html [`init`]: https://docs.rs/linked_list_allocator/0.9.0/linked_list_allocator/struct.Heap.html#method.init -After initializing the heap, we can now use all allocation and collection types of the built-in [`alloc`] crate without error: +ヒープを初期化できたら、組み込みの[`alloc`]クレートのあらゆるアロケーション・コレクション型がエラーなく使用できます: ```rust // in src/main.rs @@ -590,31 +596,31 @@ use alloc::{boxed::Box, vec, vec::Vec, rc::Rc}; fn kernel_main(boot_info: &'static BootInfo) -> ! { // […] initialize interrupts, mapper, frame_allocator, heap - // allocate a number on the heap + // ヒープに数字をアロケートする let heap_value = Box::new(41); println!("heap_value at {:p}", heap_value); - // create a dynamically sized vector + // 動的サイズのベクタを作成する let mut vec = Vec::new(); for i in 0..500 { vec.push(i); } println!("vec at {:p}", vec.as_slice()); - // create a reference counted vector -> will be freed when count reaches 0 + // 参照カウントされたベクタを作成する -> カウントが0になると解放される let reference_counted = Rc::new(vec![1, 2, 3]); let cloned_reference = reference_counted.clone(); println!("current reference count is {}", Rc::strong_count(&cloned_reference)); core::mem::drop(reference_counted); println!("reference count is {} now", Rc::strong_count(&cloned_reference)); - // […] call `test_main` in test context + // […] テストでは `test_main` を呼ぶ println!("It did not crash!"); blog_os::hlt_loop(); } ``` -This code example shows some uses of the [`Box`], [`Vec`], and [`Rc`] types. For the `Box` and `Vec` types we print the underlying heap pointers using the [`{:p}` formatting specifier]. For showcasing `Rc`, we create a reference counted heap value and use the [`Rc::strong_count`] function to print the current reference count, before and after dropping an instance (using [`core::mem::drop`]). +このコード例では[`Box`], [`Vec`], [`Rc`]型を使ってみせています。`Box`型と`Vec`型については対応するヒープポインタを[`{:p}`フォーマット指定子][`{:p}` formatting specifier]を使って出力しています。`Rc`について例示するために、参照カウントされたヒープ値を作成し、インスタンスを([`core::mem::drop`]を使って)ドロップする前と後に[`Rc::strong_count`]関数を使って現在の参照カウントを出力しています。 [`Vec`]: https://doc.rust-lang.org/alloc/vec/ [`Rc`]: https://doc.rust-lang.org/alloc/rc/ @@ -622,7 +628,7 @@ This code example shows some uses of the [`Box`], [`Vec`], and [`Rc`] types. For [`Rc::strong_count`]: https://doc.rust-lang.org/alloc/rc/struct.Rc.html#method.strong_count [`core::mem::drop`]: https://doc.rust-lang.org/core/mem/fn.drop.html -When we run it, we see the following: +実行すると、以下のような結果を得ます: ![QEMU printing ` heap_value at 0x444444440000 @@ -631,20 +637,20 @@ current reference count is 2 reference count is 1 now ](qemu-alloc-showcase.png) -As expected, we see that the `Box` and `Vec` values live on the heap, as indicated by the pointer starting with the `0x_4444_4444_*` prefix. The reference counted value also behaves as expected, with the reference count being 2 after the `clone` call, and 1 again after one of the instances was dropped. +期待したとおり、`Box`と`Vec`の値はヒープ上にあり、そのことはポインタが`0x_4444_4444_*`で始まることから分かります。参照カウントされた値も期待したとおり振る舞っており、`clone`呼び出しの後では参照カウントは2に、インスタンスの一方がドロップされた後では再び1になっています。 -The reason that the vector starts at offset `0x800` is not that the boxed value is `0x800` bytes large, but the [reallocations] that occur when the vector needs to increase its capacity. For example, when the vector's capacity is 32 and we try to add the next element, the vector allocates a new backing array with capacity 64 behind the scenes and copies all elements over. Then it frees the old allocation. +ベクタがヒープメモリの先頭から`0x800`だけずれた場所から始まるのは、Box内の値が`0x800`バイトの大きさがあるためではなく、ベクタが容量を増やさなければならないときに発生する[再割り当て (リアロケーション) ][reallocations]のためです。例えば、ベクタの容量が32の際に次の要素を追加しようとすると、ベクタは内部で容量64の配列を新たに割り当て、すべての要素をコピーしているのです。その後古い割り当てを解放します。 [reallocations]: https://doc.rust-lang.org/alloc/vec/struct.Vec.html#capacity-and-reallocation -Of course there are many more allocation and collection types in the `alloc` crate that we can now all use in our kernel, including: +もちろん`alloc`クレートにはもっと多くのアロケーション・コレクション型があり、今やそれらのすべてを私たちのカーネルで使うことができます。それには以下が含まれます: -- the thread-safe reference counted pointer [`Arc`] -- the owned string type [`String`] and the [`format!`] macro +- スレッドセーフな参照カウントポインタ[`Arc`] +- 所有された文字列型[`String`]と[`format!`]マクロ - [`LinkedList`] -- the growable ring buffer [`VecDeque`] -- the [`BinaryHeap`] priority queue -- [`BTreeMap`] and [`BTreeSet`] +- 必要に応じてサイズを大きくできるリングバッファ[`VecDeque`] +- プライオリティキューである[`BinaryHeap`] +- [`BTreeMap`]と[`BTreeSet`] [`Arc`]: https://doc.rust-lang.org/alloc/sync/struct.Arc.html [`String`]: https://doc.rust-lang.org/alloc/string/struct.String.html @@ -655,11 +661,11 @@ Of course there are many more allocation and collection types in the `alloc` cra [`BTreeMap`]: https://doc.rust-lang.org/alloc/collections/btree_map/struct.BTreeMap.html [`BTreeSet`]: https://doc.rust-lang.org/alloc/collections/btree_set/struct.BTreeSet.html -These types will become very useful when we want to implement thread lists, scheduling queues, or support for async/await. +これらの型は、スレッドリスト、スケジュールキュー、async/awaitのサポートを実装しようとするときにとても有用になります。 -## Adding a Test +## テストを追加する -To ensure that we don't accidentally break our new allocation code, we should add an integration test for it. We start by creating a new `tests/heap_allocation.rs` file with the following content: +私たちの新しい割り当てコードを間違って壊してしまうことがないことを保証するために、結合 (インテグレーション) テストを追加しなければなりません。まず、次のような内容のファイル`tests/heap_allocation.rs`を作成します。 ```rust // in tests/heap_allocation.rs @@ -687,11 +693,11 @@ fn panic(info: &PanicInfo) -> ! { } ``` -We reuse the `test_runner` and `test_panic_handler` functions from our `lib.rs`. Since we want to test allocations, we enable the `alloc` crate through the `extern crate alloc` statement. For more information about the test boilerplate check out the [_Testing_] post. +`lib.rs`の`test_runner`関数と`test_panic_handler`関数を再利用します。私たちはアロケーションをテストしたいので、`extern crate alloc`宣言を使って`alloc`クレートを有効化します。テストに共通する定型部については[テスト][_Testing_]の記事を読んでください。 [_Testing_]: @/edition-2/posts/04-testing/index.md -The implementation of the `main` function looks like this: +`main`関数の実装は以下のようになります: ```rust // in tests/heap_allocation.rs @@ -715,9 +721,9 @@ fn main(boot_info: &'static BootInfo) -> ! { } ``` -It is very similar to the `kernel_main` function in our `main.rs`, with the differences that we don't invoke `println`, don't include any example allocations, and call `test_main` unconditionally. +私たちの`main.rs`内の`kernel_main`関数によく似ていますが、`println`を呼び出さず、例として行ったアロケーションも行わず、また`test_main`を無条件で呼び出しているという違いがあります。 -Now we're ready to add a few test cases. First, we add a test that performs some simple allocations using [`Box`] and checks the allocated values, to ensure that basic allocations work: +これでいくつかテストケースを追加する準備ができました。まず、[`Box`]を使って単純な割り当て (アロケーション) を行い、割り当てられた値を確かめることで基本的なアロケーションがうまくいっていることを確かめましょう: ```rust // in tests/heap_allocation.rs @@ -732,9 +738,9 @@ fn simple_allocation() { } ``` -Most importantly, this test verifies that no allocation error occurs. +最も重要なのは、このテストはアロケーションエラーが起きないことを検証してくれるということです。 -Next, we iteratively build a large vector, to test both large allocations and multiple allocations (due to reallocations): +次に、反復によって大きなベクタを作り、大きな割り当てと(再割り当てによる)複数回の割り当ての両方をテストしましょう: ```rust // in tests/heap_allocation.rs @@ -752,11 +758,11 @@ fn large_vec() { } ``` -We verify the sum by comparing it with the formula for the [n-th partial sum]. This gives us some confidence that the allocated values are all correct. +このベクタの和を[n次部分和][n-th partial sum]の公式と比較することで検証しています。これにより、割り当てられた値はすべて正しいことがある程度保証されます。 [n-th partial sum]: https://en.wikipedia.org/wiki/1_%2B_2_%2B_3_%2B_4_%2B_%E2%8B%AF#Partial_sums -As a third test, we create ten thousand allocations after each other: +3つ目のテストとして、一万回次々にアロケーションを行います: ```rust // in tests/heap_allocation.rs @@ -772,9 +778,9 @@ fn many_boxes() { } ``` -This test ensures that the allocator reuses freed memory for subsequent allocations since it would run out of memory otherwise. This might seem like an obvious requirement for an allocator, but there are allocator designs that don't do this. An example is the bump allocator design that will be explained in the next post. +このテストではアロケータが解放されたメモリを次の割り当てで再利用していることを保証してくれます。もしそうなっていなければ、メモリ不足が起きるでしょう。これはアロケータにとって当たり前の要件だと思われるかもしれませんが、これを行わないようなアロケータの設計も存在するのです。その例として、次の記事で説明するbump allocatorがあります。 -Let's run our new integration test: +では、私たちの新しい結合テストを実行してみましょう: ``` > cargo test --test heap_allocation @@ -785,16 +791,16 @@ large_vec... [ok] many_boxes... [ok] ``` -All three tests succeeded! You can also invoke `cargo test` (without `--test` argument) to run all unit and integration tests. +すべてのテストが成功しました!`cargo test`コマンドを(`--test`引数なしに)呼ぶことで、すべての結合テストを実行することもできます。 -## Summary +## まとめ -This post gave an introduction to dynamic memory and explained why and where it is needed. We saw how Rust's borrow checker prevents common vulnerabilities and learned how Rust's allocation API works. +この記事では動的メモリについて入門し、なぜ、そしていつそれが必要になるのかを説明しました。Rustの借用チェッカがどのようにしてよくある脆弱性を防ぐのか、そしてRustのアロケーションAPIの仕組みを理解しました。 -After creating a minimal implementation of Rust's allocator interface using a dummy allocator, we created a proper heap memory region for our kernel. For that we defined a virtual address range for the heap and then mapped all pages of that range to physical frames using the `Mapper` and `FrameAllocator` from the previous post. +ダミーアロケータでRustのアロケータインターフェースの最小限の実装を作成した後、私たちのカーネル用の適切なヒープメモリ領域を作成しました。これを行うために、ヒープ用の仮想アドレス範囲を定義し、前の記事で説明した`Mapper`と`FrameAllocator`を使ってその範囲のすべてのページを物理フレームに対応付けました。 -Finally, we added a dependency on the `linked_list_allocator` crate to add a proper allocator to our kernel. With this allocator, we were able to use `Box`, `Vec`, and other allocation and collection types from the `alloc` crate. +最後に、`linked_list_allocator`クレートへの依存関係を追加し、適切なアロケータを私たちのカーネルに追加しました。このアロケータのおかげで、`alloc`クレートに含まれる`Box`、`Vec`、その他のアロケーション・コレクション型を使えるようになりました。 -## What's next? +## 次は? -While we already added heap allocation support in this post, we left most of the work to the `linked_list_allocator` crate. The next post will show in detail how an allocator can be implemented from scratch. It will present multiple possible allocator designs, shows how to implement simple versions of them, and explain their advantages and drawbacks. +この記事でヒープ割り当て機能のサポートを追加したとは言え、ほとんどの仕事は`linked_list_allocator`クレートに任せてしまっています。次の記事では、アロケータをゼロから実装する方法を詳細にお伝えします。可能なアロケータの設計を複数示し、それらを単純化した者を実装する方法を示し、その利点と欠点を示します。