5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustで作るx86_64 自作OS入門シリーズ
Part1【環境構築】 | Part2【no_std】 | Part3【Hello World】| Part4【VGA】 | Part5【割り込み】 | Part6【ページング】 | Part7【ヒープ】 | Part8【マルチタスク】| Part9【ファイルシステム】 | Part10【ELFローダー】 | Part11【シェル】 | Part12【完結】

はじめに

Part6でページングを実装しました。

今回は ヒープメモリ を実装します!これができるとBoxVecStringが使えるようになる!

ぶっちゃけ、ここが一番ワクワクしたところです。Rustの強力なコレクション型がOSカーネルで使えるって、ものすごくテンション上がる。

目次

  1. スタックとヒープ
  2. グローバルアロケータ
  3. ヒープ領域をマップする
  4. allocクレートを使う
  5. 動作確認

スタックとヒープ

スタック ヒープ
サイズ コンパイル時に決定 実行時に動的
確保 自動(関数呼び出し時) 手動(Box::newなど)
解放 自動(関数終了時) 手動(Dropトレイト)
速度 高速 やや遅い

no_std環境では、ヒープを使うには自分でアロケータを実装する必要があります。

依存クレートを追加

# Cargo.toml
[dependencies]
linked_list_allocator = "0.10"

linked_list_allocatorはフリーリスト方式のシンプルなアロケータ。自作してもいいけど、今回は既存クレートを使います。

build-stdの更新

allocクレートを使うために、.cargo/config.tomlを更新:

[unstable]
build-std = ["core", "compiler_builtins", "alloc"]  # alloc追加!
build-std-features = ["compiler-builtins-mem"]

グローバルアロケータ

Rustでは#[global_allocator]属性でグローバルアロケータを指定します。

src/allocator.rsを新規作成:

use x86_64::{
    structures::paging::{
        mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB,
    },
    VirtAddr,
};
use linked_list_allocator::LockedHeap;

pub const HEAP_START: usize = 0x_4444_4444_0000;
pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB

#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

HEAP_STARTの値

0x_4444_4444_0000という値に深い意味はないです。デバッグ時に「4が並んでるからヒープだな」ってわかりやすいから選びました。

重要なのは:

  • カーネルのコード領域と被らない
  • スタック領域と被らない
  • 48ビットアドレス空間内

ヒープ領域をマップする

ヒープ用の仮想アドレスに、物理フレームをマップします:

pub fn init_heap(
    mapper: &mut impl Mapper<Size4KiB>,
    frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) -> Result<(), MapToError<Size4KiB>> {
    let page_range = {
        let heap_start = VirtAddr::new(HEAP_START as u64);
        let heap_end = heap_start + HEAP_SIZE as u64 - 1u64;
        let heap_start_page = Page::containing_address(heap_start);
        let heap_end_page = Page::containing_address(heap_end);
        Page::range_inclusive(heap_start_page, heap_end_page)
    };

    for page in page_range {
        let frame = frame_allocator
            .allocate_frame()
            .ok_or(MapToError::FrameAllocationFailed)?;
        let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
        unsafe {
            mapper.map_to(page, frame, flags, frame_allocator)?.flush();
        }
    }

    unsafe {
        ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE);
    }

    Ok(())
}

やっていること:

  1. ヒープ領域のページ範囲を計算(100KiB ÷ 4KiB = 25ページ)
  2. 各ページに物理フレームを割り当て
  3. PRESENT | WRITABLEフラグでマップ
  4. アロケータを初期化

allocクレートを使う

main.rsに追加:

#![no_std]
#![no_main]
#![feature(abi_x86_interrupt)]

extern crate alloc;  // これを追加!

mod vga;
mod interrupts;
mod memory;
mod allocator;

use alloc::{boxed::Box, vec::Vec, string::String};

extern crate allocallocクレートをインポート。これでBoxVecStringなどが使えます。

