B-Teck!

お仕事からゲームまで幅広く

ゼロからのOS自作入門をRustでやる 環境構築編

を読み始めた。(読了までいけるかはわからない)

せっかくなのでRustで書いてみようということでググりながらHello Worldの出力まで。
全部作ると大変そうなのでuefi-rsを利用する方針でやります。

環境等

この記事はM1 Macでローカル環境で直接実行することを前提としている。
Docker上での構築やWin・Linuxでの動作については対象としていない。

記事時点の実装は以下。
github.com

Rustのインストールとプロジェクト作成

公式のGetting started を参考にインストール。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh   

ベアメタルプログラミングをする場合nightlyのコンパイラが必要なケースが多いらしいので導入

$ rustup install 

ゼロからのOS自作入門をRustで でrust-srcも必要と書いてあったので用意した

$ rustup component add rust-src --toolchain nightly-aarch64-apple-darwin  

適当な命名でプロジェクト作成

$ cargo new rusty-mikan-os

実装とビルド

Rust で UEFI のハローワールド - 借り初めのひみつきち を参考に実装していく。

Cargo.tomlの修正

uefiの依存を追加

[dependencies]
uefi = "0.24"

.cargo/config.tomlの修正

ゼロからのOS自作入門 in Rust /ブートローダまで を参考にして以下を記述。

  • ビルドのターゲットを x86_64-unknown-uefi
  • UEFIアプリケーションを動作させる際はstdを利用できないため、必要なクレートを明示
  • coreクレートに必要なmemcpy などを compiler-builtins-mem から利用するように指定
[unstable]
build-std = ["core", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]

[build]
target = "x86_64-unknown-uefi"

rust-toolchain.tomlの修正

nightlyでビルドされるように以下の記述を追加

[toolchain]
channel = "nightly"

main.rsの実装

ここの実装も Rust で UEFI のハローワールド - 借り初めのひみつきち を参考にした。
#![feature(abi_efiapi)] はこの記事を書いている時点では指定は不要になっていた。

#![no_std]
#![no_main]

use uefi::prelude::*;
use core::panic::PanicInfo;
use core::fmt::Write;

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

#[entry]
fn efi_main(_handle: Handle, mut st: SystemTable<Boot>) -> Status {
    writeln!(st.stdout(), "Hello, world!").unwrap();

    loop {}
    // Status::SUCCESS
}

以下は記述に対する覚え書き

  • #![no_std] 
    • 通常のライブラリを利用しないベアメタル環境向けという指定
  • #![no_main]
    • 通常のmain関数から始まらないという指定
  • use uefi::prelude::*;
    • uerf-rsの読み込み
  • use core::panic::PanicInfo;
    • 通常はstdの中でpanicが定義されているが、no_std を指定しているので定義が必要
    • #[panic_handler] で定義したpanic関数が呼び出される
      • とりあえず現時点では無限にloopするだけ
  • #[entry]
    • no_main を指定してるのでエントリポイントを指定している
  • st: SystemTable<Boot>
    • UEFIが提供するサービスにアクセスするためのシステムテーブル

ビルド

ここまでの記述をしていれば、以下のコマンドでビルドできる。

$ cargo build --release  

imageを作成してQEMUで動作させる

必要なものをインストール

QEMUは手元でビルドなどはせず、brewで落ちてくるものを利用した。
fatのファイルシステム作成のために dosfstools も合わせてインストールする。

$ brew install qemu
$ brew install dosfstools

また、後続の作業のために GitHub - uchan-nos/mikanos-build: Build and run scripts for MikanOS を手元に用意しておく。
devenv配下にある、OVMF_CODE.fdOVMF_VARS.fd を後の工程で利用する。

imageの作成

新規のイメージを作成してfatでフォーマットする

$ qemu-img create -f raw disk.img 200M  
$ mkfs.fat -n 'MIKAN OS' -s 2 -f 2 -R 32 -F 32 disk.img  

作成したEFIをimage内に配置する

rootに作成された /EFI/BOOTの下に BOOTX64.EFI というファイルがあると、起動時に認識して呼び出してくれる。
image内にディレクトリを作成して、EFIファイルを配置する。

$ hdiutil attach disk.img  
$ mkdir -p /Volumes/MIKAN\ OS/EFI/BOOT  
$ cp target/x86_64-unknown-uefi/release/rusty-mikan-os.efi /Volumes/MIKAN\ OS/EFI/BOOT/BOOTX64.EFI
$ hdiutil detach /Volumes/MIKAN\ OS

作成したimageを起動する

前の工程で取得したOVMF_CODE.fdOVMF_VARS.fd を指定して、作成したimageを起動する

$ qemu-system-x86_64 \
  -drive if=pflash,format=raw,file=$HOME/ghq/github.com/uchan-nos/mikanos-build/devenv/OVMF_CODE.fd \
  -drive if=pflash,format=raw,file=$HOME/ghq/github.com/uchan-nos/mikanos-build/devenv/OVMF_VARS.fd \
  -hda disk.img

以下のような画面が出れば成功

ビルドからimageの起動までをshellにまとめる

都度実行するのも大変なので以下のファイルにまとめた
rusty-mikan-os/qemu_run.sh at d175d8d600a0255e8f6224483c5f73a6951a10ca · beatdjam/rusty-mikan-os · GitHub

参考にしたサイト