引言 想起我們之前在學習C的時候,總是提到malloc,總是提起,使用malloc現(xiàn)場申請的內存是屬于堆,而直接定義的變量內存屬于棧. 還記得當初學習STM32的時候CubeIDE要設置stack 和heap的大小. 但是我們要記得,這么好用的功能,實際上是操作系統(tǒng)在負重前行. 那么為了實現(xiàn)動態(tài)內存
想起我們之前在學習C的時候,總是提到
malloc
,總是提起,使用
malloc
現(xiàn)場申請的內存是屬于
堆
,而直接定義的變量內存屬于
棧
.
還記得當初學習STM32的時候CubeIDE要設置
stack
和
heap
的大小.
但是我們要記得,這么好用的功能,實際上是 操作系統(tǒng)在負重前行 .
那么為了實現(xiàn)動態(tài)內存分配功能,操作系統(tǒng)需要有如下功能:
動態(tài)內存分配的實現(xiàn)方法 :
應用另外放置了一個大小可以隨著應用的運行動態(tài)增減的內存空間 – 堆(Heap)。同時,應用還要能夠將這個堆管理起來,即支持在運行的時候從里面分配一塊空間來存放變量,而在變量的生命周期結束之后,這塊空間需要被回收以待后面的使用。如果堆的大小固定,那么這其實就是一個連續(xù)內存分配問題,同學們可以使用操作系統(tǒng)課上所介紹到的 各種連續(xù)內存分配算法 。
動態(tài)內存分配的弊端---內存碎片 :
應用進行多次不同大小的內存分配和釋放操作后,會產(chǎn)生內存空間的浪費,即存在無法被應用使用的空閑內存碎片。
內存碎片是指無法被分配和使用的空閑內存空間?蛇M一步細分為內碎片和外碎片:
這里 首先提到了在 STD 庫中的堆相關的數(shù)據(jù)結構.可以自行閱讀并且大概理解下圖.
但是這一部分向我們傳達的信息是:
如上部分所說:
上述與堆相關的智能指針或容器都可以在 Rust 自帶的
alloc
crate 中找到。當我們使用 Rust 標準庫std
的時候可以不用關心這個 crate ,因為標準庫內已經(jīng)已經(jīng)實現(xiàn)了一套堆管理算法,并將alloc
的內容包含在std
名字空間之下讓開發(fā)者可以直接使用。然而操作系統(tǒng)內核運行在禁用標準庫(即no_std
)的裸機平臺上,核心庫core
也并沒有動態(tài)內存分配的功能,這個時候就要考慮利用alloc
庫定義的接口來實現(xiàn)基本的動態(tài)內存分配器。
具體實現(xiàn)這個
動態(tài)內存分配器
,是為自己實現(xiàn)的這個
結構體
,實現(xiàn)
GlobalAlloc
的
Trait
.
alloc
庫需要我們提供給它一個全局的動態(tài)內存分配器
,它會利用該分配器來管理堆空間,從而使得與堆相關的智能指針或容器數(shù)據(jù)結構可以正常工作。具體而言,我們的動態(tài)內存分配器需要實現(xiàn)它提供的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
,分別代表堆空間的分配和回收,也同樣使用一個裸指針(也就是地址)作為分配的返回值和回收的參數(shù)。兩個接口中都有一個alloc::alloc::Layout
類型的參數(shù), 它指出了分配的需求,分為兩部分,分別是所需空間的大小size
,以及返回地址的對齊要求align
。這個對齊要求必須是一個 2 的冪次,單位為字節(jié)數(shù),限制返回的地址必須是align
的倍數(shù)。
在
os/Cargo.toml
中引入:
buddy_system_allocator = "0.6"
alloc
庫
在
os/src/main.rs
中引入.
// os/src/main.rs
extern crate alloc;
創(chuàng)建
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);
}
}
可以看到
實例化
了一個靜態(tài)變量
HEAP_ALLOCATOR
.并且
實例化
了一個數(shù)組
HEAP_SPACE
來作為它的
堆
.
其中.
HEAP_SPACE
的大小為
KERNEL_HEAP_SIZE
.
那么這個
KERNEL_HEAP_SIZE
是取自
config
這個包的.
這里根據(jù)
代碼倉庫里的代碼
來設置
KERNEL_HEAP_SIZE
的大小.
// os/src/config.rs
pub const KERNEL_HEAP_SIZE: usize = 0x30_0000;
大小為
3145728
.
注意上一段的代碼,要標注
#[global_allocator]
這樣這里的內存分配器才能被識別為全局動態(tài)內存分配器.
#[global_allocator]
需要
開啟條件編譯
,所以需要在
main.rs
里聲明:
#![feature(alloc_error_handler)]
這時候就可以在
os/src/mm/heap_allocator.rs
里創(chuàng)建處理函數(shù)了:
// 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
里創(chuàng)建測試函數(shù).
#[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)]
很有意思,可以
阻止編譯器對你因為調試暫時不調用的函數(shù)報錯
.
這里注意使用了
println
,在文件最上邊加一句
use crate::println
.
這里的測試程序先獲取了
sbss
的到
ebss
的范圍.
這里回顧清零
bss
段的代碼:
bss
段本身是一個儲存未初始化的全局變量的內存區(qū)域sbss
是bss
的開頭ebss
是bss
的結尾
那么
bss_range
實際上是
bss
的范圍.
根據(jù)
這里
,理解
Box::new(5)
是嘗試在堆上儲存
a
的值,且這個值為
5
.
那么下邊的斷言語句:
assert_eq!(*a, 5);
assert!(bss_range.contains(&(a.as_ref() as *const _ as usize)));
就很好理解了:
- 判斷
a
的值是否為5
- 判斷
a
的指針是否在bss
的范圍內
隨后的操作則是創(chuàng)建了一個
Vec
容器,然后儲存了
0..500
的值進去,并且分別執(zhí)行上述對
a
的斷言判斷.
如果斷言沒有報錯,那么最后自然會輸出
heap_test passed!
.
(最后注意
drop
是在堆(動態(tài)內存)里釋放掉某個變量)
在
os/src/mm
下創(chuàng)建
mod.rs
使得
mm
可以被識別為一個包.
為了使用
heap_allocator
里的
init_heap
和
heap_test
,需要公開聲明這個
mod
:
// os/src/mm/mod.rs
pub mod heap_allocator;
main
函數(shù),實現(xiàn)測試
// 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!
,說明測試成功了.
本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權,請發(fā)郵件[email protected]
湘ICP備2022002427號-10 湘公網(wǎng)安備:43070202000427號© 2013~2025 haote.com 好特網(wǎng)