はじめに
前回少しだけ触れたメモリアロケーションについて。
Rustは1.28からGlobal Allocatorという仕組みが使えるようになったので紹介したい。
「ラズパイ2で〜」と銘打ってはいるが、前回とは異なりCPUに依存する部分はあまり無い(アドレス幅を32bit決め打ちにしたりはしている)。
Global Allocatorとliballoc
ここに書いてあるとおりなのだがcore::alloc::GlobalAlloc
トレイトを実装して(ここではmem::KernelAllocator
というstructを実装した)
# [global_allocator]
static GLOBAL: mem::KernelAllocator = mem::KernelAllocator;
変数名に特に縛りはなく#[global_allocator]
を指定してやることでGlobal Allocatorとして使用されるようになる。
あとは
# [macro_use]
extern crate alloc;
use alloc::prelude::*;
このように宣言してやることでliballocが使用できる、つまりたったこれだけで**#[no_std]
環境でBox
やVec
やformat!
が使えるようになるのだ!**
「nightlyじゃなきゃベアメタル厳しいのどうなの?」と言われてしまうRustではあるが、liballocが手軽に使えるようになるGlobal Allocatorという仕組み一点だけとってもベアメタル環境でRustを選択するアドバンテージは計り知れないものがあると個人的には思っている。
ちなみにRust2018からは一般的にextern crate
が不要になると理解されているが一部例外があり、上記extern crate alloc;
はRust2018でも依然として必要となる。詳しくは以下参照。
実装例
.kernelheap : {
__kernel_heap_start__ = .;
. += 0x100000;
}
. = ALIGN(4096);
__kernel_heap_end__ = .;
リンカースクリプト内で固定領域を指定して__kernel_heap_start__
と__kernel_heap_end__
を定義。
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内でリンカースクリプトで定義したシンボルのアドレスを使えるようにする。
# [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()
参照)。
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を実行するような作りになるのだろうか。