環境
- OS
- Windows 11 ProのWSL Ubuntu
- CPU
- Intel Core i9-13900K
背景
「自作OSで学ぶマイクロカーネルの設計と実装」の補足資料である上記Webページの実装はC言語で書かれています。
これをRustで実装してみたいと思いました。
この記事の筆者は、「自作OSで学ぶマイクロカーネルの設計と実装」をまだ読んでいないため、このWebサイトには載っていないが、本には載っている情報がある可能性があります。
DockerでOSの実行環境構築
現状、dockerイメージのサイズが20GB弱になるため、PCの容量に注意
参考サイトにはDocker環境が用意されていませんでしたが、WSLのUbuntu上でclang等の重要性の高い環境を万が一汚してしまうと元に戻すのが大変なので、失敗しやすい環境を用意するのが良いと考えました。UbuntuのDocker Imageをベースに必要なライブラリをインストールします。
また、このサイトではプログラムのコンパイルターゲットとして、RISCアーキテクチャを指定しています。
普段私たちが使用しているIntelやAMD, Appleのアーキテクチャとは異なるので、RISC専用の対応をする必要があります。
Webサイトの手順にはqemu-system-riscv32
などいくつかのライブラリを入れるだけで動くと書いてありましたが、自分の環境ではriscv-gnu-toolchain
をビルドしないとWebサイトのサンプルコードの./run.sh
がエラーになり、起動することができませんでした。(原因不明)
FROM ubuntu:latest
# 必要なパッケージをインストールするための準備
RUN apt-get update && \
apt-get upgrade -y
RUN apt-get install -y \
build-essential \
clang \
curl \
git \
wget \
gawk \
texinfo \
bison \
autoconf \
cmake automake autotools-dev \
python3 python3-pip \
libmpc-dev libmpfr-dev libgmp-dev flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev ninja-build libglib2.0-dev
RUN apt-get install -y \
lld \
llvm \
qemu-system-riscv32
# RISC-VのGCCツールチェーンをビルドするためのスクリプト等をダウンロード
RUN git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
# RISC-V 32ビット用のツールチェーンをビルド
WORKDIR /riscv-gnu-toolchain
RUN ./configure --prefix=/opt/riscv32 --with-arch=rv32i --with-abi=ilp32 && \
make
# 環境変数を設定して、ツールチェーンが見つかるようにする
ENV PATH="/opt/riscv32/bin:${PATH}"
RUN mkdir -p work && \
cd work && \
curl -LO https://github.com/qemu/qemu/raw/v8.0.4/pc-bios/opensbi-riscv32-generic-fw_dynamic.bin
WORKDIR /work
services:
main:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/work
tty: true
Rustの開発環境の構築
Dockerfileに以下のコードを追加します。
RUN curl https://sh.rustup.rs -sSf | sh && \
source "$HOME/.cargo/env" && \
rustup default nightly && \
rustup target add riscv32i-unknown-none-elf && \
cargo install cargo-binutils && \
rustup component add llvm-tools-preview
-
rustup
というRust公式ページで推奨されているRustの管理ツールをインストールします。 - 自作OSなど低レイヤーを扱わない限り出てこないインラインアセンブリの機能をRustで使用するためにはnightlyバージョンのRustをインストールする必要がありました。
- RustからRISC-Vアーキテクチャのアセンブリにコンパイルするためにtargetを指定しますが、RISC-V系のセットはデフォルトでは入っていないため、追加する必要があります。今回は
riscv32i-unknown-none-elf
を指定しました。unknownやnone, elfに関しては拡張命令セットが入っているかやターゲットのプラットフォーム(linux, esp, gnu, ...)などによって変える必要がありそうです。 -
cargo-bin-utils
やllvm-tools-preview
はobjdumpをする際にrustのコードの関数名を表示してくれるなど、Rustをビルドしたバイナリを読む際はサイトの方法でobjdumpするよりもこちらを使用したほうが良さそうでした。
Rustで最初のカーネルコードを書く
まず、以下のコマンドを実行し、Rustプロジェクトのテンプレートを作成します。
$ cargo new [project_name]
ここでは、src/main.rsをkernel.rsにrenameし、このファイルにkernel.cのRustコードを書きます。
自作OS特有の問題
- 通常のRustコードでは必須であるmain関数を使用しないため、
#![no_main]
というオプションが必要でした。 -
kernel_main
関数はアセンブリから呼び出すため、#[allow(dead_code)]
というオプションが必要でした - 標準ライブラリを使用しないno_std環境ではpanic関数を自分で定義する必要がありました。
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
- インラインアセンブリは以下のように使用します。C言語とは書き方が違うのでここで結構苦しみました。正しいかはわからないが、objdumpの結果を見るとうまく配置されているように感じました。
use core::arch::asm;
#[no_mangle]
#[link_section = ".text.boot"]
pub unsafe extern "C" fn boot() -> ! {
// スタックポインタを初期化
asm!(
"mv sp, {stack_top}\n
j {kernel_main}\n",
stack_top = in(reg) &__stack_top,
kernel_main = sym kernel_main,
);
loop {}
}
RustからRISC-Vにコンパイルする
kenel.ldやrun.shはサンプルコードがほぼそのまま使えるので、この記事では省略
Cargo.tomlにバイナリの出力ファイル名と一番最初に見るRustコードを指定しました。
[[bin]]
name = "kernel_elf"
path = "src/kernel.rs"
コンパイルオプションは.cargo/config.tomlに記述する必要があります。
- build ターゲットを
riscv32i-unknown-none-elf
に指定 - リンカスクリプトをkernel.ldに指定
- kernel.mapというファイルにバイナリのmapping結果を出力する
[target.riscv32i-unknown-none-elf]
rustflags = [
"-Clink-arg=-Tkernel.ld",
"-Clink-arg=-Map=kernel.map"
]
[build]
target = "riscv32i-unknown-none-elf"
ビルドの実行を実行します。
$ cargo build --release
すると、./target/riscv32i-unknown-none-elf/release/kernel_elf
に実行バイナリが生成されました!
また、kenel.mapも出力されています。
これをみると、参考サイトのC言語の結果とは少し違い、Rust特有の配置が行われているように感じます。
80200000 80200000 28 1 /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib/libcompiler_builtins-2fea179addb75593.rlib(compiler_builtins-2fea179addb75593.compiler_builtins.cd3c4ffcff995398-cgu.086.rcgu.o):(.eh_frame+0x0)
80200028 80200028 24 1 /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib/libcompiler_builtins-2fea179addb75593.rlib(compiler_builtins-2fea179addb75593.compiler_builtins.cd3c4ffcff995398-cgu.006.rcgu.o):(.eh_frame+0x3c)
8020004c 8020004c 14 1 /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/riscv32i-unknown-none-elf/lib/libcompiler_builtins-2fea179addb75593.rlib(compiler_builtins-2fea179addb75593.compiler_builtins.cd3c4ffcff995398-cgu.053.rcgu.o):(.eh_frame+0x14)
とりあえず、エラーがでなかったので、ここを記事の区切りとし、今後進めていく際に上記手法に問題があれば修正していきます。
(Rustや自作OSに詳しい方におかしい点などご指摘いただけたらとても有難いです。修正します)
ソースコード