首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

[rCore学习笔记 028] Rust 中的动态内存分配

编程知识
2024年10月01日 12:37

引言

想起我们之前在学习C的时候,总是提到malloc,总是提起,使用malloc现场申请的内存是属于,而直接定义的变量内存属于.

还记得当初学习STM32的时候CubeIDE要设置stack heap的大小.

但是我们要记得,这么好用的功能,实际上是操作系统在负重前行.

那么为了实现动态内存分配功能,操作系统需要有如下功能:

  • 初始时能提供一块大内存空间作为初始的“堆”。在没有分页机制情况下,这块空间是物理内存空间,否则就是虚拟内存空间。
  • 提供在堆上分配和释放内存的函数接口。这样函数调用方通过分配内存函数接口得到地址连续的空闲内存块进行读写,也能通过释放内存函数接口回收内存,以备后续的内存分配请求。
  • 提供空闲空间管理的连续内存分配算法。相关算法能动态地维护一系列空闲和已分配的内存块,从而有效地管理空闲块。
  • (可选)提供建立在堆上的数据结构和操作。有了上述基本的内存分配与释放

动态内存分配

实现方法

动态内存分配的实现方法:

应用另外放置了一个大小可以随着应用的运行动态增减的内存空间 – 堆(Heap)。同时,应用还要能够将这个堆管理起来,即支持在运行的时候从里面分配一块空间来存放变量,而在变量的生命周期结束之后,这块空间需要被回收以待后面的使用。如果堆的大小固定,那么这其实就是一个连续内存分配问题,同学们可以使用操作系统课上所介绍到的各种连续内存分配算法

内存碎片

动态内存分配的弊端---内存碎片:

应用进行多次不同大小的内存分配和释放操作后,会产生内存空间的浪费,即存在无法被应用使用的空闲内存碎片。

内存碎片是指无法被分配和使用的空闲内存空间。可进一步细分为内碎片和外碎片:

  • 内碎片:已被分配出去(属于某个在运行的应用)内存区域,占有这些区域的应用并不使用这块区域,操作系统也无法利用这块区域。
  • 外碎片:还没被分配出去(不属于任何在运行的应用)内存空闲区域,由于太小而无法分配给提出申请内存空间的应用。

STD库中的动态内存分配

这里首先提到了在STD库中的堆相关的数据结构.可以自行阅读并且大概理解下图.

但是这一部分向我们传达的信息是:

  1. rust编程可以很优雅地实现动态内存管理
  2. std库提供了动态内存管理的方法
  3. 但是我们的操作系统内核只能使用rust的core库来实现,因此需要重视和借用这些std库里的方法

在内核中支持动态内存分配

如上部分所说:

上述与堆相关的智能指针或容器都可以在 Rust 自带的 alloc crate 中找到。当我们使用 Rust 标准库 std 的时候可以不用关心这个 crate ,因为标准库内已经已经实现了一套堆管理算法,并将 alloc 的内容包含在 std 名字空间之下让开发者可以直接使用。然而操作系统内核运行在禁用标准库(即 no_std )的裸机平台上,核心库 core 也并没有动态内存分配的功能,这个时候就要考虑利用 alloc 库定义的接口来实现基本的动态内存分配器。

具体实现这个动态内存分配器,是为自己实现的这个结构体,实现GlobalAllocTrait.

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<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!");
}

这里的#[allow(unused)]很有意思,可以阻止编译器对你因为调试暂时不调用的函数报错.

这里注意使用了println,在文件最上边加一句use crate::println.

这里的测试程序先获取了sbss的到ebss的范围.

这里回顾清零bss段的代码:

  1. bss段本身是一个储存未初始化的全局变量的内存区域
  2. sbssbss的开头ebssbss的结尾

那么bss_range实际上是bss的范围.

根据这里,理解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的范围内

随后的操作则是创建了一个Vec容器,然后储存了0..500的值进去,并且分别执行上述对a的断言判断.

如果断言没有报错,那么最后自然会输出heap_test passed!.

(最后注意drop是在堆(动态内存)里释放掉某个变量)

使mm包可调用

os/src/mm下创建mod.rs使得mm可以被识别为一个包.

为了使用heap_allocator里的init_heapheap_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!,说明测试成功了.

