rCore的内存管理
本文最后更新于 418 天前,其中的信息可能已经有所发展或是发生改变。

rCore的内存管理

先来一点rust相关。

ref关键字

赋值语句中左边的ref关键字等价于右边的&符号。

let c = 'Q';
let ref ref_c1 = c;
let ref_c2 = &c;

再和mut结合变成可引用变量。

let c = 1;
let ref mut ref_c1 = c;
*c = 2;

iter()vsiter_mut()vsinto_iter()

  • iter():借用迭代器。
  • iter_mut():借用可变迭代器。
  • into_iter():获取迭代器。

rust智能指针/容器以及其他类型的内存布局

image-20240223191657122

rust格式化字符串

使用{:?}{:#?}进行优美地输出,前者是行输出,后者是列输出。

字符串填充方法如下(默认左对齐):

fn main() {
    //-----------------------------------
    // 以下全部输出 "Hello x    !"
    // 为"x"后面填充空格,补齐宽度5
    println!("Hello {:5}!", "x");
    // 使用参数5来指定宽度
    println!("Hello {:1$}!", "x", 5);
    // 使用x作为占位符输出内容,同时使用5作为宽度
    println!("Hello {1:0$}!", 5, "x");
    // 使用有名称的参数作为宽度
    println!("Hello {:width$}!", "x", width = 5);
    //-----------------------------------

    // 使用参数5为参数x指定宽度,同时在结尾输出参数5 => Hello x    !5
    println!("Hello {:1$}!{}", "x", 5);
}

数字填充方法如下(默认右对齐):

fn main() {
    // 宽度是5 => Hello     5!
    println!("Hello {:5}!", 5);
    // 显式的输出正号 => Hello +5!
    println!("Hello {:+}!", 5);
    // 宽度5,使用0进行填充 => Hello 00005!
    println!("Hello {:05}!", 5);
    // 负号也要占用一位宽度 => Hello -0005!
    println!("Hello {:05}!", -5);
}

对齐方法如下:

fn main() {
    // 以下全部都会补齐5个字符的长度
    // 左对齐 => Hello x    !
    println!("Hello {:<5}!", "x");
    // 右对齐 => Hello     x!
    println!("Hello {:>5}!", "x");
    // 居中对齐 => Hello   x  !
    println!("Hello {:^5}!", "x");

    // 对齐并使用指定符号填充 => Hello x&&&&!
    // 指定符号填充的前提条件是必须有对齐字符
    println!("Hello {:&<5}!", "x");
}

进制方法如下:

fn main() {
    // 二进制 => 0b11011!
    println!("{:#b}!", 27);
    // 八进制 => 0o33!
    println!("{:#o}!", 27);
    // 十进制 => 27!
    println!("{}!", 27);
    // 小写十六进制 => 0x1b!
    println!("{:#x}!", 27);
    // 大写十六进制 => 0x1B!
    println!("{:#X}!", 27);

    // 不带前缀的十六进制 => 1b!
    println!("{:x}!", 27);

    // 使用0填充二进制,宽度为10 => 0b00011011!
    println!("{:#010b}!", 27);
}

rust操作符重载

假设我们有一个结构体为Foo,内部有一个变量称之为val: usize。正常情况下我们不可以对Foo进行加一操作,但是我们可以使用core::ops对加号进行重载。具体的实现方法如下:

struct Foo {
      val: usize,
}

impl ops::Add for Foo {
  type Output = Self;

  fn add(self, rhs: usize) -> Self {
    Self { self.val + rhs }
  }
}

堆分配

使用了rCore自己实现的buddy_system_allocator来管理堆空间。使用伙伴系统算法实现,后期可以学习一下源码。这里先直接使用了。当使用rust的只能指针/容器的时候,会自动调用这些堆分配的方法,不用向C语言的malloc以及free函数显式分配。

帧分配

rCore内存管理的关键部分了,方式是维护一个可用内存的空间范围,加上一个回收空间列表。相比之下,xv6直接维护的是一个空闲空间链表,做法会暴力一些。

rCore分配空间的方式是,尽量从回收列表中拿取空间,不能满足的时候,调整可用内存空间的范围,分配一个空闲空间。大多数情况下,请求分配都是小空间,基本能从回收列表中满足。这样实现,不仅分配空间开销小,也不像xv6需要占用大量固定的内存空间。

rcore frame allocator

rCore没有限制Allocator的算法实现方式,任何实现了allocator接口的分配器都可以被rCore使用,利用了面向对象编程的思想。

sfa cls

rCore为每个物理帧使用了TrackerTracker使用了Drop, Deref等派生属性。这样当物理帧的生命周期结束之后,就可以自动地被释放。这种做法借鉴了C++RAII思想。

什么是RAII

RAII(Resourse Acquisition Is Initialization),资源获取即初始化,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。

MemorySet

在学习MemorySet之前,先看看rust如何自己创建迭代器。(因为VPNRange用到了)

  1. 首先,需要声明一个迭代器信息的结构体,记录当前迭代的位置以及结束的位置。为这个结构体实现迭代器的next方法。
  2. 为原来的结构体容器实现一个IntoIterator的方法,将其转换为一个迭代器。

rustiter

MemorySet中包含了两个属性,page_table以及map_areaMapArea又有4个属性,分别是vpn_rangedata_framemap_typemap_perm

page_table中记录了所有页表节点所在的物理也帧。map_area下则记录了每一个分配的连续虚拟内存的信息,包括分配的虚拟内存的范围,分配的帧信息,映射类型(是直接映射,还是非直接映射)以及映射权限(可读可写可执行,是否为用户空间)。

内核和每个用户进程都会有一个MemorySet来记录内存分配信息。

image-20240225195153838

PageTable

PageTable由两个属性构成:root_ppn以及frameroot_ppn记录了跟页表的物理页号,后面也可以直接使用这个值来生成satp的值。frame里存放了为这个页表分配的所有的物理页帧,目的也是为了使用rust的特性对分配的页帧自动资源管理。

MemArea

MemArea由4个属性构成:vpn_rangedata_framemap_type以及map_permvpn_range表示对该连续的虚拟内存空间的范围;data_frame使用了一个是B树数据结构BTreeMap,存储了每个虚拟地址对应的物理页帧。跟页表一样,为了使用rust自动管理资源的特性。map_typemap_perm分别表示该虚拟内存段映射的方式以及权限。

什么是B树?

B树是一个平衡多路查找树,数据库的索引技术里大量使用着B树以及B+树。

在B树中,定义了一个$M$值。定义$D(u)$表示$u$节点的字节点数。B树规定,$D(u) > 1$且$D(u) \le M$,$u$属于非叶子节点。

实际的例子:

image-20240309212510801

在B树的插入和删除中,如果非叶节点的的子节点数不满足上述$M$所需要的要求的时候,就会对节点进行分裂或者是组合。所以,B树一开始的时候并不是树状。

什么是B+树?

B树的非叶子节点是可以保存具体的数据的,这导致了每个数据的检索时间并不相同。B+树的非叶子节点不保存具体的数据,而只保存关键字的索引,从而所有的数据最终都会保存到叶子节点。正因为所有数据都保存到叶子节点,每个数据查询的次数都一样,查询效率稳定。

image-20240309213314761

总结,每一个进程的内存都是由PageTable以及多个连续的虚拟内存空间MemArea构成。

内核地址空间

MemorySet中,实现了一个new_kernel函数,用于建立一个内核的地址空间。

在这里,我们先建立内核低地址段的映射,低地址段的地址空间图入下:

kernel space r

其中,KERNEL_BASE_ADDRESS0x802000000x80000000~0x80200000留给了SBIMEMORY_MAX0x88000000

图中的各个数据段的空间可以从linker脚本中获得,每个段的权限规定如下:

  • 四个逻辑段的 U 标志位均未被设置,使得 CPU 只能在处于 S 特权级(或以上)时访问它们;
  • 代码段 .text 不允许被修改;
  • 只读数据段 .rodata 不允许被修改,也不允许从它上面取指执行;
  • .data/.bss 均允许被读写,但是不允许从它上面取指执行。

参考资料

rCore地址空间

Rust语言圣经

Rust操作符重载

RAII

平衡二叉树、B树、B+树、B*树 理解其中一种你就都明白了

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