RustでOSを書いてみる(環境構築編)

  • 33
    いいね
  • 2
    コメント

本記事は自作OS Advent Calendar 2016 16日目の 記事です。

はじめに

大学に入った頃からOS自作に興味があり、「30日でできる! OS自作入門」を見ながらはりぼてOSもどきをつくって遊んでいました(OS自作を始めたのは本が出版される少し前です)。

時は流れて2016年・・・RustでOSが作れるという話をどこかで知って以来、RustでOSを作ってみたいという気持ちが強くなり、作り始めることにしました。

作り始めてみたものの、いまだにほとんど何もできてない状態ですが、環境構築してブートプログラムからRustのコードを呼び出すくらいはできたので、本記事ではRustを使ってOS自作したい人に向けて環境構築の手順について解説していきたいと思います。

注意事項

本記事で解説する手順はi686(Intel 32bit CPU)環境向けとなります。また、Mac OS X El Capitan、及びDebian or Ubuntu上でビルドすることを念頭に置いているため、特にWindows環境で構築したい方については、別途読みかえが必要になります。

Let's 環境構築!

rustupのインストール

Rustをインストールする方法は様々ありますが、Rustにもpyenvやrbenvのようなツールがあります。それがrustupです。将来的にi686以外の環境向けにクロスコンパイルしたい場合に大変便利なので、rustupを使ってRustをインストールしてみたいと思います。

ここでは解説していない使い方などは、下記GitHubリポジトリのReadme.mdに書いてあります。
https://github.com/rust-lang-nursery/rustup.rs

rustupのインストールには、下記コマンドを打つだけです(Mac, Linuxの場合)。

curl https://sh.rustup.rs -sSf | sh

このコマンドを打つことで、インストールスクリプトをダウンロードして、あとはスクリプトが勝手にインストールしてくれます。スクリプトの実行が終わった頃には、rustupのインストールも完了です!

rustupがインストールできたら、環境にもよりますが、シェルを立ち上げた際に

~/.cargo/env

が読み込まれるように設定をしておくと良いでしょう。

Rustのインストール

さて、次はrustupを使ってRustをインストールしていきます。

インストールに当たって注意してほしいのは、RustでOSを自作するために必要な言語機能はnightlyビルドのRustを使わなければならないということです。

この辺はできればstableで作れるようにしてくれると良いのですが・・・。

というわけで、まずはnightlyビルドのものを使えるよう、下記コマンドを実行してください。

rustup install nightly

また、環境にもよりますが、ターゲット環境と開発環境のアーキテクチャが異なる場合、本記事でいうとi686以外の環境(x86_64など)でi686向けに開発したい方は、i686向けのクロスコンパイル環境を構築する必要があります。ただ、rustupを使えば下記コマンド1つで構築できます!

rustup target add i686-unknown-linux-gnu

インストールが完了したら、デフォルトで使用するRustコンパイラをnightlyにするため、下記コマンドを実行してください。

rustup default nightly

ここまでできたら、シェル上でrustc --versionと打ってみてrustcがバージョン情報を出力すれば、インストールは完了です。

もう少し詳細なインストール情報が見たい場合は、rustup showと打ってみてください。環境によりますが、下記のような情報が出てくるはずです。

Default host: x86_64-apple-darwin

installed toolchains
--------------------

stable-x86_64-apple-darwin
nightly-2016-07-03-x86_64-apple-darwin
nightly-x86_64-apple-darwin (default)

installed targets for active toolchain
--------------------------------------

i686-unknown-linux-gnu
x86_64-apple-darwin
x86_64-unknown-linux-gnu

active toolchain
----------------

nightly-x86_64-apple-darwin (default)
rustc 1.15.0-nightly (d9bdc636d 2016-11-24)

NASM、binutils、QEMUのインストール

Rust言語だけでOSが書けるわけではなく、特にブート周りはアセンブラが必要になります。さらに、これらプログラムとRustで書いたプログラムをリンクするためのリンカ(ld)やコンパイラが出力したオブジェクトファイルを解析するためのobjdumpreadelfといったコマンドが必要となります。objdumpやreadelfなどのコマンドは、binutilsというツールに含まれています。

これらをインストールしていきましょう。まずはNASMです。

アセンブラといえば、他にもgas(GNU Assembler)もありますが、自分はintel記法に慣れ親しんだ人間なので、やっぱりintel記法で書けるNASMを選択したくなります。

NASMについてはクロスコンパイルなどは考えなくて良いので、各OS環境ごとにHomebrewやapt-getなどを使ってインストールしてください。参考までに、Homebrew(Mac)とapt-get(Ubuntu、Debian)の場合のインストールコマンドを書いておきます。

Homebrewの場合

brew install nasm

apt-getの場合

apt-get install nasm

次にbinutilsのインストールについてですが、こちらは環境によってはNASMと同じようにインストール可能ですが、ここではソースコードからのビルドを行いたいと思います。

まずは下記からソースをダウンロードしましょう。
http://ftp.gnu.org/gnu/binutils/

ソースを展開したら、ソースディレクトリへ移動して下記コマンドでビルドします。なお./configureの際のオプションについては、i686用のバイナリが欲しいので、i686-unknown-linux-gnuを指定してください。
※ version 2.27の場合です。

tar zxvf binutils-2.27.tar.gz
cd binutils-2.27
./configure --target=i686-unknown-linux-gnu
make
sudo make install

そして、作ったOSを動かすためのエミュレータ(QEMU)をインストールします。QEMUはnasmと同じくHomebrew(Mac)とapt-get(Ubuntu、Debian)でインストール可能です。

Homebrewの場合

brew install qemu

apt-getの場合

apt-get install qemu

これでNASMとbinutils、QEMUを使えるようになりました。

