Buhe Blog - Microsoft Internet Explorer

OS 简单教程五


添加堆支持

为了给虚拟化内存做准备,我们来支持堆,添加堆支持以后就可以使用 Vecor HashMap 等 rust 内建的数据结构,这些数据结构使用动态内存。

Bump

简单的 Bump 堆内存分配器,下面我们就具体看看,BumpAllocator 也实现了 GlobalAlloc

unsafe impl GlobalAlloc for Locked<BumpAllocator> {
   unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
       let mut bump = self.lock(); // get a mutable reference

       let alloc_start = align_up(bump.next, layout.align());
       let alloc_end = alloc_start + layout.size();

       if alloc_end > bump.heap_end {
           ptr::null_mut() // out of memory
      } else {
           bump.next = alloc_end;
           bump.allocations += 1;
           alloc_start as *mut u8
      }
  }

   unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
       let mut bump = self.lock(); // get a mutable reference

       bump.allocations -= 1;
       if bump.allocations == 0 {
           bump.next = bump.heap_start;
      }
  }
}

pub struct BumpAllocator {
   heap_start: usize,
   heap_end: usize,
   next: usize,
   allocations: usize,
}

impl BumpAllocator {
   /// Creates a new empty bump allocator.
   pub const fn new() -> Self {
       BumpAllocator {
           heap_start: 0,
           heap_end: 0,
           next: 0,
           allocations: 0,
      }
  }

   /// Initializes the bump allocator with the given heap bounds.
   ///
   /// This method is unsafe because the caller must ensure that the given
   /// memory range is unused. Also, this method must be called only once.
   pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
       self.heap_start = heap_start;
       self.heap_end = heap_start + heap_size;
       self.next = heap_start;
  }
}

fn align_up(addr: usize, align: usize) -> usize {
   let remainder = addr % align;
   if remainder == 0 {
       addr // addr already aligned
  } else {
       addr - remainder + align
  }
}
  • next 指向已分配和未分配的边界,开始指向 heap_start 也就是内存开始的地方。
  • Bump 很简单,allocations 每次分配 +1 每次释放 -1 ,直到 =0 时 next 指向开始的位置,真实释放所有内存。
  • align_up 根据 layout.align() 来对齐。 最后初始化 Bump 分配器
use bump::{BumpAllocator, Locked};

#[global_allocator]
static HEAP_ALLOCATOR: Locked<BumpAllocator> = Locked::new(BumpAllocator::new());

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

static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];

pub fn init() {
   unsafe {
       HEAP_ALLOCATOR
          .lock()
          .init(HEAP_SPACE.as_ptr() as usize, KERNEL_HEAP_SIZE);
  }
}

第三方库

也可以选择复杂但更成熟的第三方库,首先依赖 buddy_system_allocator

buddy_system_allocator = "0.6"

我用别人写好的伙伴分配器做例子分配堆内存,代码实现起来很简单,也很容易理解。建立 heap/mod.rs 内容

use buddy_system_allocator::LockedHeap;
const KERNEL_HEAP_SIZE: usize = 0x30_0000;

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

static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];

pub fn init() {
   unsafe {
       HEAP_ALLOCATOR
          .lock()
          .init(HEAP_SPACE.as_ptr() as usize, KERNEL_HEAP_SIZE);
  }
}

#[alloc_error_handler]
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
   panic!("Heap allocation error, layout = {:?}", layout);
}
  • HEAP_ALLOCATOR 实现了堆分配的 trait ,建立一个 HEAP_ALLOCATOR 作为分配器
  • init 初始化一个数组来分配堆内存
  • handle_alloc_error 是分配失败的处理器 main.rs 里调用,如下
#![feature(alloc_error_handler)]

extern crate alloc;

#[no_mangle]
extern "C" fn rust_main() -> ! {
   clear_bss();
   heap::init();
   heap::heap_test();
   trap::init();
   task::init();
   task::run();
}
  • #![feature(alloc_error_handler)] 和 extern crate alloc; 包含分配器的 trait
  • heap::init() 初始化 最后使用 heap::heap_test() 测试测试~
pub fn heap_test() {
   use alloc::boxed::Box;
   use alloc::vec::Vec;
   extern "C" {
       fn sbss();
       fn ebss();
  }
   let bss_range = sbss as usize..ebss as usize;
   let a = Box::new(5);
   assert_eq!(*a, 5);
   assert!(bss_range.contains(&(a.as_ref() as *const _ as usize)));
   drop(a);
   let mut v: Vec<usize> = Vec::new();
   for i in 0..500 {
       v.push(i);
  }
   for i in 0..500 {
       assert_eq!(v[i], i);
  }
   assert!(bss_range.contains(&(v.as_ptr() as usize)));
   drop(v);
   println!("heap_test passed!");
}

看看输出

[rustsbi] RustSBI version 0.2.0-alpha.3
.______       __   __     _______.___________. _______..______   __
|   _ \     | | | |   /       |           | /       ||   _ \ | |
| |_) |   | | | |   |   (----`---| |----`|   (----`| |_) || |
|     /     | | | |   \   \       | |     \   \   |   _ < | |
| |\ \----.| `--' |.----)   |     | | .----)   |   | |_) || |
| _| `._____| \______/ |_______/       |__| |_______/   |______/ |__|

[rustsbi] Platform: K210 (Version 0.2.0)
[rustsbi] misa: RV64ACDFIMSU
[rustsbi] mideleg: 0x22
[rustsbi] medeleg: 0x1ab
[rustsbi] Kernel entry: 0x80020000
heap_test passed!
[kernel] app_0 [0x8002c018, 0x8002c938)
Hello OS from app
[kernel] Application exited with code 0

打印 heap_test passed! ,说明分配成功了。 具体代码请参考 https://github.com/buhe/bugu/tree/7