🍊

「ゼロからのOS自作入門」の副読本的記事

15 min read 2

最初に

ゼロからのOS自作入門」を実践するための、環境構築方法及びコマンドリストと実行結果のメモです。最短で実行したい方、うまく動かすことができない人用の記事となります。

書籍に関する感想は、以下ブログ記事に書いているので、書籍自体を買おうか迷っている人はこちらを参考にしてみてください。

https://karaage.hatenadiary.jp/entry/2021/04/02/073000

本記事は「ゼロからのOS自作入門」を読んで、個人的にまとめたものとなります。内容に関して、もし問題や誤りがあった場合の文責は私にありますので、この記事に関しての疑問は私に問い合わせください。もちろん書籍自体の質問は、書籍のサポートに連絡ください。

この記事を読むと、書籍を読まなくてもOSを動かすことはできます。ただ、書籍を読みながら自分で理解したり改造したりしながら動かさないと何も身につかないと思うので、興味ある人は書籍を買いましょう。価格の何倍もの価値がある良書だと思います。

https://amzn.to/39wj9UI

ゼロからのOS自作入門

0章 付録A 初期セットアップ

ネイティブのLinux(Ubuntu)にセットアップする方法と、Mac(Intel)+Dockerを使う方法を記載します。
ネイティブのMacにインストールする方法だと、自分の環境ではカーネルの描画がどうしてもうまくいきませんでした。以下のスクラップに奮闘記を残してあるので、チャレンジしたい方は参考にしてみてください(うまくいかない原因わかった人いたら教えてください)。

https://zenn.dev/karaage0703/scraps/b2705131673377

ネイティブのLinuxにセットアップする方法

マシンはX86互換の適当なものを選びましょう。といっても何を選んだら良いか分からない人は、SEEEDさんのODYSSEY X86J4105あたりが良いかと思います。今回の記事もネイティブのLinux(Ubuntu)に関してはこのマシンで検証しています。

https://zenn.dev/karaage0703/articles/342980220b2ec6

Linuxのセットアップに関しては、以下記事を参照ください。

https://qiita.com/karaage0703/items/705f1b750c486f00d554

全部セットアップする必要はありませんが、エディタは何かしらインストールしておきましょう。初心者であればVS Codeがおすすめです。VS Codeに関しては、以下Zennの本参照ください。

https://zenn.dev/karaage0703/books/80b6999d429abc8051bb/

あとは、書籍の以下リポジトリを参考にセットアップしましょう。

https://github.com/uchan-nos/mikanos-build

「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ビルドでこけました。セットアップ方法は、以下記事を参考にしました。

https://zenn.dev/sarisia/articles/6b57ea835344b6

上記記事のVS Code Remote Container(devcontainer)を使う方法が最高です。以下の図(少し分かりづらいですが)のようにVS CodeでDockerのコンテナ内のファイルの編集とコンテナ内でのターミナル操作ができます。XQuartzにXを飛ばすことでコンテナ内のGUIアプリも動かすことができます。

セットアップ方法は、ほとんど参考にした記事そのままなのですが、少しだけ自分が分かってなかったところを補足します。まず、事前準備として以下をセットアップしておく必要があります。

  • Docker
  • VS Code
  • XQuartz

Docker

Dockerに関しては以下記事参考にしてインストール、起動しておきます。

https://karaage.hatenadiary.jp/entry/2019/05/17/073000

VS Code

VS Codeに関しては、以下参考にしてインストールしておいてください。

https://zenn.dev/karaage0703/books/80b6999d429abc8051bb/viewer/5b814b

拡張機能の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を参考にセットアップを実施します。

https://github.com/sarisia/mikanos-devcontainer

具体的には、ターミナルで以下コマンドを実行すれば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ファイルに関して知りたい人は、以下記事等参照ください。

https://qiita.com/kakinaguru_zo/items/f74bcfbf9f75d7e7a913

最後に、バイナリファイルを書き込んだイメージを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メモリを挿入します。ここで、挿入したディスクのデバイスファイル名を確認します。dmesgdfコマンドを使って調べましょう。

自分がよくやるのは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の情報があることを知りました。

https://fukuno.jig.jp/3165

以下、無料で公開されています。

http://yuma.ohgami.jp/UEFI-Bare-Metal-Programming/

素晴らしい情報です。こちらの書籍は、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自作入門」が動かせない人・何をしているかよく分からない人の助けになるような記事を書いてみました。

自分自身もまだまだ理解できてないところがあるので、これはというポイントあれば、随時追記していきたいと思います。

https://amzn.to/39wj9UI

ゼロからのOS自作入門

参考リンク

公式サイト

https://zero.osdev.jp/

https://github.com/uchan-nos/mikanos-build

動画

https://www.youtube.com/playlist?list=PLbBGNsln3DxTLHB9GFM6_drAJ1JQXIOud

Macでの動作

https://qiita.com/yamoridon/items/4905765cc6e4f320c9b5

https://zenn.dev/sarisia/articles/6b57ea835344b6

https://gokids.hatenablog.com/entry/2018/11/14/190000

https://zenn.dev/nnabeyang/scraps/f9151dfbe4f294

この記事に贈られたバッジ

Discussion

失礼します。私も最近自作OS入門を始めて、第3章の画面表示の練習のところで、白が表示されるだけの状態になりました。解決策が分からず、放り出しそうになりかけたところ、スクラップを含めたこの記事を拝見させていただきました。よろしければ、解決策をご教示いただけないでしょうか。よろしくおねがいします。

@dtrackerhero さん
返信おそくなり申し訳ありません。

ネイティブのMac環境だと私は結局できなくて、ここに記載あるようにMac(Intel)+DockerやネイティブのLinuxを使いました。

ログインするとコメントできます