自作OS#0 UEFI編(1)
オタクもすなる自作OSといふものを、我もしてみむとて、するなり。
情報
- 目標 : 実機で動く
- 目的 : 完全にお遊び
- 技術 : ほとんどをD言語で,1から実装してみる
- ターゲット : 実機で動かしたいので,x86_64ベースで作っていく.
- 開発 : GitHub(yamader/os)
UEFIアプリケーション
1口にOSといっても単語の当たり判定がデカいので,とりあえずカーネルを読み込み・実行するためのUEFIアプリケーションを作成するところからやっていきたいと思う.
UEFIはいろんなマシンで共通する規格のようなもので,その規格書はuefi.orgにて無料で公開されている. いくつかのCPUのアーキテクチャに対応しているようだが,ターゲットがx86_64なので今回はx86_64を選ぶ.
開発環境
Gentooを使っているので,以下のようにした.そのうちDockerなりAnsibleなりのスクリプトを用意しようと思う.
# emerge -N crossdev
# crossdev --target x86_64-w64-mingw32
何故MinGWを入れているのかは,あとで説明する.
ビルド
D言語のコンパイラは,以下の理由からldc2(以下,ldc)を採用している.
- なんか移植性高そう
- クロスコンパイルの情報がすぐ見つかった
- LLVMロゴかっこいい
また,ややこしいことを避けるために,UEFIアプリケーション開発ではldcに-betterC
フラグを渡している. betterCでも十分に高機能だ.
UEFIアプリケーションはPE形式(Wind○ws用)のバイナリで作成する必要があるので,Linuxで開発を進めていくにあたり,それを吐くコンパイラ・リンカを用意する必要がある. 筆者は無難にMinGW環境を選択した.
ここで,ldcがCOFF形式のオブジェクトファイルを吐く必要があるのだが,これは-mtriple=x86_64-w64-mingw32
フラグ(clangの--target
に対応するようなもの)を渡してやるとよい.
ローダーのmakefileではコンパイラとリンカを独立したコマンドで呼び出しているが,これに特に深い意味はない.
コンパイル
さっき軽く説明した通りのオプション.最適化フラグ(-O2
とか)を立ててみてもいいかも?
% ldc2 -c -mtriple=x86_64-w64-mingw32 -betterC --of=hoge.obj hoge.d
リンク
MinGWに入ってるldを使う.--subsystem 10
で,これがUEFIアプリケーションであることを指定する.
% x86_64-w64-mingw32-ld -static --subsystem 10 -e piyo hoge.obj fuga.obj
ライブラリ
巷にはUefi.h
のD言語への移植がいくつかあるが,どうせなので自分で書いていく.
以下に実装する必要がありそうなあれこれを列挙する.
struct EFI_SYSTEM_TABLE
: エントリーポイントに渡される,全体的に使われるデータ構造enum EFI_STATUS
: Boot ServicesとかRuntime Servicesとかの戻り値として使われたりするstruct EFI_BOOT_SERVICES
: Boot Servicesenum EFI_ALLOCATE_TYPE
: 使うenum EFI_MEMORY_TYPE
: 使うstruct EFI_MEMORY_DESCRIPTOR
: 使う
struct EFI_RUNTIME_SERVICES
: Runtime Servicesstruct EFI_DEVICE_PATH_PROTOCOL
: Device Path Protocolstruct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
: コンソール出力に使う
関数形式マクロの対応
D言語はよさげに関数をインラインに展開してくれるので,文字列レベルの操作を行う必要はない.
EFI_STATUS(A)
:A
がエラー(MSBが1)かどうかを判別する
普通に関数を定義する.
bool EFI_ERROR(EFI_STATUS status) {
return cast(INTN)(status) < 0;
}
実機で動かす
- 何らかのメディアをFAT32でフォーマットし,fdiskやpartedでパーティションタイプの変更(espフラグを立てる)をしておく
- 次に,それをマウントしてコンパイル・リンクしたバイナリをメディアの
/EFI/BOOT/BOOTX64.EFI
に置く.
あとは人柱となるマシンにメディアを差し替えて起動させるだけ.
サンプル
import efi;
extern(C):
__gshared {
EFI_SYSTEM_TABLE* gST;
EFI_BOOT_SERVICES* gBS;
EFI_RUNTIME_SERVICES* gRT;
}
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) {
EFI_STATUS status;
gST = SystemTable;
gBS = gST.BootServices;
gRT = gST.RuntimeServices;
status = gST.ConOut.OutputString(gST.ConOut, cast(wchar*)"hello, world (from Dlang)"w);
if(EFI_ERROR(status)) {
return status;
}
while(true) asm { hlt; }
return EFI_STATUS.EFI_SUCCESS;
}
おお〜
まとめ
- UEFIアプリケーションのビルドができるようになった
次回へ続く……