Help us understand the problem. What is going on with this article?

[Rust] ラズパイ2でベアメタルメモリアロケーション

はじめに

前回少しだけ触れたメモリアロケーションについて。

Rustは1.28からGlobal Allocatorという仕組みが使えるようになったので紹介したい。

「ラズパイ2で〜」と銘打ってはいるが、前回とは異なりCPUに依存する部分はあまり無い(アドレス幅を32bit決め打ちにしたりはしている)。

Global Allocatorとliballoc

https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html

ここに書いてあるとおりなのだがcore::alloc::GlobalAllocトレイトを実装して(ここではmem::KernelAllocatorというstructを実装した)

main.rs
#[global_allocator]
static GLOBAL: mem::KernelAllocator = mem::KernelAllocator;

変数名に特に縛りはなく#[global_allocator]を指定してやることでGlobal Allocatorとして使用されるようになる。

あとは

main.rs
#[macro_use]
extern crate alloc;

use alloc::prelude::*;

このように宣言してやることでliballocが使用できる、つまりたったこれだけで#[no_std]環境でBoxVecformat!が使えるようになるのだ!

「nightlyじゃなきゃベアメタル厳しいのどうなの?」と言われてしまうRustではあるが、liballocが手軽に使えるようになるGlobal Allocatorという仕組み一点だけとってもベアメタル環境でRustを選択するアドバンテージは計り知れないものがあると個人的には思っている。

ちなみにRust2018からは一般的にextern crateが不要になると理解されているが一部例外があり、上記extern crate alloc;はRust2018でも依然として必要となる。詳しくは以下参照。

https://rust-lang-nursery.github.io/edition-guide/rust-2018/module-system/path-clarity.html#an-exception

実装例

https://github.com/osdev-rs/minimal-kernel-rpi2/tree/qiita-mem

linker.ld
    .kernelheap : {
        __kernel_heap_start__ = .;
        . += 0x100000;
    }

    . = ALIGN(4096);
    __kernel_heap_end__ = .;

リンカースクリプト内で固定領域を指定して__kernel_heap_start____kernel_heap_end__を定義。

mem.rs
extern {
    static __kernel_heap_start__: u32;
    static __kernel_heap_end__: u32;
}

#[inline]
unsafe fn kernel_heap_start() -> u32 {
    &__kernel_heap_start__ as *const _ as u32
}

#[inline]
unsafe fn kernel_heap_end() -> u32 {
    &__kernel_heap_end__ as *const _ as u32
}

Rust内でリンカースクリプトで定義したシンボルのアドレスを使えるようにする。

mem.rs
#[derive(Clone,Copy)]
struct FreeInfo {
    addr: u32,
    size: usize
}

const MAX_FREES:usize = 4090;

static mut FREES: usize = 0;
static mut FREE: [FreeInfo; MAX_FREES] = [FreeInfo{addr:0,size:0}; MAX_FREES];

pub unsafe fn init() {
    FREES = 1;
    FREE[0] = FreeInfo{
        addr: kernel_heap_start() ,
        size: (kernel_heap_end() - kernel_heap_start()) as usize
    };
}

メモリ管理の方法は『30日でできる!OS自作入門』にある空きメモリだけを管理するアルゴリズムを借用した。
最初は全メモリを持つ1つのFreeInfoだけがあり(init()参照)、そこから必要に応じてメモリを取り出していき、開放されたメモリは前後の領域とマージできない場合はFREE[]に追加されていく(KernelAllocator::dealloc()参照)。

mem.rs
unsafe impl GlobalAlloc for KernelAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // ...
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        // ...
    }
}

#[alloc_error_handler]
fn foo(_: Layout) -> ! {
    uart::write("alloc_error");
    loop {}
}

実装の詳細は省略。

あとは上で記載したようにKernelAllocatorのstaticインスタンスを生成し#[global_allocator]を指定すればliballocが使用可能となる。

今後の課題とか

UserモードでMMUを使用したときのメモリアロケーションをどのように実装するべきか見当がついていない。Global Allocator内でプロセッサモードを見てUserモードであればSVCを実行するような作りになるのだろうか。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away