Slide 1

Slide 1 text

RustでOS開発はじめの一歩 UV Study: Rust LT会 Dec. 19 2023 - nasa

Slide 2

Slide 2 text

発表のモチベ ● 最近RustでOSを書いている ● これまでCLIツールやライブラリをRustで実装してきた ● OSだと普段書いているコードが普通には動作しない ● どんな違いがあるのか話したい

Slide 3

Slide 3 text

動作環境 ● platform: QEMU riscv64 ● build target: riscv64gc-unknown-none-elf # .cargo/config.toml [build] target = "riscv64gc-unknown-none-elf" [target.riscv64gc-unknown-none-elf] runner = """ qemu-system-riscv64 \ -machine virt \ -bios default \ --no-reboot \ -nographic \ -serial mon:stdio \ -kernel """

Slide 4

Slide 4 text

ベアメタル環境 ● OSによりプログラムがホスティングされない ● 自分の書いたプログラムを動かし始めるまでの環境がない ● time, io, fsなどOSの上に構成されている標準機能が使えない ● メモリアロケーターも無いのでVec, String等が使えない

Slide 5

Slide 5 text

ベアメタル環境

Slide 6

Slide 6 text

main関数を動かす

Slide 7

Slide 7 text

main関数を動かす QEMU riscvのdefault biosは0x80200000を実行する

Slide 8

Slide 8 text

main関数を動かす プログラムを0x8020_0000に配置し呼出す

Slide 9

Slide 9 text

main関数を動かす リンカースクリプトで任意の アドレスにプログラムを配置する .entryシンボルを8020_0000に配置 OUTPUT_ARCH( "riscv" ) ENTRY( _entry ) SECTIONS { . = 0x80200000; .text : { *(.entry) *(.text .text.*) } .rodata : { *(.rdata .rodata .rodata.*) } .data : { *(.data .data.*) } .bss : { *(.bss bss.*) } }

Slide 10

Slide 10 text

main関数を動かす main.rsで.entryを定義 これで関数_entryが呼ばれる // src/main.rs #![no_std] #![no_main] #[no_mangle] #[link_section = ".entry"] pub fn _entry() { main(); } #[inline] fn main() { loop {} } #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

Slide 11

Slide 11 text

関数呼び出しをサポートしたい

Slide 12

Slide 12 text

関数呼び出し みんな大好き関数呼び出し。 というかプログラムを書く上でほぼ必須 ● 関数呼び出しが出来ない。。 ● inlineオプションを付けると呼び出せる fn a() { b() } fn b() { c() } pub fn _entry() { main(); } #[inline] fn main() { loop {} }

Slide 13

Slide 13 text

関数呼び出し ● 関数呼び出しが出来ない。。 ● inlineオプションを付けると呼び出せる ● (正確には関数を呼ぶとローカル変数の値がおかしくなる) なぜこれらが起きるのか

Slide 14

Slide 14 text

関数呼び出し ● 関数呼び出し時にスタック領域が確保される ● しかしSPの初期値は0 ● SPを初期化する必要があった

Slide 15

Slide 15 text

関数呼び出し // src/main.rs static INIT_SP: [u8; 4096 * 1024] = [0; 4096 * 1024]; static STACK_SIZE: usize = 4096 * 1024; // 4MB pub unsafe fn _entry() { // NOTE: スタックポインタの初期値を設定する // NOTE: スタックは下位に伸びていくのでINIT_SP + STACK_SIZEを設定 しSTACK_SIZE分の領域を確保 asm!("la sp, INIT_SP", "ld a0, STACK_SIZE", "add sp, sp, a0",); main(); } SPを初期化 誰からも変更されないように配列を定義し末尾アドレスを利用

Slide 16

Slide 16 text

動的メモリ確保したい

Slide 17

Slide 17 text

動的メモリ確保 StringやVec,Boxが普通に使いたくなる これらはallocクレートに定義されている (stdが存在する場合はstdの一部)

Slide 18

Slide 18 text

動的メモリ確保 allocを使うにはメモリアロケーターを実装する必要がある (heaplessクレートを利用するとアロケーターの実装無しでString, Vec等が使 える)

Slide 19

Slide 19 text

メモリアロケーターの実装 global_allocatorディレクティブで登録出来る GlobalAllocトレイトを実装している必要がある #[global_allocator] static mut ALLOCATOR: BumpAllocator = BumpAllocator::new(); pub struct BumpAllocator { arena: RefCell<[u8; ARENA_SIZE]>, next: Cell, } unsafe impl GlobalAlloc for BumpAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 {} unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} }

Slide 20

Slide 20 text

メモリアロケーターの実装 bump allocatorを採用 heapの始点から終点まで順次割り当 てていくだけ フラグメンテーションを一切考慮してい ない 実装が超簡単なので採用

Slide 21

Slide 21 text

まとめ

Slide 22

Slide 22 text

まとめ ● 普段Rustを書く時に色んなものに乗っかっている事が分かった ● OSの機能以外にブート、メモリアロケーター、スタックポインタを気にする 必要があった 「俺の戦いはこれからだ!!」

Slide 23

Slide 23 text

自己紹介 HN: nasa (Asan Kondo) nasaが欲しい ● github: k-nasa ● x(twitter): nasa_desu お仕事: MLOpsっぽいこと、DSの生産性改善

Slide 24

Slide 24 text

(余談) 遭遇したバグたち ● 一度出力した文字が出力できない 「Hel?o W?r?d.」が出力される ○ 原因: SP初期化していないから ● カーネル起動時に有効化していないタイマ割り込みが発生して死ぬ ○ 原因1: エラーコードがズレてた。。本当は`address misaligned` ○ 原因2: アライメントを考慮していなかった ● プロセス切り替え時にSPが意図通り切り替わらない ○ 原因: 調査中。涙

Slide 25

Slide 25 text

ご清聴ありがとうございました!