オタクもすなる自作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形式のフォーマットで作成する必要があるので,Linuxで開発を進めていくにあたり,それを扱うツールチェーンを用意する必要がある。 今回は無難にMinGWを選択したが,LLVMでも良いと思われる。
今回のMakefileではコンパイラとリンカを別々で呼び出している。
ldcコマンドでリンクまで済ませることもできるが,オプションがどうも長ったらしくなってしまうので,こうしている。
どちらにせよ,ldcはCOFF形式のオブジェクトファイルを吐く必要があるのだが,これは--mtriple=x86_64-w64-mingw32
フラグ(clangの--target=...
に対応するようなもの)を渡してやると対応できる。
コンパイル・リンク
さっき軽く説明した通りのオプション。最適化フラグ(-O2
とか)を立ててみてもいいかも?
% ldc2 -betterC --mtriple=x86_64-w64-mingw32 --of=hoge.obj -c hoge.d
リンクには,MinGW(のbinutils)に入ってるldを使う。--subsystem 10
で,これがUEFIアプリケーションであることを指定する。
% x86_64-w64-mingw32-ld -static --subsystem 10 -e piyo hoge.obj fuga.obj
ライブラリ
巷にはefi.h
やUefi.h
のD言語への移植がいくつかあるが,どうせなので自分で書いていく。
以下に実装する必要がありそうなあれこれを列挙する。
enum EFI_STATUS
struct EFI_SYSTEM_TABLE
struct EFI_BOOT_SERVICES
enum EFI_ALLOCATE_TYPE
enum EFI_MEMORY_TYPE
struct EFI_MEMORY_DESCRIPTOR
struct EFI_RUNTIME_SERVICES
struct EFI_DEVICE_PATH_PROTOCOL
struct EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
関数形式マクロの対応
D言語はよさげに関数をインラインに展開してくれるので,文字列レベルの操作を行う必要はない。
EFI_STATUS(A)
:A
がエラー(MSBが1)かどうかを判別する
例えば上記のようなマクロは,D言語では関数で定義する。
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アプリケーションのビルドができるようになった
次回へ続く……