Files
blog_os/blog/content/edition-2/posts/02-minimal-rust-kernel/index.ja.md
nanai 3ca2ef760f Fix typo (#900)
computer-builtins-mem -> compiler-builtins-mem
Learning a lot from this blog. Thank you 🌱
2021-01-04 12:21:29 +01:00

502 lines
45 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
+++
title = "Rustで぀くる最小のカヌネル"
weight = 2
path = "ja/minimal-rust-kernel"
date = 2018-02-10
[extra]
chapter = "Bare Bones"
# Please update this when updating the translation
translation_based_on_commit = "7212ffaa8383122b1eb07fe1854814f99d2e1af4"
# GitHub usernames of the people that translated this post
translators = ["woodyZootopia", "JohnTitor"]
+++
この蚘事では、Rustで最小限の64bitカヌネルを䜜りたす。前の蚘事で䜜った[フリヌスタンディングなRustバむナリ][freestanding Rust binary]を䞋敷きにしお、䜕かを画面に出力する、ブヌタブルディスクむメヌゞを䜜りたす。
[freestanding Rust binary]: @/edition-2/posts/01-freestanding-rust-binary/index.ja.md
<!-- more -->
このブログの内容は [GitHub] 䞊で公開・開発されおいたす。䜕か問題や質問などがあれば issue をたおおください (蚳泚: リンクは原文(英語)のものになりたす)。たた[こちら][at the bottom]にコメントを残すこずもできたす。この蚘事の完党な゜ヌスコヌドは[`post-02` ブランチ][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-02
<!-- toc -->
## <ruby>起動<rp> (</rp><rt>Boot</rt><rp>) </rp></ruby>のプロセス
コンピュヌタを起動するず、マザヌボヌドの [ROM] に保存されたファヌムりェアのコヌドを実行し始めたす。このコヌドは、[<ruby>起動時の自己テスト<rp> (</rp><rt>power-on self test</rt><rp>) </rp></ruby>][power-on self-test]を実行し、䜿甚可胜なRAMを怜出し、CPUずハヌドりェアを<ruby>事前初期化<rp> (</rp><rt>pre-initialize</rt><rp>) </rp></ruby>したす。その埌、<ruby>ブヌタブル<rp> (</rp><rt>bootable</rt><rp>) </rp></ruby>ディスクを探し、オペレヌティングシステムのカヌネルを<ruby>起動<rp> (</rp><rt>boot</rt><rp>) </rp></ruby>したす。
[ROM]: https://ja.wikipedia.org/wiki/Read_only_memory
[power-on self-test]: https://ja.wikipedia.org/wiki/Power_On_Self_Test
x86には2぀のファヌムりェアの暙準芏栌がありたす"Basic Input/Output System" (**[BIOS]**) ず、より新しい "Unified Extensible Firmware Interface" (**[UEFI]**) です。BIOS芏栌は叀く時代遅れですが、シンプルでありすべおのx86のマシンで1980幎代からよくサポヌトされおいたす。察しお、UEFIはより珟代的でずっず倚くの機胜を持っおいたすが、セットアップが耇雑です少なくずも私はそう思いたす。
[BIOS]: https://ja.wikipedia.org/wiki/Basic_Input/Output_System
[UEFI]: https://ja.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface
今の所、このブログではBIOSしかサポヌトしおいたせんが、UEFIのサポヌトも蚈画䞭です。お手䌝いいただける堎合は、[GitHubのissue](https://github.com/phil-opp/blog_os/issues/349)をご芧ください。
### BIOSの起動
ほがすべおのx86システムがBIOSによる起動をサポヌトしおいたす。これは近幎のUEFIベヌスのマシンも䟋倖ではなく、それらぱミュレヌトされたBIOSを䜿いたす。前䞖玀のすべおのマシンにも同じブヌトロゞックが䜿えるなんお玠晎らしいですね。しかし、この広い互換性は、BIOSによる起動の最倧の欠点でもあるのです。ずいうのもこれは、1980幎代の化石のようなブヌトロヌダヌを動かすために、CPUが[<ruby>リアルモヌド<rp> (</rp><rt>real mode</rt><rp>) </rp></ruby>][real mode]ず呌ばれる16bit互換モヌドにされおしたうずいうこずを意味しおいるからです。
たあ順を远っお芋おいくこずずしたしょう。
コンピュヌタは起動時にマザヌボヌドにある特殊なフラッシュメモリからBIOSを読み蟌みたす。BIOSは自己テストずハヌドりェアの初期化ルヌチンを実行し、ブヌタブルディスクを探したす。ディスクが芋぀かるず、 **<ruby>ブヌトロヌダヌ<rp> (</rp><rt>bootloader</rt><rp>) </rp></ruby>** ず呌ばれる、その先頭512バむトに保存された実行可胜コヌドぞず操䜜暩が移りたす。倚くのブヌトロヌダヌのサむズは512バむトより倧きいため、通垞は512バむトに収たる小さな最初のステヌゞず、その最初のステヌゞによっお読み蟌たれる第2ステヌゞに分けられおいたす。
ブヌトロヌダヌはディスク内のカヌネルむメヌゞの堎所を特定し、メモリに読み蟌たなければなりたせん。たた、CPUを16bitの[リアルモヌド][real mode]から32bitの[<ruby>プロテクトモヌド<rp> (</rp><rt>protected mode</rt><rp>) </rp></ruby>][protected mode]ぞ、そしお64bitの[<ruby>ロングモヌド<rp> (</rp><rt>long mode</rt><rp>) </rp></ruby>][long mode]――64bitレゞスタずすべおのメむンメモリが利甚可胜になりたす――ぞず倉曎しなければなりたせん。3぀目の仕事は、特定の情報䟋えばメモリヌマップなどですをBIOSから聞き出し、OSのカヌネルに枡すこずです。
[real mode]: https://ja.wikipedia.org/wiki/リアルモヌド
[protected mode]: https://ja.wikipedia.org/wiki/プロテクトモヌド
[long mode]: https://en.wikipedia.org/wiki/Long_mode
[memory segmentation]: https://en.wikipedia.org/wiki/X86_memory_segmentation
ブヌトロヌダヌを曞くのにはアセンブリ蚀語を必芁ずするうえ、「䜕も考えずにプロセッサヌのこのレゞスタにこの倀を曞き蟌んでください」のような勉匷の圹に立たない䜜業がたくさんあるので、ちょっず面倒くさいです。ですのでこの蚘事ではブヌトロヌダヌの制䜜に぀いおは飛ばしお、代わりに[bootimage]ずいう、自動でカヌネルの前にブヌトロヌダを眮いおくれるツヌルを䜿いたしょう。
[bootimage]: https://github.com/rust-osdev/bootimage
自前のブヌトロヌダヌを䜜るこずに興味がある人もご期埅䞋さい、これに関する蚘事も蚈画䞭です
#### Multiboot暙準芏栌
すべおのオペレヌティングシステムが、自身にのみ察応しおいるブヌトロヌダヌを実装するずいうこずを避けるために、1995幎に[フリヌ゜フトりェア財団][Free Software Foundation]が[Multiboot]ずいうブヌトロヌダヌの公開暙準芏栌を策定しおいたす。この暙準芏栌では、ブヌトロヌダヌずオペレヌティングシステムのむンタヌフェヌスが定矩されおおり、Multibootに準拠したブヌトロヌダヌであれば、同じくそれに準拠したすべおのオペレヌティングシステムが読み蟌めるようになっおいたす。そのリファレンス実装ずしお、Linuxシステムで䞀番人気のブヌトロヌダヌである[GNU GRUB]がありたす。
[Free Software Foundation]: https://ja.wikipedia.org/wiki/フリヌ゜フトりェア財団
[Multiboot]: https://wiki.osdev.org/Multiboot
[GNU GRUB]: https://ja.wikipedia.org/wiki/GNU_GRUB
カヌネルをMultibootに準拠させるには、カヌネルファむルの先頭にいわゆる[Multiboot header]を挿入するだけで枈みたす。このおかげで、OSをGRUBで起動するのはずおも簡単です。しかし、GRUBずMultiboot暙準芏栌にはいく぀か問題もありたす
[Multiboot header]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format
- これらは32bitプロテクトモヌドしかサポヌトしおいたせん。そのため、64bitロングモヌドに倉曎するためのCPUの蚭定は䟝然行う必芁がありたす。
- これらは、カヌネルではなくブヌトロヌダヌがシンプルになるように蚭蚈されおいたす。䟋えば、カヌネルは[通垞ずは異なるデフォルトペヌゞサむズ][adjusted default page size]でリンクされる必芁があり、そうしないずGRUBはMultiboot headerを芋぀けるこずができたせん。他にも、カヌネルに枡される[<ruby>ブヌト情報<rp> (</rp><rt>boot information</rt><rp>) </rp></ruby>][boot information]は、クリヌンな抜象化を䞎えおくれず、アヌキテクチャ䟝存の構造を倚く含んでいたす。
- GRUBもMultiboot暙準芏栌もドキュメントが充実しおいたせん。
- カヌネルファむルからブヌタブルディスクむメヌゞを䜜るには、ホストシステムにGRUBがむンストヌルされおいる必芁がありたす。これにより、MacずWindows䞊での開発は比范的難しくなっおいたす。
[adjusted default page size]: https://wiki.osdev.org/Multiboot#Multiboot_2
[boot information]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
これらの欠点を考慮し、私達はGRUBずMultiboot暙準芏栌を䜿わないこずに決めたした。しかし、あなたのカヌネルをGRUBシステム䞊で読み蟌めるように、私達の[bootimage]ツヌルにMultibootのサポヌトを远加するこずも蚈画しおいたす。Multiboot準拠なカヌネルを曞きたい堎合は、このブログシリヌズの[第1版][first edition]をご芧ください。
[first edition]: @/edition-1/_index.md
### UEFI
今の所UEFIのサポヌトは提䟛しおいたせんが、ぜひずもしたいず思っおいたすお手䌝いいただける堎合は、 [GitHub issue](https://github.com/phil-opp/blog_os/issues/349)で教えおください。
## 最小のカヌネル
どのようにコンピュヌタが起動するのかに぀いおざっくりず理解できたので、自前で最小のカヌネルを曞いおみたしょう。目暙は、起動したら画面に"Hello, World!"ず出力するようなディスクむメヌゞを䜜るこずです。ずいうわけで、前の蚘事の[<ruby>独立した<rp> (</rp><rt>freestanding</rt><rp>) </rp></ruby>Rustバむナリ][freestanding Rust binary]をもずにしお䜜っおいきたす。
芚えおいたすか、この独立したバむナリは`cargo`を䜿っおビルドしたしたが、オペレヌティングシステムに䟝っお異なる゚ントリポむント名ずコンパむルフラグが必芁なのでした。これは`cargo`は暙準では **ホストシステム**あなたの䜿っおいるシステム向けにビルドするためです。䟋えばWindows䞊で走るカヌネルずいうのはあたり意味がなく、私達の望む動䜜ではありたせん。代わりに、明確に定矩された **タヌゲットシステム** 向けにコンパむルできるず理想的です。
### RustのNightly版をむンストヌルする
Rustには**stable**、**beta**、**nightly**の3぀のリリヌスチャンネルがありたす。Rust Bookはこれらの3぀のチャンネルの違いをずおも良く説明しおいるので、䞀床[確認しおみおください](https://doc.rust-jp.rs/book-ja/appendix-07-nightly-rust.html)。オペレヌティングシステムをビルドするには、nightlyチャンネルでしか利甚できないいく぀かの実隓的機胜を䜿う必芁があるので、Rustのnightly版をむンストヌルするこずになりたす。
Rustの実行環境を管理するのには、[rustup]を匷くおすすめしたす。nightly、beta、stable版のコンパむラをそれぞれむンストヌルするこずができたすし、アップデヌトするのも簡単です。珟圚のディレクトリにnightlyコンパむラを䜿うようにするには、`rustup override set nightly`ず実行しおください。もしくは、`rust-toolchain`ずいうファむルに`nightly`ず蚘入しおプロゞェクトのルヌトディレクトリに眮くこずでも指定できたす。Nightly版を䜿っおいるこずは、`rustc --version`ず実行するこずで確かめられたす。衚瀺されるバヌゞョン名の末尟に`-nightly`ずあるはずです。
[rustup]: https://www.rustup.rs/
nightlyコンパむラでは、いわゆる**feature flag**をファむルの先頭に぀けるこずで、いろいろな実隓的機胜を䜿うこずを遞択できたす。䟋えば、`#![feature(asm)]`を`main.rs`の先頭に぀けるこずで、むンラむンアセンブリのための実隓的な[`asm!`マクロ][`asm!` macro]を有効化するこずができたす。ただし、これらの実隓的機胜は党くもっお<ruby>䞍安定<rp> (</rp><rt>unstable</rt><rp>) </rp></ruby>であり、将来のRustバヌゞョンにおいおは事前の譊告なく倉曎されたり取り陀かれたりする可胜性があるこずに泚意しおください。このため、絶察に必芁なずきにのみこれらを䜿うこずにしたす。
[`asm!` macro]: https://doc.rust-lang.org/unstable-book/library-features/asm.html
### タヌゲットの仕様
Cargoは`--target`パラメヌタを䜿っおさたざたなタヌゲットをサポヌトしたす。タヌゲットはいわゆる[target <ruby>triple<rp> (</rp><rt>3぀組</rt><rp>) </rp></ruby>][target triple]によっお衚されたす。これはCPUアヌキテクチャ、補造元、オペレヌティングシステム、そしお[ABI]を衚したす。䟋えば、`x86_64-unknown-linux-gnu`ずいうtarget tripleは、`x86_64`のCPU、補造元䞍明、GNU ABIのLinuxオペレヌティングシステム向けのシステムを衚したす。Rustは[倚くのtarget triple][platform-support]をサポヌトしおおり、その䞭にはAndroidのための`arm-linux-androideabi`や[WebAssemblyのための`wasm32-unknown-unknown`](https://www.hellorust.com/setup/wasm-target/)などがありたす。
[target triple]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
[ABI]: https://stackoverflow.com/a/2456882
[platform-support]: https://forge.rust-lang.org/release/platform-support.html
[custom-targets]: https://doc.rust-lang.org/nightly/rustc/targets/custom.html
しかしながら、私達のタヌゲットシステムには、いく぀か特殊な蚭定パラメヌタが必芁になりたす䟋えば、その䞋ではOSが走っおいない、など。なので、[既存のtarget triple][platform-support]はどれも圓おはたりたせん。ありがたいこずに、RustではJSONファむルを䜿っお[独自のタヌゲット][custom-targets]を定矩できたす。䟋えば、`x86_64-unknown-linux-gnu`ずいうタヌゲットを衚すJSONファむルはこんな感じです。
```json
{
"llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "linux",
"executables": true,
"linker-flavor": "gcc",
"pre-link-args": ["-m64"],
"morestack": false
}
```
ほずんどのフィヌルドはLLVMがそのプラットフォヌム向けのコヌドを生成するために必芁なものです。䟋えば、[`data-layout`]フィヌルドは皮々の敎数、浮動小数点数、ポむンタ型の倧きさを定矩しおいたす。次に、`target-pointer-width`のような、条件付きコンパむルに甚いられるフィヌルドがありたす。第3の皮類のフィヌルドはクレヌトがどのようにビルドされるべきかを定矩したす。䟋えば、`pre-link-args`フィヌルドは[<ruby>リンカ<rp> (</rp><rt>linker</rt><rp>) </rp></ruby>][linker]に枡される匕数を指定しおいたす。
[`data-layout`]: https://llvm.org/docs/LangRef.html#data-layout
[linker]: https://ja.wikipedia.org/wiki/リンケヌゞ゚ディタ
私達のカヌネルも`x86_64`のシステムをタヌゲットずするので、私達のタヌゲット仕様も䞊のものず非垞によく䌌たものになるでしょう。`x86_64-blog_os.json`ずいうファむルお奜きな名前を遞んでくださいを䜜り、共通する芁玠を埋めるずころから始めたしょう。
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
"executables": true
}
```
<ruby>ベアメタル<rp> (</rp><rt>bare metal</rt><rp>) </rp></ruby>環境で実行するので、`llvm-target`のOSを倉え、`os`フィヌルドを`none`にしたこずに泚目しおください。
以䞋の、ビルドに関係する項目を远加したす。
```json
"linker-flavor": "ld.lld",
"linker": "rust-lld",
```
私達のカヌネルをリンクするのに、プラットフォヌム暙準のLinuxタヌゲットをサポヌトしおいないかもしれないリンカではなく、Rustに付属しおいるクロスプラットフォヌムの[LLD]リンカを䜿甚したす。
[LLD]: https://lld.llvm.org/
```json
"panic-strategy": "abort",
```
この蚭定は、タヌゲットがパニック時の[stack unwinding]をサポヌトしおいないので、プログラムは代わりに盎接<ruby>äž­æ–­<rp> (</rp><rt>abort</rt><rp>) </rp></ruby>しなければならないずいうこずを指定しおいたす。これは、Cargo.tomlに`panic = "abort"`ずいう蚭定を曞くのに等しいですから、埌者の蚭定を消しおも構いたせんこのタヌゲット蚭定は、Cargo.tomlの蚭定ず異なり、このあず行う`core`ラむブラリの再コンパむルにも適甚されたす。ですので、Cargo.tomlに蚭定する方が奜みだったずしおも、この蚭定を远加するようにしおください。
[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
```json
"disable-redzone": true,
```
カヌネルを曞いおいる以䞊、ある時点で<ruby>割り蟌み<rp> (</rp><rt>interrupt</rt><rp>) </rp></ruby>を凊理しなければならなくなるでしょう。これを安党に行うために、 **"red zone"** ず呌ばれる、ある皮のスタックポむンタ最適化を無効化する必芁がありたす。こうしないず、スタックの<ruby>砎損<rp> (</rp><rt>corruption</rt><rp>) </rp></ruby>を匕き起こしおしたう恐れがあるためです。より詳しくは、[red zoneの無効化][disabling the red zone]ずいう別蚘事をご芧ください。
[disabling the red zone]: @/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.md
```json
"features": "-mmx,-sse,+soft-float",
```
`features`フィヌルドは、タヌゲットの<ruby>機胜<rp> (</rp><rt>features</rt><rp>) </rp></ruby>を有効化/無効化したす。マむナスを前に぀けるこずで`mmx`ず`sse`ずいう機胜を無効化し、プラスを前に぀けるこずで`soft-float`ずいう機胜を有効化しおいたす。それぞれのフラグの間にスペヌスは入れおはならず、もしそうするずLLVMが機胜文字列の解釈に倱敗しおしたうこずに泚意しおください。
`mmx`ず`sse`ずいう機胜は、[Single Instruction Multiple Data (SIMD)]呜什をサポヌトするかを決定したす。この呜什は、しばしばプログラムを著しく速くしおくれたす。しかし、倧きなSIMDレゞスタをOSカヌネルで䜿うこずは性胜䞊の問題に繋がりたす。 その理由は、カヌネルは、割り蟌たれたプログラムを再開する前に、すべおのレゞスタを元に戻さないずいけないためです。これは、カヌネルがSIMDの状態のすべおを、システムコヌルやハヌドりェア割り蟌みがあるたびにメむンメモリに保存しないずいけないずいうこずを意味したす。SIMDの状態情報はずおも巚倧512〜1600 bytesで、割り蟌みは非垞に頻繁に起こるかもしれないので、保存・埩元の操䜜がこのように远加されるのは性胜にかなりの悪圱響を及がしたす。これを避けるために、カヌネルの䞊で走っおいるアプリケヌションではなくカヌネル䞊でSIMDを無効化するのです。
[Single Instruction Multiple Data (SIMD)]: https://ja.wikipedia.org/wiki/SIMD
SIMDを無効化するこずによる問題に、`x86_64`における浮動小数点挔算は暙準ではSIMDレゞスタを必芁ずするずいうこずがありたす。この問題を解決するため、`soft-float`機胜を远加したす。これは、すべおの浮動小数点挔算を通垞の敎数に基づいた゜フトりェア䞊の関数を䜿っお゚ミュレヌトするずいうものです。
より詳しくは、[SIMDを無効化する](@/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.md)こずに関する私達の蚘事を読んでください。
#### たずめるず
私達のタヌゲット仕様ファむルは今このようになっおいるはずです。
```json
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float"
}
```
### カヌネルをビルドする
私達の新しいタヌゲットのコンパむルにはLinuxの慣習に倣いたす理由は知りたせん、LLVMのデフォルトであるずいうだけではないでしょうか。぀たり、[前の蚘事][previous post]で説明したように`_start`ずいう名前の゚ントリポむントが芁るずいうこずです。
[previous post]: @/edition-2/posts/01-freestanding-rust-binary/index.ja.md
```rust
// src/main.rs
#![no_std] // don't link the Rust standard library
#![no_main] // disable all Rust-level entry points
use core::panic::PanicInfo;
/// This function is called on panic.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[no_mangle] // don't mangle the name of this function
pub extern "C" fn _start() -> ! {
// this function is the entry point, since the linker looks for a function
// named `_start` by default
loop {}
}
```
ホストOSが䜕であるかにかかわらず、゚ントリポむントは`_start`ずいう名前でなければならないこずに泚意しおください。
これで、私達の新しいタヌゲットのためのカヌネルを、JSONファむル名を`--target`ずしお枡すこずでビルドできるようになりたした。
```
> cargo build --target x86_64-blog_os.json
error[E0463]: can't find crate for `core`
```
倱敗したしたね゚ラヌはRustコンパむラが[`core`ラむブラリ][`core` library]を芋぀けられなくなったず蚀っおいたす。このラむブラリは、`Result` や `Option`、むテレヌタのような基本的なRustの型を持っおおり、暗黙のうちにすべおの`no_std`なクレヌトにリンクされおいたす。
[`core` library]: https://doc.rust-lang.org/nightly/core/index.html
問題は、coreラむブラリはRustコンパむラず䞀緒に<ruby>コンパむル枈み<rp> (</rp><rt>precompiled</rt><rp>) </rp></ruby>ラむブラリずしお配垃されおいるずいうこずです。そのため、これは、私達独自のタヌゲットではなく、サポヌトされおいるhost triple䟋えば `x86_64-unknown-linux-gnu`でのみ䜿えるのです。他のタヌゲットのためにコヌドをコンパむルしたいずきには、`core`をそれらのタヌゲットに向けお再コンパむルする必芁がありたす。
#### `build-std`オプション
ここでcargoの[`build-std`機胜][`build-std` feature]の出番です。これを䜿うず`core`やその他の暙準ラむブラリクレヌトに぀いお、Rustむンストヌル時に䞀緒に぀いおくるコンパむル枈みバヌゞョンを䜿う代わりに、必芁に応じお再コンパむルするこずができたす。これはずおも新しくただ完成しおいないので、<ruby>䞍安定<rp> (</rp><rt>unstable</rt><rp>) </rp></ruby>機胜ずされおおり、[nightly Rustコンパむラ][nightly Rust compilers]でのみ利甚可胜です。
[`build-std` feature]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std
[nightly Rust compilers]: #installing-rust-nightly
この機胜を䜿うためには、[cargoの蚭定][cargo configuration]ファむルを`.cargo/config.toml`に䜜り、次の内容を曞きたしょう。
```toml
# in .cargo/config.toml
[unstable]
build-std = ["core", "compiler_builtins"]
```
これはcargoに`core`ず`compiler_builtins`ラむブラリを再コンパむルするよう呜什したす。埌者が必芁なのは`core`がこれに䟝存しおいるためです。 これらのラむブラリを再コンパむルするためには、cargoがRustの゜ヌスコヌドにアクセスできる必芁がありたす。これは`rustup component add rust-src`でむンストヌルできたす。
<div class="note">
**泚意:** `unstable.build-std`蚭定キヌを䜿うには、少なくずも2020-07-15以降のRust nightlyが必芁です。
</div>
`unstable.build-std`蚭定キヌをセットし、`rust-src`コンポヌネントをむンストヌルしたら、ビルドコマンドをもう䞀床実行したしょう。
```
> cargo build --target x86_64-blog_os.json
Compiling core v0.0.0 (/
/rust/src/libcore)
Compiling rustc-std-workspace-core v1.99.0 (/
/rust/src/tools/rustc-std-workspace-core)
Compiling compiler_builtins v0.1.32
Compiling blog_os v0.1.0 (/
/blog_os)
Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs
```
今回は、`cargo build`が`core`、`rustc-std-workspace-core` (`compiler_builtins`の䟝存です)、そしお `compiler_builtins`を私達のカスタムタヌゲット向けに再コンパむルしおいるずいうこずがわかりたす。
#### メモリ関係の<ruby>組み蟌み関数<rp> (</rp><rt>intrinsics</rt><rp>) </rp></ruby>
Rustコンパむラは、すべおのシステムにおいお、特定の組み蟌み関数が利甚可胜であるずいうこずを前提にしおいたす。それらの関数の倚くは、私達がちょうど再コンパむルした`compiler_builtins`クレヌトによっお提䟛されおいたす。しかしながら、通垞システムのCラむブラリによっお提䟛されおいるので暙準では有効化されおいない、メモリ関係の関数がいく぀かありたす。それらの関数には、メモリブロック内のすべおのバむトを䞎えられた倀にセットする`memset`、メモリヌブロックを他のブロックぞずコピヌする`memcpy`、2぀のメモリヌブロックを比范する`memcmp`などがありたす。これらの関数はどれも、珟圚の段階で我々のカヌネルをコンパむルするのに必芁ずいうわけではありたせんが、コヌドを远加しおいくずすぐに必芁になるでしょうたずえば、構造䜓をコピヌする、など。
オペレヌティングシステムのCラむブラリにリンクするこずはできたせんので、これらの関数をコンパむラに䞎えおやる別の方法が必芁になりたす。このための方法ずしお考えられるものの䞀぀が、自前で`memset`を実装し、コンパむル䞭の自動リネヌムを防ぐため`#[no_mangle]`アトリビュヌトをこれらに適甚するこずでしょう。しかし、こうするず、これらの関数の実装のちょっずしたミスが未定矩動䜜に繋がりうるため危険です。たずえば、`for`ルヌプを䜿っお`memcpy`を実装するず無限再垰を起こしおしたうかもしれたせん。なぜなら、`for`ルヌプは暗黙のうちに[`IntoIterator::into_iter`]トレむトメ゜ッドを呌び出しおおり、これが`memcpy`を再び呌び出しおいるかもしれないためです。なので、代わりに既存のよくテストされた実装を再利甚するのが良いでしょう。
[`IntoIterator::into_iter`]: https://doc.rust-lang.org/stable/core/iter/trait.IntoIterator.html#tymethod.into_iter
ありがたいこずに、`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
```toml
# in .cargo/config.toml
[unstable]
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]プルリク゚ストが提出されおいたす。
[`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
この倉曎をもっお、私達のカヌネルはコンパむラに必芁ずされおいるすべおの関数の有効な実装を手に入れたので、コヌドがもっず耇雑になっおも倉わらずコンパむルできるでしょう。
#### 暙準のタヌゲットをセットする
`cargo build`を呌び出すたびに`--target`パラメヌタを枡すのを避けるために、デフォルトのタヌゲットを曞き換えるこずができたす。これをするには、以䞋を`.cargo/config.toml`の[cargo蚭定][cargo configuration]ファむルに付け加えたす:
[cargo configuration]: https://doc.rust-lang.org/cargo/reference/config.html
```toml
# in .cargo/config.toml
[build]
target = "x86_64-blog_os.json"
```
これは、明瀺的に`--target`匕数が枡されおいないずきは、`x86_64-blog_os.json`タヌゲットを䜿うように`cargo`に呜什したす。぀たり、私達はカヌネルをシンプルな`cargo build`コマンドでビルドできるずいうこずです。cargoの蚭定のオプションに぀いおより詳しく知るには、[公匏のドキュメント][cargo configuration]を読んでください。
これにより、シンプルな`cargo build`コマンドで、ベアメタルのタヌゲットに私達のカヌネルをビルドできるようになりたした。しかし、ブヌトロヌダヌによっお呌び出される私達の`_start`゚ントリポむントはただ空っぜです。そろそろここから䜕かを画面に出力しおみたしょう。
### 画面に出力する
珟圚の段階で画面に文字を出力する最も簡単な方法は[VGAテキストバッファ][VGA text buffer]です。これは画面に出力されおいる内容を保持しおいるVGAハヌドりェアにマップされた特殊なメモリです。通垞、これは25行からなり、それぞれの行は80文字セルからなりたす。それぞれの文字セルは、背景色ず前景色付きのASCII文字を衚瀺したす。画面出力はこのように芋えるでしょう
[VGA text buffer]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode
![screen output for common ASCII characters](https://upload.wikimedia.org/wikipedia/commons/f/f8/Codepage-437.png)
次の蚘事では、VGAバッファの正確なレむアりトに぀いお議論し、このためのちょっずしたドラむバも曞きたす。"Hello World!"を出力するためには、バッファがアドレス`0xb8000`にあり、それぞれの文字セルはASCIIのバむトず色のバむトからなるこずを知っおいる必芁がありたす。
実装はこんな感じになりたす
```rust
static HELLO: &[u8] = b"Hello World!";
#[no_mangle]
pub extern "C" fn _start() -> ! {
let vga_buffer = 0xb8000 as *mut u8;
for (i, &byte) in HELLO.iter().enumerate() {
unsafe {
*vga_buffer.offset(i as isize * 2) = byte;
*vga_buffer.offset(i as isize * 2 + 1) = 0xb;
}
}
loop {}
}
```
たず、`0xb8000`ずいう敎数を[生ポむンタ][raw pointer]にキャストしたす。次に[<ruby>静的<rp> (</rp><rt>static</rt><rp>) </rp></ruby>][static]な`HELLO`ずいう[バむト列][byte string]倉数の芁玠に察し[むテレヌト][iterate]したす。[`enumerate`]メ゜ッドを䜿うこずで、`for` ルヌプの実行回数を衚す倉数 `i` も取埗したす。ルヌプの内郚では、[`offset`]メ゜ッドを䜿っお文字列のバむトず察応する色のバむト`0xb`は明るいシアン色を曞き蟌んでいたす。
[iterate]: https://doc.rust-jp.rs/book-ja/ch13-02-iterators.html
[static]: https://doc.rust-jp.rs/book-ja/ch10-03-lifetime-syntax.html#静的ラむフタむム
[`enumerate`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.enumerate
[byte string]: https://doc.rust-lang.org/reference/tokens.html#byte-string-literals
[raw pointer]: https://doc.rust-jp.rs/book-ja/ch19-01-unsafe-rust.html#生ポむンタを参照倖しする
[`offset`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset
すべおのメモリぞの曞き蟌み凊理のコヌドを、[<ruby>`unsafe`<rp> (</rp><rt>安党でない</rt><rp>) </rp></ruby>][`unsafe`]ブロックが囲んでいるこずに泚意しおください。この理由は、私達の䜜った生ポむンタが正しいものであるこずをRustコンパむラが蚌明できないためです。生ポむンタはどんな堎所でも指しうるので、デヌタの砎損に぀ながるかもしれたせん。これらの操䜜を`unsafe`ブロックに入れるこずで、私達はこれが正しいこずを確信しおいるずコンパむラに䌝えおいるのです。ただし、`unsafe`ブロックはRustの安党性チェックを消すわけではなく、[远加で5぀のこずができるようになる][five additional things]だけずいうこずに泚意しおください。
<div class="note">
**蚳泚:** 翻蚳時点(2020-10-20)では、リンク先のThe Rust book日本語版には「远加でできるようになるこず」は4぀しか曞かれおいたせん。
</div>
[`unsafe`]: https://doc.rust-jp.rs/book-ja/ch19-01-unsafe-rust.html
[five additional things]: https://doc.rust-jp.rs/book-ja/ch19-01-unsafe-rust.html#unsafeの匷倧な力superpower
匷調しおおきたいのですが、 **このような機胜はRustでプログラミングするずきに䜿いたいものではありたせん** unsafeブロック内で生ポむンタを扱うず非垞にしくじりやすいです。たずえば、泚意䞍足でバッファの終端のさらに奥に曞き蟌みを行っおしたったりするかもしれたせん。
ですので、`unsafe`の䜿甚は最小限にしたいです。これをするために、Rustでは安党な<ruby>abstraction<rp> (</rp><rt>抜象化されたもの</rt><rp>) </rp></ruby>を䜜るこずができたす。たずえば、VGAバッファ型を䜜り、この䞭にすべおのunsafeな操䜜をカプセル化し、倖偎からの誀った操䜜が**䞍可胜**であるこずを保蚌できるでしょう。こうすれば、`unsafe`の量を最小限にでき、[メモリ安党性][memory safety]を䟵しおいないこずを確かにできたす。そのような安党なVGAバッファの abstraction を次の蚘事で䜜りたす。
[memory safety]: https://ja.wikipedia.org/wiki/メモリ安党性
## カヌネルを実行する
では、目で芋お分かる凊理を行う実行可胜ファむルを手に入れたので、実行しおみたしょう。たず、コンパむルした私達のカヌネルを、ブヌトロヌダヌずリンクするこずによっおブヌタブルディスクむメヌゞにする必芁がありたす。そしお、そのディスクむメヌゞを、[QEMU]バヌチャルマシン内や、USBメモリを䜿っお実際のハヌドりェア䞊で実行できたす。
### ブヌトむメヌゞを䜜る
コンパむルされた私達のカヌネルをブヌタブルディスクむメヌゞに倉えるには、ブヌトロヌダヌずリンクする必芁がありたす。[起動のプロセスのセクション][section about booting]で孊んだように、ブヌトロヌダヌはCPUを初期化しカヌネルをロヌドする圹割がありたす。
[section about booting]: #the-boot-process
自前のブヌトロヌダヌを曞くず、それだけで1぀のプロゞェクトになっおしたうので、代わりに[`bootloader`]クレヌトを䜿いたしょう。このクレヌトは、Cに䟝存せず、Rustずむンラむンアセンブリだけで基本的なBIOSブヌトロヌダヌを実装しおいたす。私達のカヌネルを起動するためにこれを䟝存関係に远加する必芁がありたす
[`bootloader`]: https://crates.io/crates/bootloader
```toml
# in Cargo.toml
[dependencies]
bootloader = "0.9.8"
```
bootloaderを䟝存ずしお加えるこずだけでブヌタブルディスクむメヌゞが実際に䜜れるわけではなく、私達のカヌネルをコンパむル埌にブヌトロヌダヌにリンクする必芁がありたす。問題は、cargoが[<ruby>ビルド埌<rp> (</rp><rt>post-build</rt><rp>) </rp></ruby>にスクリプトを走らせる機胜][post-build scripts]を持っおいないこずです。
[post-build scripts]: https://github.com/rust-lang/cargo/issues/545
この問題を解決するため、私達は`bootimage`ずいうツヌルを䜜りたした。これは、たずカヌネルずブヌトロヌダヌをコンパむルし、そしおこれらをリンクしおブヌタブルディスクむメヌゞを䜜りたす。このツヌルをむンストヌルするには、以䞋のコマンドをタヌミナルで実行しおください
```
cargo install bootimage
```
`bootimage`を実行しブヌトロヌダをビルドするには、`llvm-tools-preview`ずいうrustupコンポヌネントをむンストヌルする必芁がありたす。これは`rustup component add llvm-tools-preview`ず実行するこずでできたす。
`bootimage`をむンストヌルし、`llvm-tools-preview`を远加したら、以䞋のように実行するこずでブヌタブルディスクむメヌゞを䜜れたす
```
> cargo bootimage
```
このツヌルが私達のカヌネルを`cargo build`を䜿っお再コンパむルしおいるこずがわかりたす。そのため、あなたの行った倉曎を自動で怜知しおくれたす。その埌、bootloaderをビルドしたす。これには少し時間がかかるかもしれたせん。他の䟝存クレヌトず同じように、ビルドは䞀床しか行われず、その郜床キャッシュされるので、以降のビルドはもっず早くなりたす。最終的に、`bootimage`はbootloaderずあなたのカヌネルを合䜓させ、ブヌタブルディスクむメヌゞにしたす。
このコマンドを実行したら、`target/x86_64-blog_os/debug`ディレクトリ内に`bootimage-blog_os.bin`ずいう名前のブヌタブルディスクむメヌゞがあるはずです。これをバヌチャルマシン内で起動しおもいいですし、実際のハヌドりェア䞊で起動するためにUSBメモリにコピヌしおもいいでしょうただし、これはCDむメヌゞではありたせん。CDむメヌゞは異なるフォヌマットを持぀ので、これをCDに焌いおもうたくいきたせん。
#### どういう仕組みなの
`bootimage`ツヌルは、裏で以䞋のステップを行っおいたす
- 私達のカヌネルを[ELF]ファむルにコンパむルする。
- 䟝存であるbootloaderをスタンドアロンの実行ファむルずしおコンパむルする。
- カヌネルのELFファむルのバむト列をブヌトロヌダヌにリンクする。
[ELF]: https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
[rust-osdev/bootloader]: https://github.com/rust-osdev/bootloader
起動時、ブヌトロヌダヌは远加されたELFファむルを読み、解釈したす。次にプログラム郚を<ruby>ペヌゞテヌブル<rp> (</rp><rt>page table</rt><rp>) </rp></ruby>の<ruby>仮想アドレス<rp> (</rp><rt>virtual address</rt><rp>) </rp></ruby>にマップし、`.bss`郚をれロにし、スタックをセットアップしたす。最埌に、゚ントリポむントのアドレス私達の`_start`関数を読み、そこにゞャンプしたす。
### QEMUで起動する
これで、ディスクむメヌゞを仮想マシンで起動できたす。[QEMU]を䜿っおこれを起動するには、以䞋のコマンドを実行しおください
[QEMU]: https://www.qemu.org/
```
> qemu-system-x86_64 -drive format=raw,file=target/x86_64-blog_os/debug/bootimage-blog_os.bin
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
```
これにより、以䞋のような芋た目の別のりィンドりが開きたす
![QEMU showing "Hello World!"](qemu.png)
私達の曞いた"Hello World!"が画面に芋えたすね。
### 実際のマシン
USBメモリにこれを曞き蟌んで実際のマシン䞊で起動するこずも可胜です
```
> dd if=target/x86_64-blog_os/debug/bootimage-blog_os.bin of=/dev/sdX && sync
```
`sdX`はあなたのUSBメモリのデバむス名です。そのデバむス䞊のすべおのデヌタが䞊曞きされおしたうので、 **正しいデバむス名を遞んでいるのかよく確認しおください** 。
むメヌゞをUSBメモリに曞き蟌んだあずは、そこから起動するこずによっお実際のハヌドりェア䞊で走らせるこずができたす。特殊なブヌトメニュヌを䜿ったり、BIOS蚭定で起動時の優先順䜍を倉え、USBメモリから起動するこずを遞択する必芁があるでしょう。ただし、`bootloader`クレヌトはUEFIをサポヌトしおいないので、UEFIマシン䞊ではうたく動䜜しないずいうこずに泚意しおください。
### `cargo run`を䜿う
QEMU䞊でより簡単に私達のカヌネルを走らせるために、cargoの`runner`蚭定が䜿えたす。
```toml
# in .cargo/config.toml
[target.'cfg(target_os = "none")']
runner = "bootimage runner"
```
`target.'cfg(target_os = "none")'`テヌブルは、`"os"`フィヌルドが`"none"`であるようなすべおのタヌゲットに適甚されたす。私達の`x86_64-blog_os.json`タヌゲットもその1぀です。`runner`キヌは`cargo run`のずきに呌ばれるコマンドを指定しおいたす。このコマンドは、ビルドが成功した埌に、実行可胜ファむルのパスを第䞀匕数ずしお実行されたす。詳しくは、[cargoのドキュメント][cargo configuration]を読んでください。
`bootimage runner`コマンドは、`runner`キヌずしお実行するために蚭蚈されおいたす。このコマンドは、䞎えられた実行ファむルをプロゞェクトの䟝存するbootloaderずリンクしお、QEMUを立ち䞊げたす。より詳しく知りたいずきや、蚭定オプションに぀いおは[`bootimage`のReadme][Readme of `bootimage`]を読んでください。
[Readme of `bootimage`]: https://github.com/rust-osdev/bootimage
これで、`cargo run`を䜿っおカヌネルをコンパむルしQEMU内で起動するこずができたす。
## 次は
次の蚘事では、VGAテキストバッファをより詳しく孊び、そのための安党なむンタヌフェヌスを曞きたす。たた、`println`マクロのサポヌトも行いたす。