您的位置:首頁 > 軟件教程 > 教程 > [rCore學習筆記 028] Rust 中的動態(tài)內存分配

[rCore學習筆記 028] Rust 中的動態(tài)內存分配

來源:好特整理 | 時間:2024-10-02 09:53:39 | 閱讀:191 |  標簽: T 2 S C   | 分享到:

引言 想起我們之前在學習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)需要有如下功能:

  • 初始時能提供一塊大內存空間作為初始的“堆”。在沒有分頁機制情況下,這塊空間是物理內存空間,否則就是虛擬內存空間。
  • 提供在堆上分配和釋放內存的函數(shù)接口。這樣函數(shù)調用方通過分配內存函數(shù)接口得到地址連續(xù)的空閑內存塊進行讀寫,也能通過釋放內存函數(shù)接口回收內存,以備后續(xù)的內存分配請求。
  • 提供空閑空間管理的連續(xù)內存分配算法。相關算法能動態(tài)地維護一系列空閑和已分配的內存塊,從而有效地管理空閑塊。
  • (可選)提供建立在堆上的數(shù)據(jù)結構和操作。有了上述基本的內存分配與釋放

動態(tài)內存分配

實現(xiàn)方法

動態(tài)內存分配的實現(xiàn)方法 :

應用另外放置了一個大小可以隨著應用的運行動態(tài)增減的內存空間 – 堆(Heap)。同時,應用還要能夠將這個堆管理起來,即支持在運行的時候從里面分配一塊空間來存放變量,而在變量的生命周期結束之后,這塊空間需要被回收以待后面的使用。如果堆的大小固定,那么這其實就是一個連續(xù)內存分配問題,同學們可以使用操作系統(tǒng)課上所介紹到的 各種連續(xù)內存分配算法 。

內存碎片

動態(tài)內存分配的弊端---內存碎片 :

應用進行多次不同大小的內存分配和釋放操作后,會產(chǎn)生內存空間的浪費,即存在無法被應用使用的空閑內存碎片。

內存碎片是指無法被分配和使用的空閑內存空間?蛇M一步細分為內碎片和外碎片:

  • 內碎片:已被分配出去(屬于某個在運行的應用)內存區(qū)域,占有這些區(qū)域的應用并不使用這塊區(qū)域,操作系統(tǒng)也無法利用這塊區(qū)域。
  • 外碎片:還沒被分配出去(不屬于任何在運行的應用)內存空閑區(qū)域,由于太小而無法分配給提出申請內存空間的應用。

STD庫中的動態(tài)內存分配

這里 首先提到了在 STD 庫中的堆相關的數(shù)據(jù)結構.可以自行閱讀并且大概理解下圖.

[rCore學習筆記 028] Rust 中的動態(tài)內存分配

但是這一部分向我們傳達的信息是:

  1. rust編程可以很優(yōu)雅地實現(xiàn)動態(tài)內存管理
  2. std庫提供了動態(tài)內存管理的方法
  3. 但是我們的操作系統(tǒng)內核只能使用rust的core庫來實現(xiàn),因此需要重視和借用這些std庫里的方法

在內核中支持動態(tài)內存分配

如上部分所說:

上述與堆相關的智能指針或容器都可以在 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ù)。

具體編程實現(xiàn)

引入已有的內存分配器庫

os/Cargo.toml 中引入:

buddy_system_allocator = "0.6"

引入 alloc

os/src/main.rs 中引入.

// os/src/main.rs

extern crate alloc;

實例化全局動態(tài)內存分配器

創(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 .

標注全局動態(tài)內存分配器的語義項

注意上一段的代碼,要標注 #[global_allocator] 這樣這里的內存分配器才能被識別為全局動態(tài)內存分配器.

#[global_allocator]

處理動態(tài)內存分配失敗的情形

需要 開啟條件編譯 ,所以需要在 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);
}

測試實現(xiàn)效果

創(chuàng)建測試函數(shù)

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 段的代碼:

  1. bss 段本身是一個儲存未初始化的全局變量的內存區(qū)域
  2. 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)));

就很好理解了:

  1. 判斷 a 的值是否為 5
  2. 判斷 a 的指針是否在 bss 的范圍內

隨后的操作則是創(chuàng)建了一個 Vec 容器,然后儲存了 0..500 的值進去,并且分別執(zhí)行上述對 a 的斷言判斷.

如果斷言沒有報錯,那么最后自然會輸出 heap_test passed! .

(最后注意 drop 是在堆(動態(tài)內存)里釋放掉某個變量)

使mm包可調用

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ā)布此文僅為傳遞信息,不代表好特網(wǎng)認同期限觀點或證實其描述。

相關視頻攻略

更多

掃二維碼進入好特網(wǎng)手機版本!

掃二維碼進入好特網(wǎng)微信公眾號!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權,請發(fā)郵件[email protected]

湘ICP備2022002427號-10 湘公網(wǎng)安備:43070202000427號© 2013~2025 haote.com 好特網(wǎng)