【ゼロからのOS自作入門】読書メモ
"OS自作 a.k.a みかん本" へ向けて読んできた本
理解するためにチェックしたり調べたメモ。
三年前、まだプログラミングを始めて1年も経たない頃、自分が何が分らないかすら分からなかったゆえに「入門」の文字に惹かれて買ったものの呆気なく弾き返された。
それから解説記事などを参考にぼちぼちと少しずつ読んできた主な本たち。
数日前に「UNIX/Linuxプログラミング教室」の二周目が終わったところで、次何を学ぼうかな?と思案していていると「OS自作するために学んでたんだ!」と思い出した。目標ってついつい忘れてしまう。
ということで三年前に木っ端微塵にされた本書をまずは「読む」。
そして50ページほどまで進んだ現在……
「読める!読めるぞ!!!」
とりあえず全体を俯瞰するためにまずは「読む」だけ。
700ページ……厚い、重い、寝ながら読めない。
メモ
第2章 EDK Ⅱ入門とメモリマップ
2.1 EDK Ⅱ入門
わからない。Wikiを読んでも「EDK II (formerly Tiano) is the reference implementation of UEFI by Intel」。「リファレンス実装」という概念があるらしい。完成品まではいかないものの、こういうものを作りたいという仕様を最低限クリアしてる及第点のものだろうか。サンプルとかテスト用レベルのものを指すことが多いようだ。
UEFIとは、BIOSの後継のファームウェア(あるいはそのインターフェイス仕様)。
ファームウェアとは、パソコンのOSが起動する前の制御や、ハードディスクをはじめとした機器の制御を行うソフトウェア(狭義であるが一般的にイメージされるファームウェア)。
つまりEDK ⅡはファームウェアであるUEFIを作るためSDKということか?
2.2 EDK Ⅱでハローワールド
どうやらこの本の最初のゴールは「このアプリをどんどん拡張して、USBメモリからメインメモリにOSを読み込むのに使うブートローダに進化」させることのようだ。始まりの始まりは
パッケージ宣言ファイル
パッケージ記述ファイル
モジュール情報ファイル
そしてソースコード
パッケージ宣言ファイル、パッケージ記述ファイル、似たような名前だが何が違うんだろう?本の最後の付録に説明が載っている。
宣言ファイルはパッケージの名前を決めるファイル。見た感じ重要な情報はなさそう(開発する側にとっては)。
記述ファイルは重要な情報が多い。アーキテクチャ、ビルドされたファイルの出力先、ライブラリ、パッケージを構成するコンポーネントなどを書く。
モジュール情報ファイルは、コンポーネント名やエントリポイント、ソースコードなどなど。
エントリポイント、ここがポイント。
コンポーネントはひとまとまりのプログラム。パッケージはそれを集めたもの。
2.3 メインメモリ
2.4 メモリマップ
メモリが何に使われているかを示す地図。
PhysicalStart: 番地
Type: そこにある建物(実行コードやデータなど)
NumberOfPages: 建物の大きさ
OSがちゃんと動くためにはメインメモリの情報を正確に得られなければならない。
なのでOS作りを始める前にメモリマップを取得するプログラムを作る必要がある。
まずはメモリマップを取得し保存するのが最初の目標。
2.5 メモリマップの取得(osbook_day02b)
UEFIの機能は主に二つ、OSを起動するブートサービス、ランタイムサービス。それぞれのグローバル変数は
gBS = ブートサービス
gRT = ランタイムサービス(この本では使わない)
gBS->GetMemoryMap(
// 引数はメモリマップの大きさや
// メモリマップのが書き込まれる先へのポインタなど
)
2.6 メモリマップのファイルへの保存
struct MemoryMap memmap = { ... }
GetMemoryMap(&memmap);
Open(..., &memmap_file, ...);
SaveMemoryMap(&memmap, memmap_file); // CSVでファイルに書き出し
2.7 メモリマップの確認
Main.cの「Print(L"Hello, Mikan World!\n");」を適当に変更(Mikanを推しの名前に)するとちゃんとビルドされてるのを確認できて良き哉。
2.8 ポインタ入門(1):アドレスとポインタ
2.9 ポインタとアロー演算子
struct MemoryMap {
UINTN buffer_size;
VOID* buffer;
UINTN map_size;
UINTN map_key;
UINTN descriptor_size;
UINT32 descriptor_version;
};
構造体は宣言順に並んだメモリ構造となる。構造体の銭湯からメンバ銭湯までの距離(バイト数)をオフセットと呼ぶ。
buffer_sizeのオフセットは0バイト
bufferのオフセットは8バイト
struct MemoryMap m;
struct MemoryMap* pm = &m; // pmはMemoryMap構造体mを指すポインタとなる
bufferを指す書き方
m.buffer
pm->buffer
(*pm).buffer
コラム2.2 ポインタのポインタ
ポインタの重要な使いどころは、関数においてポインタを引数にすることでその値を書き換えること。
// ポインタpがさす値を42に変更する関数
void f(int* p){
*p =42;
}
int g() {
int x = 1;
int* p = &x; // pはxを指すポインタ
f(p); // f()は引数が指し示す値を変更する関数。つまりpが指すxの値が変更される
return x;
}
第3章 画面表示の練習とブートローダ
3.1 QEMUモニタ
クェムってなんなんだべ?と思ったら、Q-EMU、エミュレータ。Virtual Boxのお友達みたいなもんかな?
メモリ破壊とは、プログラムが割り当てられたメモリ領域を上書きすること。バッファ・オーバーフローとかかな。
メモリダンプとは、指定したアドレス付近の値を出力する事。
3.2 レジスタ
汎用レジスタとは、演算に使うレジスタ。値を記憶する。やっていることはメインメモリと似ているが、大きさは一億分の1だが、スピードは200倍。
add rax, rbx // rax += rbx;
; オペコード オペランド1, オペランド2
x86-64の加算命令の例。左が書き込み先、右が読み込み元。つまりRAXにRBXを加える。
特殊レジスタとは、CPUの設定やタイマなどCPUの内臓機能を制御するレジスタ。
RIP: CPUが次に実行する命令のメモリアドレスを保持するレジスタ。
RFLAGS: 命令の実行結果によって変化するフラグを集めたレジスタ。
CR0: CPUの重要な設定を集めたレジスタ。
などなど。
3.3 初めてのカーネル(osbook_day03a)
ブートローダとOS
ブートローダ:UEFIアプリ
OS(カーネル):ELFバイナリ
ブートローダからカーネルを呼び出す。
extern "C" void KernelMain() {
while (1) __asm__("hlt");
}
永久ループ。
asm インラインアセンブラ。
hlt命令(halt)、CPUを停止させる。割り込みがあると動作が再開する。
コンパイルとリンク
ホスト環境:OSの上で動くプログラムのための環境
フリースタンディング環境:OSがない環境。当然こちらの環境。
ld.lldは、LLVM(Low-Level Virtual Machine)のリンカー。オブジェクトファイルから実行可能ファイルを作る。
メイン関数でカーネルファイルを読み込む
カーネル起動前にブートサービスを停止させる
メイン関数でカーネルを起動する
エントリポイント:KernelMain()の実体があるアドレス。