【OS自作】littleosbook をやってみる #1

べつに暇なわけじゃないけど、たまには低レイヤなことをしたい。簡易的なオペレーティング・システムの開発方法を解説したサイト The little book about OS development を見つけたのでやってみる。ページは以下。

littleosbook.github.io

追記:本家の littleosbook は不備が多いがメンテナンスされてない。以下はフォークされたリポジトリであり、かなりの不備が修正されている。絶対こっち見るほうがオススメ。

ordoflammae.github.io

作業内容をここにメモって残しておけば、この先10年で3人くらいの役には立つだろう。このチュートリアルはけっこう長いので、ブログ記事としては何回かに分けることになりそう。

簡易的なコンパイラはいくつか作ったことあるけど OS は作ったことない。調べながらやればなんとかなるだろう。ちなみに使用した環境は Ubuntu の 20.04.3。

1 Introduction

導入部分。想定読者のレベルはそこそこ高そう。低レイヤなプログラミングには慣れていないが、まぁなんとかなるだろう。

2 First Steps

さっそくプリミティブな OS を作るところから始まる。

2.1 Tools

環境構築とブートに仕組みについて。x86 用の仮想マシンとして Bochs を使うらしい。

2.2 Booting

BIOS が起動してブートローダが起動する。そしてブートローダが OS のプログラムをロードすることで OS を起動する。ブートローダってちゃんと調べたことないから後で Wikipedia でも読もう。

en.wikipedia.org

2.3 Hello Cafebabe

最小の OS を実装する。手順通りに進めてゆく。まずはアセンブリを書いてコンパイルobject file を生成する。そして、実行可能ファイルを生成するために linker script を書いて ld コマンドを実行する。その結果 kernel.elf というファイルが生成される。これが OS カーネルの実行可能ファイルである。

次に、ブートローダである GRUB を入手する。記載されている GitHub 上の URL が誤っている。正しくは ここ にある。OS の実行可能ファイルとブートローダを格納して ISO イメージを作成する。その結果 os.iso というファイルが作成される。

Bochs を使って作成した OS をエミュレータで実行する。ここで実行が上手くいかなかった。どうやら、実行画面を描画するための display library として指定している sdl のインストールが上手くいっていない。準備段階で実行した apt-get install bochs-sdl が上手く機能しないらしい。この問題は以下の issue でも報告されていた。

github.com

issue にあるように、この問題は display library として sdl の代わりに X Window System を使うようにすると解決した。具体的には sudo apt-get install bochs-x を実行した後、以下のように Bocks の設定として x を指定した。

    megs:            32
    display_library: x
    (省略)
    cpu:             count=1, ips=1000000

そして、コマンド bochs -f bochsrc.txt -q を叩くとデバッガが起動する。プロンプトで c コマンドを実行するとエミュレータ画面に何かしらの遷移がある。画面に何が表示されているのかはよく分からない。q コマンドでエミュレータを終了する。実行ログを見ると cafebabe という値が eax レジスタに格納されているようなので上手くいってそう。

$ grep cafebabe bochslog.txt 
02120339208i[CPU0  ] | EAX=cafebabe  EBX=0002cd80  ECX=00000001  EDX=00000000

3 Getting to C

OS のコードを書くためにアセンブリではなく C 言語を使うための準備をする。

3.1 Setting Up a Stack

C プログラムを実行するためにはスタック領域が必要である。つまり、カーネルプログラムがメモリにロードされた後、カーネルがスタック領域を使って様々な OS の仕事をできるようにする必要がある。そのための領域を確保する。いい加減なやり方としてはメモリにランダムにアクセスして、その周辺をスタック領域として使うやり方である。これは使えるメモリの領域が不明だったり、すでに使われている領域と被ってしまう可能性があるので避けるべき。

そこで今回は、カーネル用に固定した領域を確保する方法を取る。具体的には、未初期化領域を表す bss segment に適当なサイズを確保し、そこをカーネルが使うスタック領域とする。掲載されているコードを loader.s に追加してみた。

3.2 Calling C Code From Assembly

アセンブリから C プログラムを呼び出す方法について。スタックに push してから call すればよい。この先、頻繁に用いるメモリレイアウトがあるらしく構造体を定義したほうがよいとのこと。そこで、以下のようなコードを書いて kmain.c とした。

struct example
{
    unsigned char config;   /* bit 0 - 7   */
    unsigned short address; /* bit 8 - 23  */
    unsigned char index;    /* bit 24 - 31 */
} __attribute__((packed));

void kmain() { }

3.3 Compiling C Code

普通の C プログラムをコンパイルするのとカーネルコンパイルするのでは前提が全然違うので、gcc のオプションを色々付けるとのこと。

3.4 Build Tools

掲載されている通りに Makefile ファイルを作った。すると make runコンパイルからエミュレータの起動まで一気にできるようになった。これは楽ちん。

所感

パート1はいったんここで終わり。トラブルシューティングなどの必要もあり、調べながら進めたのでここまでの内容はそれなりに分かった。わりと丁寧に書かれたチュートリアルだと思うが、Bochs の実行方法が書かれていなかったり、object file やレジスタに関する基礎知識は前提とされているように感じた。調べながら進めれば問題ない内容にはなっていると思うので大きな問題ではないが。ぼちぼち進めていこう。