2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

"Writing an OS in 1000 lines"のDocker + Rustでの開発環境を構築してみた

Last updated at Posted at 2023-08-14

環境

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がエラーになり、起動することができませんでした。(原因不明)

Dockerfile
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
docker-compose.yml
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-utilsllvm-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コードを指定しました。

Cargo.toml
[[bin]]
name = "kernel_elf"
path = "src/kernel.rs"

コンパイルオプションは.cargo/config.tomlに記述する必要があります。

  • build ターゲットをriscv32i-unknown-none-elfに指定
  • リンカスクリプトをkernel.ldに指定
  • kernel.mapというファイルにバイナリのmapping結果を出力する
.cargo/config.toml
[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に詳しい方におかしい点などご指摘いただけたらとても有難いです。修正します)

ソースコード

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?