2022年1月くらいに、「ゼロからのOS自作入門」を(第6章くらいまで)写経したメモを残す。
前回Goでコンパイラーを書いた際にメモを残さず、振り返るときに後悔したので、雑でもいいのでメモすることにした。
第1章
UEFIアプリケーションの入ったdiskをUbuntu Dockerコンテナ内で作成し、それをローカルに立ち上げたQEMUで実行した。(UEFIアプリケーションの実装はまだ行っていない)
UEFIアプリケーションとは
UEFI BIOSはマザーボードに搭載されているプログラムであり、これがコンピューターを初期化した後に、接臆されているストレージを探索する。FTAファイルシステムの中に、実行可能ファイル(EFIファイル)を発見すると、BIOSはそのファイルをメインメモリへ読み出す。CPUはBIOSの実行を中断し、読み出したファイルの実行を行う。
このようにUEFI BIOSによって実行されるプログラムをUEFIアプリケーションと呼ぶ。この一つとして、OSをメインメモリに読み込み、起動させるプログラムであるブートローダを1-3章で書こうとしている。(と理解した)
disk imageを作成するコマンド
(dockerコンテナ内)disk imageを作成する。
(dockerコンテナ内)disk imageを作成する。P.30
qemu-img create -f raw disk.img 200M
# fat形式にフォーマット
mkfs.fat -n 'DISK WITH UEFI APP' -s 2 -f 2 -R 32 -F 32 disk.img
# 以降ディスクをマウントして、その中にファイルを作成し、アンマウントする
mkdir -p mnt
sudo mount -o loop disk.img mnt
sudo mkdir -p mnt/EFI/BOOT
sudo cp BOOTX64.EFI mnt/EFI/BOOT # from. https://github.com/uchan-nos/mikanos-build/blob/master/day01/bin/hello.efi
sudo umount mnt
run_qemu.sh を使用すれば、efiアプリケーションをQEMUで実行するまで1コマンドでできるが、自分はdisk作成する環境とQEMU実行する環境が違うので、使えないという認識。と思ったが、make_image.shのみならコンテナの中で使えそう。
第2章
EDK IIは、UEIF BIOSの開発キット。
2章ではEDK IIの使い方を練習し、3章ではEDK IIを使ってOSのブートローダを開発する。
本章で作ったのは、MikanLoaderという「メモリマップを取得してファイルに書き出す」プログラム。
UEFI = ブートサービス + ランタイムサービス
- ブートサービス = OSを起動するために必要な機能を提供。gBS
- ランタイムサービス = OS機能前後どちらでも使える機能を提供。gRT
今回使用する gBS->GetMemoryMap() は,関数呼び出し時点のメモリマップを取得する。
第3章
- カーネルをELFファイルで実装する。
- ブートローディング時にスクリーンの情報(FrameBuffer)を読み出し、それをカーネルのエントリポイントの関数実行時に引数として渡す。カーネルはれを使って画面に表示されるピクセルを書き換える。
- そのELFファイルを呼び出すように前章で作ったEFIアプリを改造する。
その他に説明されていたのは、以下。
[3.1] QEMUモニタを使用してデバッグをする方法
-
info registers
コマンドを使用して、レジスタに乗っかっている値を確認できる。 -
x /fmt addr
コマンドを使用して、メインメモリの中の指定したアドレスあたりに乗っかっている値を確認できる。例えば、x /2i 0x067ae4c4
を実行すると、アドレス0x067ae4c4付から2命令分を逆アセンブルして表示できる。
[3.2] レジスタの基本
- レジスタは汎用レジスタと特殊レジスタに分類される。
- 汎用レジスタは、下位バイトを小さな別のレジスタとしてアクセスできる。例えば以下。
- 特殊レジスタは、例えば以下。
- RIPは、IP(Instruction Pointer)を保持。
- RFALGSは、フラグを集めたレジスタ。キャリーフラグや、ゼロフラグなど。
- CROは、CPUの重要な設定を集めたレジスタ。例えば、ビット0(PE)=1なら、CPUは保護モードに遷移する。
第4章
Pixel Writerクラスを実装した。これは、グローバル変数としてkernelプログラムにロードされ、ピクセル箇所と色を指定したら、画面のその場所のピクセルの色を更新してくれるもの。
画面の設定に関しては、ブートローダーの起動時に、gop->Mode->FrameBuffer***
といった形で取得している。これをもとにFrameBufferConfigを初期化して、kernel/main.cppのエントリポイントに引数として渡している。
その他のトピックは以下。
- 4.1 Cプログラムのmakefileの書き方。
- 4.5 ブートローダの修正。kernel.elfのプログラムヘッダを読み、LOADセグメントsの大きさを調べそれをメモリ確保した上で、一旦ロードしたkernel.elfのLOADセグメントのみをそのメモリにコピーするようにした。
- c++のクラスの書き方
- 配置newについて
- vtableについて
第5章
- ASCII文字をレンダリングする#WriteAscii()の実装
- コンソールクラスの実装、printk()関数の実装
メモ
- printkについてのメモ https://github.com/kudojp/mikanOS/pull/9
第6章 マウス入力とPC
まず、カーソルのフォント(?)をmouse_cursor_shape
として、main.cppに定義した。
USBは、シリアルバス(1bit/信号線1本1回)の規格の一種。(なお周辺機器の接続はシリアルが主流)
PCには、USBホストコントローラという制御チップが搭載されている。OSはこのコントローラを制御することでUSB機器と接続する。
- コンピューター側のホストドライバがUSBホストコントローラーを制御する(?)
- USB側のターゲットドライバがUSB機器を制御する(?)
本書では、前者のホストドライバを以下の階層構造で実装する。
⏫ OS(のカーネル?)は最上位のレイヤーのAPIを使用しているという認識。
- ホストコントローラはホストコントローラの規格別に作る
- ホストコントローラの規格は主に、OHCI, UHCI, EHCI(for USB2.0), xHCI(for USB3.xk)
- 本書では、xHCI向けのホストコントローラのみを実装する
- バスドライバは、ホストコントローラドライバの機能を利用し、USB規格で定められたAPIを提供する
- APIとは例えば、GET_DESCRIPTORメッセージ、DEVICE定数、CONFIGURATION定数
- クラスドライバに対してこれらのAPIを提供する
- クラスドライバはUSBターゲットの種類ごとに作る
- HIDクラス(for キーボードやマウス)、オーディオクラス(for オーディオ機器)、マスストレージクラス(for 記憶装置)
- バスドライバが提供するUSB規格に従ったAPIを用いてクラスドライバが実装される
今回はこれらのホストドライバは
https://github.com/uchan-nos/mikanos/tree/master/kernel/usb
PCIに関してはこちらに詳しい
https://linuxjf.osdn.jp/JFdocs/The-Linux-Kernel-7.html