Skip to content

Latest commit

 

History

History
137 lines (86 loc) · 6.44 KB

memo.md

File metadata and controls

137 lines (86 loc) · 6.44 KB

Writing an OS in Rust

Writing an OS in Rust (Second Edition)

A Freestanding Rust Binary

最初の一歩は、標準ライブラリを使わない実行可能バイナリを作ること。

Introduction

OSのカーネルを書くためには、どのOSの機能にも依存しないコードを書かないといけない。 つまり

  • スレッド
  • ファイル
  • ヒープメモリ
  • ネットワーキング
  • 乱数
  • 標準出力
  • その他もろもろ

が使えないということ。

Rustの標準ライブラリも使えない。 ただし

  • イテレータ
  • クロージャ
  • パターンマッチ
  • Option
  • Result
  • 文字列フォーマット
  • 所有権システム

なんかは使える。

RustでOSのカーネルを作るためには、OSに依存せずに実行できるものを作らないといけない。

"freestanding"とか"bare-metal"なんて呼ばれているらしい。 それを作っていく。

Disabling the Standard Library

ふつう、Rustのクレートは標準ライブラリとリンクしていて、

スレッドやファイルやネットワーキングなどの機能はOSに依存している。

またlibc(Cの標準ライブラリ)にも依存していて、これがOSと緊密に連携している。

今回の目標はOSを書いてみることなので、あらゆるOS依存のライブラリは使わない。

そこで自動で標準ライブラリがインクルードされるのを防ぐためにno_stdアトリビュートを使う。

The no_std Attribute

cargoで新しいプロジェクトを作り、他はいじらずにmain.rsの先頭に#![no_std]をつける。

これでcargo buildしてみると、println!マクロがないと怒られる。

println!は、標準出力(OSが提供する特別なファイルディスクリプタ)に書き込むために

標準ライブラリに用意されているマクロなのでno_stdだと使えない。

なのでprintln!("Hello, world!")を取り除いて空のmain関数にする。

cargo buildすると、

> cargo build
error: `#[panic_handler]` function required, but not found
error: language item required, but not found: `eh_personality`

みたいなエラーがでる。

#[panic_handler]の関数と、eh_personalityとかいう言語アイテムがないらしい。

この2つのエラーを解決する。

Panic Implementation

panic_handlerアトリビュートは、panicが起こったときに呼ばれる関数を定義するやつ。

no_stdだと自分で用意する必要がある。

書かれているとおりにpanic関数を定義する。

!型のことをnever typeといって、それを返す関数をdiverging function(発散する関数)というらしい。

The eh_personality Language Item

Language items(言語アイテム)というのはコンパイラ向けの特別な関数や型のこと。たとえばCopyトレイトがそう。

ここではeh_personality言語アイテムが必要なんだけど、今回は自分で実装しない。 (型チェックが効かなかったり、実装がunstableで、要するに煩雑なのかな)

代わりの方法で回避する。

このeh_personality 言語アイテムは、panic時のスタックのアンワインドに関わるものらしい。

いろいろ込み入っていて、OS specificなライブラリを使っていたりもするので今回は踏み込まない。ただ単にアンワインドを無効にする。

Disabling Unwinding

RustにはpanicをabortするオプションがあるのでCargo.tomlに設定する。

これでeh_personalityがなくても大丈夫になる。

cargo buildしてみると今度は

> cargo build
error: requires `start` lang_item

というエラーがでる。

The start attribute

プログラムを走らせたときいきなりmain関数が呼ばれると思っている人がいるが実際はそうじゃなく、ほとんどの言語にはランタイムがあって(たとえばJavaのGCだったり、Goのgoroutineだったりを提供する)、それらはmain関数が呼ばれる前に初期化されている必要がある。

ふつうのRustバイナリの場合は、まずcrt0(c runtime zero)というCのランタイムライブラリ(スタートアップルーチン)が呼ばれ、こいつがスタックを用意してくれたりレジスタをあれこれやってくれるらしい。(※よくわかってない)

それからCのランタイムがRustランタイムのエントリーポイントを呼び出し(これがstart言語アイテム)、そこからmain関数が呼ばれる。

今回はRustランタイムとcrt0が使えない。start言語アイテムを自分で作ることもできない(crt0が必要なため)

なのでcrt0エントリーポイントをオーバーライドしてしまう。

Overwriting the Entry Point

ふつうのエントリーポイントを使わないことをRustコンパイラに教えるために#![no_main]アトリビュートをつける。そしてmain関数を削除する。

main関数は、それを呼び出すランタイムが存在しないと意味をなさないので不要になる。

代わりに、OSのエントリーポイントをオーバーライドする。

エントリーポイントの名前はOSごとに慣習があるが、今回はLinuxのデフォルトエントリーポイントに使われている_startという名前で関数を作ってオーバーライドする。

名前マングリング(名前修飾)をさせないために#![no_mangle]をつける。

no_mangleをつけないと_startがマングリングされて_ZN3blog_os4_start7hb173fedf945531caEみたいなシンボルになってしまい、リンカがエントリーポイントとして認識できなくなる。

extern "C"をつけて、Cの呼出規約を使うことをコンパイラに示す。

_startは値を返す必要がないためdiverging functionにする。エントリーポイントはOSやブートローダーから直接呼ばれるもので、関数から呼ばれることはないため値を返さない。値を返すかわりにOSのexit(システムコール)したりする。

今はとりあえず無限にループしとけばいい的なことが書かれている。