mformat, mcopyのインストール

最後は、ディスクイメージを作成するために使うmformatとmcopyのインストールです。これらのコマンドはmtoolsというツール群の中に入っているので、mtoolsをインストールします。

Homebrewの場合

brew install mtools

apt-getの場合

apt-get install mtools

これで、OS開発に必要な最低限の道具は揃いました。

ビルドできるか試してみる

ブートローダーを用意する

ブート周りの話を解説し始めるとAdvent Calendar1日分かそれ以上かかるので、ここではサクッと既存のアセンブリコードを持ってくることにします。

まず下記2つのコードをダウンロードしてください。
https://github.com/kotetuco/ructiss/blob/master/arch/i386/boot/ipl.asm
https://github.com/kotetuco/ructiss/blob/master/arch/i386/boot/secondboot.asm

ダウンロードしたら、下記コマンドでこれら2つのコードをアセンブルします。

nasm -f bin -o ipl.bin ipl.asm
nasm -f bin -o secondboot.bin secondboot.asm

Rust Core Libraryを用意する

さて、ここからはRustのコードを書いていくことになるのですが、その前に、Rustのコードを書くためにはRustの基本的なデータ型などが定義されたRust Core Library(libcore)というライブラリが必要なので、先にビルドしておきましょう。

まず、Rustのコード一式をダウンロードします。
https://github.com/rust-lang/rust

ダウンロードしたら、以下のコマンドを使って、Coreライブラリのみコンパイルしてください。

rustc --verbose --target=i686-unknown-linux-gnu --crate-type=rlib --emit=link,dep-info -C opt-level=2 -C no-prepopulate-passes -C no-stack-check -Z no-landing-pads -o libcore.rlib ./rust-lang/src/libcore/lib.rs

注意:Rustのソースコードのバージョンとインストール済みRustのバージョンによっては、コンパイルに失敗することがあります。その場合は、ダウンロードするRustのコードのバージョンを上げるなり下げるなりしてください。

Rustで書いたOSのコードをコンパイルする

ここまで出来たら、いよいよRustのコードを書いてみたいと思います。init_os.rsという名前で、次のようなコードを書いてみてください。

#![feature(lang_items)]
#![feature(start)]
#![no_main]
#![feature(no_std)]
#![no_std]
#![feature(asm)]

#[no_mangle]
fn hlt() {
    unsafe {
        asm!("hlt");
    }
}

#[no_mangle]
#[start]
pub extern fn init_os() {
    loop {
        hlt()
    }
}

#[lang = "eh_personality"]
extern fn eh_personality() {}

#[lang = "panic_fmt"]
extern fn panic_fmt() -> ! { loop {} }

詳しく解説しませんが、何をやっているかというと、単純にhltというアセンブリ命令を打ち続けているだけです。なので、仮にビルドできたとしても、QEMUの画面に出てくるのは真っ黒な画面です :disappointed_relieved:

書けたら、上記コードをコンパイルしましょう。

rustc --target=i686-unknown-linux-gnu --crate-type=staticlib --emit=obj -C lto -C opt-level=2 -C no-prepopulate-passes -C no-stack-check -Z verbose -Z no-landing-pads -o init_os.o init_os.rs --extern core=libcore.rlib

リンク・OSイメージ作成

さて、あとはリンクしてOSのイメージを作るだけです。

まずここまでコンパイルして作成したオブジェクトファイル一式をリンクしましょう・・・と言っても、実はinit_os.rsをコンパイルした際に既にlibcoreをリンクしているため、ここでは単純にリンカスクリプトで定義したオブジェクトファイルの形式に変換しているだけという感じです。

リンカスクリプトについても、奥が深いため、ここでは詳しく解説せず、既存のリンカスクリプトを持ってきましょう。
https://github.com/kotetuco/ructiss/blob/master/kernel.ld
※ もしリンカやリンカスクリプトについて詳しく知りたい方がいれば、「リンカ・ローダ実践開発テクニック」という本を読むと良いと思います。

ダウンロードできたら、下記コマンドでリンクを行います。

i686-unknown-linux-gnu-ld -v -nostdlib -Tdata=0x00310000 init_os.o -T kernel.ld -o kernel.bin

次に、最初にアセンブルしたオブジェクトファイルのうち、secondboot.binを今リンクしたkernel.binの前にくっつけます。くっつけるには下記コマンドを使います。

cat secondboot.bin kernel.bin > first_os.sys

ここまでくれば、あと一息です。最後は、最初にアセンブルしたオブジェクトファイルのうちipl.binとfirst_os.sysを用いて、フロッピーディスクイメージを作ります。
※ 「今時フロッピーイメージかよ・・・」とか、「フロッピーディスクって何?」という指摘はごもっともです。。この辺も今後isoイメージとかに対応していきたいです。

mformat -f 1440 -C -B ipl.bin -i first_os.img ::
mcopy -i first_os.img first_os.sys ::

ここまで出来れば、OSのビルドは大成功です!

せっかくなので、QEMUで動かしてみましょうか。

qemu-system-i386 -m 32 -localtime -vga std -fda first_os.img -monitor stdio

※ QEMUだけ、i386なので注意。

下のような真っ黒なQEMUの画面が出てくれば、(全く見栄えは良くないですが)大成功です!

スクリーンショット 2016-12-16 1.01.36.png

あとがき

なるべく、OS自作に興味があり、かつRustを使う方を念頭に置いて書いてみました。少しでもお役に立てたら幸いです。

なお、上記環境構築手順をDockerfileにまとめたものがあるので、よかったらどうぞ。
https://github.com/kotetuco/rust-baremetal

本当はRustでOSを作る利点について話をしたいと思いましたが、利点を話せるほどRustに触れてるわけではないので、またの機会に話をしたいと思います。