From:https://www.cnblogs.com/chenhan-winddevil/p/18442833
本文地址: http://shuzixingkong.net/article/2434
0评论
提交 加载更多评论
其他文章 掌握Docker:简化KES单机安装与管理的最佳实践
今天我们将继续深入探讨KES的单机安装,依然围绕Docker的使用展开。这一部分的内容将涵盖一些常见的陷阱以及在遇到问题时如何进行有效的反馈和解决。首先,我们需要找到官方的安装教程,确保以官方指南为主,同时结合我们自己的使用习惯。 为什么我们如此青睐Docker,而不是选择传统的命令行安装呢?在当今
掌握Docker:简化KES单机安装与管理的最佳实践 掌握Docker:简化KES单机安装与管理的最佳实践 掌握Docker:简化KES单机安装与管理的最佳实践
七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)
七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用) @目录七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)1. 乐观锁2. 代码生成器3. 执行SQL分析打印4. 总结:5. 最后: 1. 乐观锁 首先我们需要
七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用) 七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用) 七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)
[python] 基于PyOD库实现数据异常检测
PyOD是一个全面且易于使用的Python库,专门用于检测多变量数据中的异常点或离群点。异常点是指那些与大多数数据点显著不同的数据,它们可能表示错误、噪声或潜在的有趣现象。无论是处理小规模项目还是大型数据集,PyOD提供了50多种算法以满足用户的需求。PyOD的特点包括: 统一且用户友好的接口,适用
[python] 基于PyOD库实现数据异常检测 [python] 基于PyOD库实现数据异常检测 [python] 基于PyOD库实现数据异常检测
Windows应急响应-灰鸽子远控木马
目录应急背景木马查杀1.查看异常连接2.根据端口号查看对应进程文件3.排查异常服务4.发现启动项开始查杀入侵排查1.账号排查2.查看服务3.查看启动项4.查看计划任务5.网络情况6.进程排查重启再排查一遍 应急背景 历某今天刚入职公司,拿到公司电脑后准备下载一些接下来工作中要用的办公软件,他就去某度
Windows应急响应-灰鸽子远控木马 Windows应急响应-灰鸽子远控木马 Windows应急响应-灰鸽子远控木马
论文解读《MASTERKEY: Automated Jailbreaking of Large Language Model Chatbots》
导言 ​ 在参加东南大学网络安全学院夏令营的契机下,我第一次接触大模型安全领域。L老师是网络安全领域的一位大牛,在和L老师交流期间,被告知需要准备一次paper presentation介绍四大会中感兴趣的一篇文章,我选择了汇报这篇来自NDSS2024的《MASTERKEY: Automated J
论文解读《MASTERKEY: Automated Jailbreaking of Large Language Model Chatbots》 论文解读《MASTERKEY: Automated Jailbreaking of Large Language Model Chatbots》 论文解读《MASTERKEY: Automated Jailbreaking of Large Language Model Chatbots》
【防忘笔记】测试过程与技术
测试人员应该想些什么 我自己是做后端的,对于模棱两可的需求和莫名其妙的测试case是深恶痛绝的,所以有时候我就会想测试人员应该会需要注意什么?以他们的角度,他们更在乎什么 最近有机会了解相关的知识,遂整理记录一下,以便之后在工作中更好的理解发生的各种事情 以客户为中心 这个真的很重要,以至于可以直接
【防忘笔记】测试过程与技术 【防忘笔记】测试过程与技术 【防忘笔记】测试过程与技术
manim边学边做--形状匹配
manim中有几个特殊的用于形状匹配的对象,它们的作用是标记和注释已有的对象,本身一般不单独使用。 形状匹配对象一共有4种: BackgroundRectangle:为已有的对象提供一个矩形的背景 Cross:用交叉线标记已有对象 SurroundingRectangle:用矩形框围住某个对象 Un
manim边学边做--形状匹配 manim边学边做--形状匹配 manim边学边做--形状匹配
将ASD光谱仪的.asd文件转为文本文件
本文介绍基于ViewSpec Pro软件,将ASD地物光谱仪获取到的.asd格式文件,批量转换为通用的.txt文本格式文件的方法~
将ASD光谱仪的.asd文件转为文本文件 将ASD光谱仪的.asd文件转为文本文件 将ASD光谱仪的.asd文件转为文本文件