main.rsの更新

fn kernel_main(boot_info: &'static BootInfo) -> ! {
    // ... 前回までの初期化 ...
    
    // ページングの初期化
    let phys_mem_offset = x86_64::VirtAddr::new(boot_info.physical_memory_offset);
    let mut mapper = unsafe { memory::init(phys_mem_offset) };
    let mut frame_allocator = unsafe { 
        memory::BootInfoFrameAllocator::init(&boot_info.memory_map) 
    };
    serial_print("Paging initialized\n");
    
    // ヒープの初期化
    allocator::init_heap(&mut mapper, &mut frame_allocator)
        .expect("heap initialization failed");
    serial_print("Heap initialized\n");
    
    // ヒープのテスト!
    println!("Testing heap allocations...");
    
    // Boxのテスト
    let heap_value = Box::new(42);
    println!("Box<i32>: {}", *heap_value);
    
    // Vecのテスト
    let mut vec = Vec::new();
    for i in 0..10 {
        vec.push(i);
    }
    println!("Vec: {:?}", vec);
    
    // Stringのテスト
    let mut s = String::from("Hello");
    s.push_str(", Heap!");
    println!("String: {}", s);
    
    // ...
}

動作確認

cargo bootimage
   Compiling alloc v0.0.0 (...)
   Compiling linked_list_allocator v0.10.5
   Compiling my-os v0.1.0 (C:\Users\Aqua\Documents\Qiita\my-os)
warning: method `as_usize` is never used
warning: function `translate_addr` is never used
    Finished `dev` profile [optimized + debuginfo] target(s) in 7.81s
Building bootloader
    Finished `release` profile [optimized + debuginfo] target(s) in 1.76s
Created bootimage for `my-os` at `target\x86_64-my_os\debug\bootimage-my-os.bin`

allocクレートがビルドされてる!

qemu-system-x86_64 -drive format=raw,file=target\x86_64-my_os\debug\bootimage-my-os.bin -serial file:serial.log

シリアルログ:

=== My OS Booting ===
VGA initialized
IDT initialized
PICs initialized
Interrupts enabled
Paging initialized
Heap initialized
Heap test complete
Entering infinite loop...

画面:

Testing heap allocations...
Box<i32>: 42
Vec: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
String: Hello, Heap!

Heap works! Try pressing keys...

動いた!!!

VecStringが自作OSで使えるなんて...感動です。

何が起きてるのか

Box::new(42)を呼ぶと:

1. Box::new(42) 呼び出し
   ↓
2. alloc::alloc() が呼ばれる
   ↓
3. #[global_allocator] の ALLOCATOR.alloc() が呼ばれる
   ↓
4. linked_list_allocator がフリーリストから4バイト確保
   ↓
5. ヒープ領域(0x4444_4444_0000〜)から確保
   ↓
6. そのアドレスに42を書き込む
   ↓
7. Box<i32> 完成!

linked_list_allocatorの仕組み

フリーリスト方式:

初期状態:
[             Free: 100KiB             ]

Box::new(42) 後:
[42][          Free: ~100KiB           ]

Vec確保後:
[42][0,1,2,...,9][   Free: ~100KiB    ]

解放されたブロックはリストに戻されて、隣接するフリーブロックは結合されます。

ヒープサイズについて

今回は100KiBにしました。小さいけど、デモには十分。

実際のOSでは:

  • 初期サイズをもっと大きくする
  • 必要に応じて拡張する仕組みを作る

まとめ

Part7では以下を達成しました:

  • グローバルアロケータの設定
  • ヒープ領域のマッピング
  • allocクレートの有効化
  • Box、Vec、Stringが動いた!

ここまで来ると、かなりOSっぽくなってきました。

次回以降は:

  • 非同期処理(async/await)
  • ファイルシステム
  • ユーザーモード

などを実装していく予定です。まだまだ道のりは長い...でも楽しい!

この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!

5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?