この記事はAdvent Calender「ゆる募2025年」の12日目の記事です。
男の夢
世の男の子が夢見ることといえば自作OSですよね。
しかし、現代のアーキテクチャは複雑化が進みいきなり手を付けるのは少しばかり大変です。
そこで、私がOSを作り始めるために調べたことをまとめてみようと思います。
ブートまでの流れ
まず、自分で作ったカーネルが実際にメモリ上に読み込まれ、実行されるまでの流れです。
ざっくりと解説していきます。
- ファームウェアの実行
UEFIやBIOSといったマザーボードメーカーが事前に用意したコードが実行されます。 - 自作のプログラムの起動
決まったパスに置かれた決められた形式のプログラムが起動されます。
本当にこれだけなので、自分の動かしたい環境がUEFIかBIOSかを調べて、
適切なファイルを生成すれば起動できます。
UEFIであれば下のサイトが非常に役に立ちます。
便利なブートローダー
ブートするだけならこれだけなのですが、
UEFIかBIOSかなどによって用意するプログラムも変わったりするため少し大変です。
そのため特に理由がない場合ブートローダーを使用して起動までの流れを任せてしまうのがいいでしょう。
自作してもいいのですが、どうせブートローダ部分ではOS部分のオリジナリティーには特に関与できないので、
作りたいOSのシステムがあるのならなおさら外部のブートローダーがいいです。
GRUB(GRand Unified Bootloader)
最も便利なブートローダでしょう。
MULTIBOOTプロトコルとLinuxBootプロトコルが選べます。
個人的にLinuxBootプロトコルで64bitブートをお勧めします。
(理由は後のx86_64アーキテクチャの解説で分かります)
現代OSの機能
OSといえばタスクのスケジューリング機能などにフォーカスを当てられることが多いですが、
現代のOSにはユーザーへの応答速度などのためにいくつかの面白い機能が存在します。
そんな機能たちを少し紹介しようと思います。
オンデマンドページング
64bit-CPUは基本的にメモリを物理アドレスではなく、仮想アドレスで扱います。
つまり、実際のアドレスとは異なるアドレスを振り分けるということです。
これにより、複数のプログラムを並列的にメモリの先頭にあるかのように振舞わせることができるわけです。
これは仮想アドレスを登録してるテーブルをプログラムごとに用意して、切り替えて使っているのですが、
テーブルのすべての要素に必ずアドレスが登録されているわけではなく、
もし登録されていない要素を使おうとするとエラーが発生します。
これを利用して実装しているのがオンデマンドページングです。
例えば、ファイルをメモリ上に展開するときを考えましょう。
この時、普通に考えればファイルのサイズに比例した時間が展開にかかります。
しかし、これでは一部分しか操作しない場合や、複数のファイルを先に展開して、
順番に操作する場合などにもったいないといえます。
だって読み込むのはゆっくり読み込んだり、一部分だけで十分だったということですから。
そこで、ファイルを展開するのを後回しにして、
アドレスを割り当てていない仮想アドレスをプログラムに渡します。
そうすると、プログラムがいざ操作しようとしてアドレスにアクセスするとエラーになります。
だって、その仮想アドレスには物理アドレスが割り当てられていないためです。
ここで、このエラーをハンドリングして、アクセスしようとしたアドレスに対応するファイルの一部分を
読み込み、エラーが発生した箇所に戻ります。
このようにすると、結果的にプログラムはファイルにアクセスできますし、
ファイルも使う分しか読み込まずに済みます。
リアルタイム制は損なわれますが、アプリを開くたびに長いロードを待たなくてよくなるわけです。
コピーオンライト
ほぼオンデマンドページングと一緒で、Linuxのforkのような場面で、
親プロセスと同じメモリ空間を使用して子プロセスを実行し、
メモリの改変(変数の代入など)を行おうとした場合にその部分だけコピーして、
子プロセス専用のメモリとして新たに作成します。
これにより子プロセスのためにコピーするメモリ空間を最小限にしています。
x86_64アーキテクチャ
結局現在主流のアーキテクチャはこれです。
このアーキテクチャのCPUはIntelとAMDが有名です。
ラズパイなんかはAArch64とかですが今回は解説しません。
解説といってもあまりにも要素が多いので、
OSを起動するまでに必要な話をざっくり解説します。
x86_64アーキテクチャは後方互換性を非常に大事にしているアーキテクチャで、
起源は16bit-CPU時代まで遡ります。
はい、これが大問題です。なんと世に出回るx86_64のCPUは16bitモードから始まります。
たとえ10万のハイエンド64bit-CPUだろうと最初は16bitです。
これを16bit->32bit->64bitの順番で起動しないといけないので、マジで大変です。
でも、LinuxBootプロトコルとかUEFIアプリとかには64bitから起動する方法があるので、
LinuxBootプロトコルの使用を強くお勧めします。
とはいえ、自分で64bitモードに入りたい人のために、流れを書いておきます。
1.メモリ上に仮想アドレスのテーブルを作る(1024Byteアライメント)
2.CR3にテーブルのアドレスを登録
3.LME,CR4を64bitモード用に設定
4.CR0を64bit実行モードにする
5.LongJump
といった流れです。
少し雑になってしまいましたが下のサイトを参考にすると分かりやすいと思います。
Paging
ページング入門
おわりに
最後のほうはだいぶ雑になってしまいましたが、
なんとなくOS事情が分かっていただけたら嬉しいです
リンクを貼っているサイトは自作OSに役立つ情報がたくさんあるので、
ぜひ自作OSをやりたくなった際にはご活用ください