kawasin73のブログ

技術記事とかいろんなことをかくブログです

55 日かけて OS を作った

整骨院は保険がきく。どうも、かわしんです。 突発的な不調の場合は整骨院は保険が適用され 3 割負担になります。慢性的なもの(肩こり)には適用されません。

さて、5 月 20 日に以下のように宣言して作り始めた OS が、昨日ようやくひと段落したのでまとめの記事を書くことにしました。

かの有名な「30日でできる! OS自作入門」(通称 : 30日OS本)をただ写経しただけなのですが、すごく勉強になりました。

この本は 30 日とうたっていますが、僕は 55 日かかりました。ただ忙しくて毎日はできなかっただけなので実働自体は 30 日なのですが、これも僕の実力です。

毎日 1 日分だけを進めて全て自分の手で打ち込み、サンプルコードからのコピペはしないという縛りで実装しました。もちろん、自分が書いたコードはコピペ可としました。そうしないと辛すぎるので。

だいたい 1 日分は 2 ~ 3 時間くらいかかりました。写経するだけですが、本の中身を読んで理解したり、写し間違えによるバグを直したりすると結構時間がかかります。

いつも、学校から帰ってきて仕事を始める前の息抜きに OS を作っていましたが、結局 OS を作るだけで1日が終わってしまうことが多々ありました。そのくらい重いです。

できたもの

出来上がった OS はこれです。

f:id:kawasin73:20190714140358p:plain

僕のプロフィール JPEG 画像を表示して、インベーダーゲームを表示してるだけですが、この裏で 1 文字 1 文字打ち込んだコードが動いています。

ソースコードはこれです。自分の名前をつけようと kos (kawasinOS) と名付けましたが、めんどくさくなったので途中からは本に書いてある通り Haribote OS の名前で実装しています。

リポジトリ名が kos-sample となっているのはその名残です。-sample とつけているあたり、またもう1つ OS を作る野望が見え隠れしますが疲れてしまったので次があるかは未定です。

github.com

感想

知識だけでない実践

OS の仕組み自体は大学の授業で習っていたので、ページングやスケジューリングアルゴリズム、プロセスの仕組み、ユーザランドカーネルランド、CPUの保護、ファイルシステムなどの理論は知っていました。しかし、それを具体的にどのように実装するかは曖昧なままでした。

多分、すでに OS のコードベースがあってそれに機能追加やパフォーマンス改善をする OS 開発をすることはできたと思いますが、何もない状態から 0 ベースで作り上げることは無理だったと思います。

この 30 日 OS 本は、本当に 0 から作り上げていく本なので、どのようにコンピュータが動いているのか全てを理解したかった僕にはうってつけでした。

コンピュータと C 言語の本質

普段ソフトウェアを作るときのインターフェースは システムコール であり OS を相手取って操作します。また、システムコールの上に構築されたライブラリの関数呼び出しも利用します。

一方で、OS を作るときのインターフェースは CPU命令 であり、CPU を相手取って操作します。PIC (Programable Interrupt Controller) や PIT (Programable Interval Timer) やマウス・キーボードなどのデバイスなど、全て CPU 経由で操作します。CPU にはそのための命令が全て用意されており、その振る舞いも全て仕様が決まっています。

ソフトウェア開発との違いはインターフェイスがすでに C 言語の関数の形で用意されている点です(もちろんその関数自体はアセンブリで作られていますが)。OS 開発で必要なインターフェイスである CPU 命令は C 言語のインターフェイスを持っていないため、自分でアセンブリを書いて C 言語で扱える関数を用意 します。この辺りは、正月に作った C コンパイラ 1 の知識が役に立ちました。

そして、自分でアセンブリによって作った関数を C 言語から呼び出して OS を構築していきます。ここで C 言語は何も機能を持っておらず、所詮メモリを操作して関数を呼び出しているだけ のことしかできないのだと理解しました。なんでも自由にできる万能な言語だと思っていた C 言語が、メモリ操作関数呼び出し だけしかできない低機能野郎だったと知れたのは意外な発見でした。

この辺りの C 言語の本質、CPU と OS との関わりなどは、大学の授業では知ることができませんがこの 30 日 OS 本を読むことで理解できました。

プログラミングの方法

また、30 日 OS 本では 基本的なプログラミングの方法 が学べます。この方法は DMM でインターンしてる時 2 にメンターさんに教えてもらった方法なのですが、この本でも全く同じ方法でプログラミングされておりプログラミングの基本中の基本とはこのことなのかと思いました。

普段フレームワークありきでアプリケーションなどを作っている場合にはなんとなく作ってもなんとなく動くものができてしまいますが、正しく動くメンテナブルなソフトウェア を 0 から作る場合にはこの基本は重要です。といっても大したことではないのですが。

まず、main 関数に全ての処理を愚直に書き連ね ます(30 日 OS 本では、HariMain ですが)。関数定義などはしません。main 関数一本勝負です(あまりに冗長なところは関数化してもいいですが)。そして、実際に動かしてみて正しく動くことを確認します。実際に 30 日 OS 本では、数百行の HariMain 関数まで育てています。

