「ゼロからのOS自作入門」の副読本的記事
最初に
「ゼロからのOS自作入門」を実践するための、環境構築方法及びコマンドリストと実行結果のメモです。最短で実行したい方、うまく動かすことができない人用の記事となります。
書籍に関する感想は、以下ブログ記事に書いているので、書籍自体を買おうか迷っている人はこちらを参考にしてみてください。
本記事は「ゼロからのOS自作入門」を読んで、個人的にまとめたものとなります。内容に関して、もし問題や誤りがあった場合の文責は私にありますので、この記事に関しての疑問は私に問い合わせください。もちろん書籍自体の質問は、書籍のサポートに連絡ください。
この記事を読むと、書籍を読まなくてもOSを動かすことはできます。ただ、書籍を読みながら自分で理解したり改造したりしながら動かさないと何も身につかないと思うので、興味ある人は書籍を買いましょう。価格の何倍もの価値がある良書だと思います。
0章 付録A 初期セットアップ
ネイティブのLinux(Ubuntu)にセットアップする方法と、Mac(Intel)+Dockerを使う方法を記載します。
ネイティブのMacにインストールする方法だと、自分の環境ではカーネルの描画がどうしてもうまくいきませんでした。以下のスクラップに奮闘記を残してあるので、チャレンジしたい方は参考にしてみてください(うまくいかない原因わかった人いたら教えてください)。
ネイティブのLinuxにセットアップする方法
マシンはX86互換の適当なものを選びましょう。といっても何を選んだら良いか分からない人は、SEEEDさんのODYSSEY X86J4105あたりが良いかと思います。今回の記事もネイティブのLinux(Ubuntu)に関してはこのマシンで検証しています。
Linuxのセットアップに関しては、以下記事を参照ください。
全部セットアップする必要はありませんが、エディタは何かしらインストールしておきましょう。初心者であればVS Codeがおすすめです。VS Codeに関しては、以下Zennの本参照ください。
あとは、書籍の以下リポジトリを参考にセットアップしましょう。
「MikanOS のソースコードの入手」のところまでコマンドをREADMEの通りに打ちましょう。「MikanOS のソースコードの入手」以降に関しては、以下コマンドを実行します。
$ sudo apt install -y python3-distutils
$ mkdir ~/workspace && cd ~/workspace
$ git clone https://github.com/uchan-nos/mikanos.git
$ OS_DIR=~/workspace/mikanos
$ cd ~/edk2
$ ln -s $HOME/workspace/mikanos/MikanLoaderPkg ./
$ source edksetup.sh
Conf/target.txt
の編集はREADMEの通りです。VS Codeで編集する場合は、以下コマンド実行しましょう。
$ cd ~/edk2 && code .
編集が終わったら、ネイティブなLinuxでのセットアップは完了です。1章に進みましょう。
Mac+Dockerでセットアップする方法
MacはIntel Macを使います。Apple Silicon(M1) Macは、自分の環境ではDockerビルドでこけました。セットアップ方法は、以下記事を参考にしました。
上記記事のVS Code Remote Container(devcontainer)を使う方法が最高です。以下の図(少し分かりづらいですが)のようにVS CodeでDockerのコンテナ内のファイルの編集とコンテナ内でのターミナル操作ができます。XQuartzにXを飛ばすことでコンテナ内のGUIアプリも動かすことができます。
セットアップ方法は、ほとんど参考にした記事そのままなのですが、少しだけ自分が分かってなかったところを補足します。まず、事前準備として以下をセットアップしておく必要があります。
- Docker
- VS Code
- XQuartz
Docker
Dockerに関しては以下記事参考にしてインストールして、使い方を確認しておいてください。
VS Code
VS Codeに関しては、以下参考にしてインストールしておいてください。
拡張機能のRemote - Containers
もインストールしておいてください。
XQuartz
XQuartzもインストールします。Homebrewでインストールする場合は以下です。
$ brew install xquartz
Homebrewがセットアップされてない人は、最初に以下コマンドを打ち込んでHomebrewをセットアップしてからbrewコマンドを実行ください。
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
XQuartzを起動して、設定のセキュリティで以下のように設定します。
ターミナルで以下実行しておきます。以下コマンドは、XQartzを起動するたびに実行する必要があります。
$ xhost + 127.0.0.1
VS Code devcontainerセットアップ
上記のDocker, VS Code, XQuartzの設定ができたら、全部を起動した状態で以下のREADMEを参考にセットアップを実施します。
具体的には、ターミナルで以下コマンドを実行すればOKです。
$ git clone https://github.com/karaage0703/mikanos-devcontainer
$ cd mikanos-devcontainer
$ code .
これでVS Codeが立ち上がるので、コマンドパレットでRemote-Containers: Open Folder in Container...
を選択します。しばらく待つと、以下のようなダイアログが表示されるのでmikanos-devcontainer
が選ばれていることを確認してOKをクリックします。
その後、Dockerイメージが立ち上がると以下のような画面になります。
右下のターミナルにvscode ➜ /workspaces/mikanos-devcontainer (master) $
と書かれていますが、これはDocker内のターミナルになります。
Mac+Dockerの場合は、以降はこのターミナル内でコマンドを実行していきます。すでに、Docker内では一通りセットアップできた状態になっているので、以下コマンドを実行すればセットアップは完了です。
$ cd /workspaces/mikanos-devcontainer/
$ git clone https://github.com/uchan-nos/mikanos.git
$ OS_DIR=/workspaces/mikanos-devcontainer/mikanos
$ cd ~/edk2
$ ln -s $HOME/workspace/mikanos/MikanLoaderPkg ./
$ source edksetup.sh
$ cp /workspaces/mikanos-devcontainer/target.txt Conf/target.txt
注意事項
ターミナルを開き直したら、最初に以下コマンドを実行する必要があります。
ネイティブなLinuxの場合
$ OS_DIR=~/workspace/mikanos
Mac+Dockerの場合
$ OS_DIR=/workspaces/mikanos-devcontainer/mikanos
$OS_DIR
をパスに読み替えてもらってもOKです。
1章 PCの仕組みとハローワールド
1.4 エミュレータでのやり方
書籍のバイナリファイルの場所に移動します。バイナリのコードは、mikanos-buildリポジトリのday01/bin/hello.efi
がそれです。
$ cd ~/osbook/day01/bin
本のコンセプトからすると、この内容は写経するのが良いと思いますが、初学者だとかなりハードル高いので、ここでは、まずお手本のデータ使って動くのを確認していきます。動いた後、バイナリエディタで、「Hello, World!」のところを好きな文字に変えたり、写経するが良いと思います。
最初は、イメージを作ってFATでフォーマットします。
$ qemu-img create -f raw disk.img 200M
$ mkfs.fat -n 'MIKAN OS' -s 2 -f 2 -R 32 -F 32 disk.img
イメージをマウントして、バイナリファイル(hello.efi
)を書き込んで、アンマウントします。
$ mkdir -p mnt
$ sudo mount -o loop disk.img mnt
$ sudo mkdir -p mnt/EFI/BOOT
$ sudo cp hello.efi mnt/EFI/BOOT/BOOTX64.EFI
$ sudo umount mnt
エミュレータとしてQEMUを使います。書籍の通りにUEFIブートするために、OVMFファイルをmikanos-buildに用意してあるものを使用します。OVMFファイルに関して知りたい人は、以下記事等参照ください。
最後に、バイナリファイルを書き込んだイメージをQEMUで読み込み起動します。
$ qemu-system-x86_64 -drive if=pflash,file=$HOME/osbook/devenv/OVMF_CODE.fd -drive if=pflash,file=$HOME/osbook/devenv/OVMF_VARS.fd -hda disk.img
以下のように表示されたらOKです。
1.9 C言語でハローワールド
バイナリでなく、C言語でハローワールドをしましょう。
C言語のソースを以下のコマンドでコンパイル&リンクします。
$ cd ~/osbook/day01/c/
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c hello.c
$ lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o
hello.efi
ができました。run_qemu.sh
というスクリプトを使って、先程のイメージを作って、書き込んでQEMUで動かすという流れを自動化します。
以下コマンド1行を実行するだけで、先程同様QEMUで「Hello, World」が表示できます。
$ ~/osbook/devenv/run_qemu.sh hello.efi
2章 EDK II入門とメモリマップ
2.2 EDK2でハローワールド
EDK2というUEFI用の開発キットを使用します。
ビルドします。
$ cd $OS_DIR
$ git checkout osbook_day02a
$ cd ~/edk2
$ build
QEMUで動かします。
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi
3章 画面表示の練習とブートローダ
3.3 初めてのカーネル
カーネルをコンパイルしてリンクします。
$ cd $OS_DIR
$ git checkout osbook_day03a
$ cd kernel
$ clang++ -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -std=c++17 -c main.cpp
$ ld.lld --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
main.cpp
の中身は以下たった3行の無限ループです。これがカーネルです。
extern "C" void KernelMain() {
while (1) __asm__("hlt");
}
ブートローダをビルドしてQEMUでカーネルを起動します。
$ cd ~/edk2
$ source edksetup.sh
$ build
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $OS_DIR/kernel/kernel.elf
info registers
を実行します。
(qemu) info registers
RAX=0000000000100000 RBX=000000003effef18 RCX=0000000000000000 RDX=0000000000000000
RSI=000000003feaca08 RDI=000000003feac9e0 RBP=000000003fea8850 RSP=000000003fea8850
R8 =000000003fea87c4 R9 =000000003fb7b48f R10=000000003fbcd018 R11=fffffffffffffffc
R12=000000003effe920 R13=000000003feac8d0 R14=000000003fea9110 R15=000000003e66273c
RIP=0000000000101011 RFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=1
次の実行される機械語命令の位置を示すRIP付近を2命令分逆アセンブルして表示します。
(qemu) x /2i 0x0000000000101011
0x00101011: eb fd jmp 0x101010
0x00101013: cc int3
自分のアドレスにジャンプしているので、コードの通り永久ループしていそうですね。
3.4 ブートローダからピクセルを描く
$ cd $OS_DIR
$ git checkout osbook_day03b
$ cd kernel
$ clang++ -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -std=c++17 -c main.cpp
$ ld.lld --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
$ cd ~/edk2
$ source edksetup.sh
$ build
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $OS_DIR/kernel/kernel.elf
3.5 カーネルからピクセルを描く
$ source ~/osbook/devenv/buildenv.sh
$ echo $CPPFLAGS
$ cd $OS_DIR
$ git checkout osbook_day03c
$ cd kernel
$ clang++ $CPPFLAGS -O2 --target=x86_64-elf -fno-exceptions -ffreestanding -c main.cpp
$ ld.lld $LDFLAGS --entry KernelMain -z norelro --image-base 0x100000 --static -o kernel.elf main.o
$ cd ~/edk2
$ source edksetup.sh
$ build
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $OS_DIR/kernel/kernel.elf
3.7 ポインタ入門(2):ポインタとアセンブリ言語
以下のコードをfoo.cpp
という名前で保存します。
#include <cstdint>
void foo() {
int i = 42;
int* p = &i;
int r1 = *p;
*p = 1;
int r2 = i;
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
int* q = reinterpret_cast<int*>(addr);
}
書籍では、「Clangで(最適化をかけずに)コンパイルする」と書いてあります。多分以下のようにして、アセンブリ言語にするということだと思います。
$ source ~/osbook/devenv/buildenv.sh
$ clang++ $CPPFLAGS -O0 --target=x86_64-elf -fno-exceptions -ffreestanding -c foo.cpp -S -masm=intel
$ less foo.s
最適化をかけないために-O0
オプションを指定し、書籍のIntel構文に合わせるために-masm=intel
というオプションを指定しています。
4章 ピクセル描画とmake入門
4.1 make入門
makeを試してみます。以下コマンドでビルドします。
$ cd $OS_DIR
$ git checkout osbook_day04a
$ cd kernel
$ make
make cleanもしてみましょう。
$ make clean
4.2 ピクセルを自在に描く
$ cd $OS_DIR
$ git checkout osbook_day04b
$ cd kernel
$ source ~/osbook/devenv/buildenv.sh
$ make
$ cd ~/edk2
$ source edksetup.sh
$ build
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $OS_DIR/kernel/kernel.elf
5章以降
同じ要領で実践できると思います。あとは書籍と合わせてお楽しみください。
もし、はまりやすいところや、面白いと思ったところがあれば追記していきます。
最終(最新)のMikanOSの動作
エミュレータでの動作確認
最終的なMikanOSを動かす方法です。ネイティブのLinuxのみで確認しています。
Mac+Dockerでは./uefi.hpp:7:10: fatal error: 'Uefi.h' file not found
とエラーになり動かせませんでした(原因調査中)。
$ cd $OS_DIR
$ git checkout master
$ cd kernel
$ source ~/osbook/devenv/buildenv.sh
$ make
$ cd ~/edk2
$ source edksetup.sh
$ build
$ ~/osbook/devenv/run_qemu.sh ~/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi $OS_DIR/kernel/kernel.elf
以下のように表示されます。かなりOSっぽいですね。
実機での動作確認
USBメモリを挿入します。ここで、挿入したディスクのデバイスファイル名を確認します。dmesg
やdf
コマンドを使って調べましょう。
自分がよくやるのはdf -h
をUSBメモリ挿入前、挿入後で比較し、増えているもので特定する方法です。以降は/dev/sdb
がUSBメモリの場合の説明です。
ディスクをアンマウント、FATでフォーマット、マウントしてファイルの書き込み、アンマントを実施します。
$ sudo umount /dev/sdb
$ sudo mkfs.fat /dev/sdb
$ sudo mkdir -p /mnt/usbmem
$ sudo mount /dev/sdb /mnt/usbmem
$ sudo mkdir -p /mnt/usbmem/EFI/BOOT
$ sudo cp ~/edk2/Build/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi /mnt/usbmem/EFI/BOOT/BOOTX64.EFI
$ sudo cp $OS_DIR/kernel/kernel.elf /mnt/usbmem/
$ sudo umount /dev/sdb
上記コマンドを実行すると、メモリの中のファイル構成は、書籍の通り以下のようになっているはずです。
kernel.elf
/EFI/BOOT/BOOTX64.EFI
この状態で、PCを再起動してUSBメモリからブートさせます。
USBからブートを選択します。
無事起動!と言いたいところですが、マウスが動きませんでした。自作OSの沼は深い…
トラブルシューティング
git checkoutできない
とりあえず以下実行してから、git checkoutしましょう。コマンドの意味は別途調べておきましょう。
$ git stash
寄り道: フルスクラッチで作る!UEFIベアメタルプログラミング
以下のブログで、他にも自作OSの情報があることを知りました。
以下、無料で公開されています。
素晴らしい情報です。こちらの書籍は、edk2を使わない分、より低レイヤーへの理解が深まりそうです。ただ、この記事の内容「ゼロからのOS自作入門」読んで事前知識なかったら、何から初めてよいか途方にくれていたと思います。「ゼロからのOS自作入門」読んで、開発方法などを身につけた後なら、ちょちょいのちょいですね。ありがとう「ゼロからのOS自作入門」。
まずは「hello world」をやってみましょう。
適当なディレクトリを作ります。
$ mkdir -p ~/work && cd ~/work
プログラムをダウンロードして、コンパイル&ビルドします。
$ curl -O https://raw.githubusercontent.com/cupnes/c92_uefi_bare_metal_programming_samples/master/sample1_1_hello_uefi/main.c
$ clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c main.c
$ lld-link /subsystem:efi_application /entry:efi_main /out:main.efi main.o
/entry:efi_main
とエントリーポイントを指定しないといけない点が注意です。これは「ゼロからのOS自作入門」では省略されていたポイントですね。「フルスクラッチで作る!UEFIベアメタルプログラミング」では、このあたり説明されているので、良い補完関係になっていると思います。
続いて、「ゼロからのOS自作入門」通りにイメージを作ります。
$ qemu-img create -f raw disk.img 200M
$ mkfs.fat -n 'MIKAN OS' -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 main.efi mnt/EFI/BOOT/BOOTX64.EFI
$ sudo umount mnt
QEMUで実行したら「Hello UEFI!」と表示されます。
$ qemu-system-x86_64 -drive if=pflash,file=$HOME/osbook/devenv/OVMF_CODE.fd -drive if=pflash,file=$HOME/osbook/devenv/OVMF_VARS.fd -hda disk.img
まとめ
「ゼロからのOS自作入門」が動かせない人・何をしているかよく分からない人の助けになるような記事を書いてみました。
自分自身もまだまだ理解できてないところがあるので、これはというポイントあれば、随時追記していきたいと思います。
参考リンク
公式サイト
動画
Macでの動作
記事
Discussion
失礼します。私も最近自作OS入門を始めて、第3章の画面表示の練習のところで、白が表示されるだけの状態になりました。解決策が分からず、放り出しそうになりかけたところ、スクラップを含めたこの記事を拝見させていただきました。よろしければ、解決策をご教示いただけないでしょうか。よろしくおねがいします。
@dtrackerhero さん
返信おそくなり申し訳ありません。
ネイティブのMac環境だと私は結局できなくて、ここに記載あるようにMac(Intel)+DockerやネイティブのLinuxを使いました。
失礼します.
一点気づいた点があったのでコメントします.
「VS Code devcontainerセットアップ」でのシンボリックリンクの設定で,
$ ln -s $HOME/workspace/mikanos/MikanLoaderPkg ./
となっていますが,mikanos-devcontainerを用いた場合「MikanLoaderPkg」が配置されるのは /workspaces/mikanos-devcontainer/mikanos/MikanLoaderPkg かと思います.
ですので,下記のように修正する必要があると思いました.
↓ 修正版
$ ln -s /workspaces/mikanos-devcontainer/mikanos/MikanLoaderPkg ./
上記修正をすることで「2.2 EDK Ⅱ でハローワールド」p51のbuildが通るようになりました.
もし私の見当違いでしたらすみません.
コメントありがとうございます。この記事では
workspace
で統一しているのでworkspace
で問題なかった記憶ですが、記事を書いてからだいぶ時間が経っているので自信がありません。また試す機会があったら見直したいと思います。
お返事頂きありがとうございます.
はい.また機会があればお試しください.
環境構築をするにあたり,こちらの記事大変参考になりました.
ありがとうございました.
コメント失礼します。
「VS Code devcontainerセットアップ」のコンテナ内でコマンドを実行していくところなのですが、cpコマンドの引数(コピー元、コピー先)が逆になっているかもしれません。
元:
cp /workspaces/mikanos-devcontainer/target.txt Conf/target.txt
修正:
cp Conf/target.txt /workspaces/mikanos-devcontainer/target.txt
私の環境だと、上記修正を加えたところ、ファイルのコピーが成功しました。
私の勘違いだったらすみません。