rCore的内存管理
- 4 mins先来一点
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
智能指针/容器以及其他类型的内存布局
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<usize> 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
没有限制Allocator
的算法实现方式,任何实现了allocator
接口的分配器都可以被rCore
使用,利用了面向对象编程的思想。
rCore
为每个物理帧使用了Tracker
。Tracker
使用了Drop, Deref
等派生属性。这样当物理帧的生命周期结束之后,就可以自动地被释放。这种做法借鉴了C++
的RAII
思想。
什么是
RAII
?
RAII
(Resourse Acquisition Is Initialization),资源获取即初始化,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
MemorySet
在学习
MemorySet
之前,先看看rust
如何自己创建迭代器。(因为VPNRange
用到了)
- 首先,需要声明一个迭代器信息的结构体,记录当前迭代的位置以及结束的位置。为这个结构体实现迭代器的
next
方法。- 为原来的结构体容器实现一个
IntoIterator
的方法,将其转换为一个迭代器。
MemorySet
中包含了两个属性,page_table
以及map_area
。MapArea
又有4个属性,分别是vpn_range
,data_frame
,map_type
和map_perm
。
在page_table
中记录了所有页表节点所在的物理也帧。map_area
下则记录了每一个分配的连续虚拟内存的信息,包括分配的虚拟内存的范围,分配的帧信息,映射类型(是直接映射,还是非直接映射)以及映射权限(可读可写可执行,是否为用户空间)。
内核和每个用户进程都会有一个MemorySet
来记录内存分配信息。