次に、main 関数の中身を眺めて共通化できそうなところを探して、関数に抽出 します。その時 1 つの関数には 1 つの役割のみを担わせるように注意します。それによって main 関数が読みやすくなります。また、同じ分野の処理は同じファイルにまとめるなどの分割を行うとよりわかりやすくなります。

ここで重要なのはある関数を変更した時に別の分野の関数に影響しないことです。これにより、手戻りが無くなります。正しく役割を分割して関数に抽出できていれば別ファイルの関数群を気にする事なく機能追加を行うことができます。

関数を抽出する時には、カプセル化 して実装の詳細を隠蔽することが重要です。これはよく言われていることですが、Haribote OS のコードを見るとうまく隠蔽されているように思います。一部(ビジュアル系など)は構造体のメンバにアクセスしてしまっているので完全ではないですが、程よく隠蔽されている感じでしょうか。

関数は引数に、コンテキストとなる構造体(コンソール系の処理なら struct CONSOLE など)を 1 つと新しい状態を表すデータを複数受け取ります。コンテキストとなる構造体の中身の操作は関数に隠蔽されます。

また、重要なこととして 最適化は一番最後 に行うということがあります。まず最初は 効率が悪くてもいいのでシンプルで正しく動くコード を実直に実装します。実際、30 日 OS 本では 平気で線形探索 をバンバンします。データ構造とアルゴリズムのへったくれもないです。関数に抽出するなどした後、実際に動かして遅い部分にデータ構造とアルゴリズムを適用して最適化していきます。

ただ写経しているだけでもこれだけのプログラミングの基本を学ぶことができるので、30 日 OS 本はいい本です。

TIPS

あとは、工夫したことや、新しく 30 日 OS 本をやる人へ向けたアドバイスなどを残しておきます。

macOS での開発する時の参考

30 日 OS 本は Windows での開発を前提とされています。完成品となるサンプルコードが本に CD で添付されており、github などでも公開されています。macOS で動かしたリポジトリが以下に公開されており、各チャプターごとのスナップショットがディレクトリわけしてあって、写経に便利なので参考にしていました。

github.com

確か、何かのデータが置かれているアドレスの値が本に書かれている値ではうまく動かなくて、このリポジトリの値を参考にしたらうまくいったということがあったような気がします。どの値だったかは忘れました。

また、専用のコンパイラgccベースらしい)やツールなどを揃える必要がありますが、Windows 向けのツールを macOS 向けに移植されたものを利用しているようです。

残念ながらその公開リンクはリンク切れしていましたが、こちらのリポジトリで公開されていました。GitHub - HariboteOS/z_tools_osx

そのソースコードがこちらのリポジトリで公開されていたので、それをコンパイルしてツール群を利用することにしました。GitHub - HariboteOS/tolsrc: Tool set for developing Haribote OS .

しかし、Xcode 10 からは 32 bit 環境向けのコンパイルは Deprecated になっていたため、解決策を調査して報告してプルリクエストを出し、マージしてもらいました。

github.com

30 日 OS 本の最初には 起動イメージバイナリをバイナリエディタでベタ書き するという洗礼があります。これを手打ちするわけですが、バイナリエディタには iHex を利用しました。

CPU が速すぎる

この本が出版された 2006 年当時の CPU で QEMU を動かすことを想定しているため、本の各所で「動かすとカクカクする」といった表現がされています。本の中ではそれを解決するために最適化を行う章が組み込まれています。

しかし、現代の CPU は当時に比べるととても速くなり、また QEMU のソフトウェアとしての品質も上がっているためだと思うのですが、ぶっちゃけカクカクしない という場面がところどころあり、時代の進歩を感じました。

一方で、CPU が速すぎるためにうまくいかなかった ところがあります。途中のタイマーのパフォーマンス計測のあたりだったと思うのですが、for ループの中で何度も io_cli() (割り込み禁止)と io_sti() (割り込み許可)を繰り返し呼び出す部分があります。

この処理の間に別の処理(グラフィックスのメモリ書き込みなど)を行なっている分には問題はないのですが、この 2 つの関数を交互に高速に呼び出すと QEMU が猛烈に遅くなりキーボードやマウスの割り込みをほとんど受け付けなくなります。CPU が速すぎてこの2つの関数呼び出しの数が多くなりすぎたため、逆に QEMU の動作が遅くなってしまうのが意外で面白かったです。

首をいわした

写経は肉体労働です。本を机に広げてその内容をディスプレイを見ながら打ち込んで行くのですが、僕の作業環境はこんな感じでした。

f:id:kawasin73:20190714171935j:plain

本の中身を首を下に向けて読んで、また首をあげてディスプレイをみるということを繰り返していたので、おかげさまで首をいわしました。プログラマの職業病です。

最初から、左の画面にサンプルコードを表示して、正面のディスプレイに写経することをすれば首の移動は左右だったので負担も少なかったのではないかと後悔しています。皆さんも気をつけてください。