引言 想起我们之前在学习C的时候,总是提到malloc,总是提起,使用malloc现场申请的内存是属于堆,而直接定义的变量内存属于栈. 还记得当初学习STM32的时候CubeIDE要设置stack 和heap的大小. 但是我们要记得,这么好用的功能,实际上是操作系统在负重前行. 那么为了实现动态内存
想起我们之前在学习C的时候,总是提到
malloc
,总是提起,使用
malloc
现场申请的内存是属于
堆
,而直接定义的变量内存属于
栈
.
还记得当初学习STM32的时候CubeIDE要设置
stack
和
heap
的大小.
但是我们要记得,这么好用的功能,实际上是 操作系统在负重前行 .
那么为了实现动态内存分配功能,操作系统需要有如下功能:
动态内存分配的实现方法 :
应用另外放置了一个大小可以随着应用的运行动态增减的内存空间 – 堆(Heap)。同时,应用还要能够将这个堆管理起来,即支持在运行的时候从里面分配一块空间来存放变量,而在变量的生命周期结束之后,这块空间需要被回收以待后面的使用。如果堆的大小固定,那么这其实就是一个连续内存分配问题,同学们可以使用操作系统课上所介绍到的 各种连续内存分配算法 。
动态内存分配的弊端---内存碎片 :
应用进行多次不同大小的内存分配和释放操作后,会产生内存空间的浪费,即存在无法被应用使用的空闲内存碎片。
内存碎片是指无法被分配和使用的空闲内存空间。可进一步细分为内碎片和外碎片:
这里 首先提到了在 STD 库中的堆相关的数据结构.可以自行阅读并且大概理解下图.
但是这一部分向我们传达的信息是:
如上部分所说:
上述与堆相关的智能指针或容器都可以在 Rust 自带的
alloc
crate 中找到。当我们使用 Rust 标准库std
的时候可以不用关心这个 crate ,因为标准库内已经已经实现了一套堆管理算法,并将alloc
的内容包含在std
名字空间之下让开发者可以直接使用。然而操作系统内核运行在禁用标准库(即no_std
)的裸机平台上,核心库core
也并没有动态内存分配的功能,这个时候就要考虑利用alloc
库定义的接口来实现基本的动态内存分配器。
具体实现这个
动态内存分配器
,是为自己实现的这个
结构体
,实现
GlobalAlloc
的
Trait
.
alloc
库需要我们提供给它一个全局的动态内存分配器
,它会利用该分配器来管理堆空间,从而使得与堆相关的智能指针或容器数据结构可以正常工作。具体而言,我们的动态内存分配器需要实现它提供的GlobalAlloc
Trait
GlobalAlloc
的抽象接口:
// alloc::alloc::GlobalAlloc
pub unsafe fn alloc(&self, layout: Layout) -> *mut u8;
pub unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
可以看到,它们类似 C 语言中的
malloc/free
,分别代表堆空间的分配和回收,也同样使用一个裸指针(也就是地址)作为分配的返回值和回收的参数。两个接口中都有一个alloc::alloc::Layout
类型的参数, 它指出了分配的需求,分为两部分,分别是所需空间的大小size
,以及返回地址的对齐要求align
。这个对齐要求必须是一个 2 的幂次,单位为字节数,限制返回的地址必须是align
的倍数。
在
os/Cargo.toml
中引入:
buddy_system_allocator = "0.6"
alloc
库
在
os/src/main.rs
中引入.
// os/src/main.rs
extern crate alloc;
创建
os/src/mm/heap_allocator.rs
.
// os/src/mm/heap_allocator.rs
use buddy_system_allocator::LockedHeap;
use crate::config::KERNEL_HEAP_SIZE;
#[global_allocator]
static HEAP_ALLOCATOR: LockedHeap = LockedHeap::empty();
static mut HEAP_SPACE: [u8; KERNEL_HEAP_SIZE] = [0; KERNEL_HEAP_SIZE];
pub fn init_heap() {
unsafe {
HEAP_ALLOCATOR
.lock()
.init(HEAP_SPACE.as_ptr() as usize, KERNEL_HEAP_SIZE);
}
}
可以看到
实例化
了一个静态变量
HEAP_ALLOCATOR
.并且
实例化
了一个数组
HEAP_SPACE
来作为它的
堆
.
其中.
HEAP_SPACE
的大小为
KERNEL_HEAP_SIZE
.
那么这个
KERNEL_HEAP_SIZE
是取自
config
这个包的.
这里根据
代码仓库里的代码
来设置
KERNEL_HEAP_SIZE
的大小.
// os/src/config.rs
pub const KERNEL_HEAP_SIZE: usize = 0x30_0000;
大小为
3145728
.
注意上一段的代码,要标注
#[global_allocator]
这样这里的内存分配器才能被识别为全局动态内存分配器.
#[global_allocator]
需要
开启条件编译
,所以需要在
main.rs
里声明:
#![feature(alloc_error_handler)]
这时候就可以在
os/src/mm/heap_allocator.rs
里创建处理函数了:
// os/src/mm/heap_allocator.rs
#[alloc_error_handler]
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
panic!("Heap allocation error, layout = {:?}", layout);
}
在
os/src/mm/heap_allocator.rs
里创建测试函数.
#[allow(unused)]
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 = 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!");
}
这里的
#[allow(unused)]
很有意思,可以
阻止编译器对你因为调试暂时不调用的函数报错
.
这里注意使用了
println
,在文件最上边加一句
use crate::println
.
这里的测试程序先获取了
sbss
的到
ebss
的范围.
这里回顾清零
bss
段的代码:
bss
段本身是一个储存未初始化的全局变量的内存区域sbss
是bss
的开头ebss
是bss
的结尾
那么
bss_range
实际上是
bss
的范围.
根据
这里
,理解
Box::new(5)
是尝试在堆上储存
a
的值,且这个值为
5
.
那么下边的断言语句:
assert_eq!(*a, 5);
assert!(bss_range.contains(&(a.as_ref() as *const _ as usize)));
就很好理解了:
- 判断
a
的值是否为5
- 判断
a
的指针是否在bss
的范围内
随后的操作则是创建了一个
Vec
容器,然后储存了
0..500
的值进去,并且分别执行上述对
a
的断言判断.
如果断言没有报错,那么最后自然会输出
heap_test passed!
.
(最后注意
drop
是在堆(动态内存)里释放掉某个变量)
在
os/src/mm
下创建
mod.rs
使得
mm
可以被识别为一个包.
为了使用
heap_allocator
里的
init_heap
和
heap_test
,需要公开声明这个
mod
:
// os/src/mm/mod.rs
pub mod heap_allocator;
main
函数,实现测试
// os/src/main.rs
/// the rust entry-point of os
#[no_mangle]
pub fn rust_main() -> ! {
clear_bss();
println!("[kernel] Hello, world!");
logging::init();
println!("[kernel] logging init end");
mm::heap_allocator::init_heap();
println!("[kernel] heap init end");
mm::heap_allocator::heap_test();
println!("heap test passed");
trap::init();
println!("[kernel] trap init end");
loader::load_apps();
trap::enable_timer_interrupt();
timer::set_next_trigger();
task::run_first_task();
panic!("Unreachable in rust_main!");
}
cd os
make run
得到运行结果:
[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______ __ __ _______.___________. _______..______ __
| _ \ | | | | / | | / || _ \ | |
| |_) | | | | | | (----`---| |----`| (----`| |_) || |
| / | | | | \ \ | | \ \ | _ < | |
| |\ \----.| `--' |.----) | | | .----) | | |_) || |
| _| `._____| \______/ |_______/ |__| |_______/ |______/ |__|
[rustsbi] Implementation : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name : riscv-virtio,qemu
[rustsbi] Platform SMP : 1
[rustsbi] Platform Memory : 0x80000000..0x88000000
[rustsbi] Boot HART : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
heap_test passed!
[kernel] IllegalInstruction in application, kernel killed it.
All applications completed!
这里我为了log比较简短,把
user
里需要编译的app只保留了一个
user/src/bin/00hello_world.rs
.
这里看log,
heap_test passed!
,说明测试成功了.
小猫短剧 官方版 4.0.1.6 62.25 MB
下载
湘ICP备2022002427号-10湘公网安备:43070202000427号
© 2013~2019 haote.com 好特网