Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Miykael_xxm
RCore Tutorial Book V3
提交
e5afbae8
R
RCore Tutorial Book V3
项目概览
Miykael_xxm
/
RCore Tutorial Book V3
与 Fork 源项目一致
Fork自
rcore-os / RCore Tutorial Book V3
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
RCore Tutorial Book V3
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
e5afbae8
编写于
1月 20, 2021
作者:
Y
Yifan Wu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Chapter4.4 50% completed.
上级
2f9f26ff
变更
8
展开全部
隐藏空白更改
内联
并排
Showing
8 changed file
with
542 addition
and
13 deletion
+542
-13
docs/_sources/chapter4/2rust-dynamic-allocation.rst.txt
docs/_sources/chapter4/2rust-dynamic-allocation.rst.txt
+2
-0
docs/_sources/chapter4/4sv39-implementation-2.rst.txt
docs/_sources/chapter4/4sv39-implementation-2.rst.txt
+179
-3
docs/chapter4/2rust-dynamic-allocation.html
docs/chapter4/2rust-dynamic-allocation.html
+3
-3
docs/chapter4/4sv39-implementation-2.html
docs/chapter4/4sv39-implementation-2.html
+176
-3
docs/objects.inv
docs/objects.inv
+0
-0
docs/searchindex.js
docs/searchindex.js
+1
-1
source/chapter4/2rust-dynamic-allocation.rst
source/chapter4/2rust-dynamic-allocation.rst
+2
-0
source/chapter4/4sv39-implementation-2.rst
source/chapter4/4sv39-implementation-2.rst
+179
-3
未找到文件。
docs/_sources/chapter4/2rust-dynamic-allocation.rst.txt
浏览文件 @
e5afbae8
...
@@ -56,6 +56,8 @@ Rust 中的动态内存分配
...
@@ -56,6 +56,8 @@ Rust 中的动态内存分配
而动态内存分配的缺点在于:它背后运行着连续内存分配算法,它相比静态分配会带来一些额外的开销。如果动态分配非常频繁,
而动态内存分配的缺点在于:它背后运行着连续内存分配算法,它相比静态分配会带来一些额外的开销。如果动态分配非常频繁,
它甚至可能会成为应用的性能瓶颈。
它甚至可能会成为应用的性能瓶颈。
.. _rust-heap-data-structures:
Rust 中的堆数据结构
Rust 中的堆数据结构
------------------------------------------------
------------------------------------------------
...
...
docs/_sources/chapter4/4sv39-implementation-2.rst.txt
浏览文件 @
e5afbae8
...
@@ -120,11 +120,12 @@
...
@@ -120,11 +120,12 @@
- 在分配 ``alloc`` 的时候,首先会检查栈 ``recycled`` 内有没有之前回收的物理页号,如果有的话直接弹出栈顶并返回;
- 在分配 ``alloc`` 的时候,首先会检查栈 ``recycled`` 内有没有之前回收的物理页号,如果有的话直接弹出栈顶并返回;
否则的话我们只能从之前从未分配过的物理页号区间 :math:`[\text{current},\text{end})` 上进行分配,我们分配它的
否则的话我们只能从之前从未分配过的物理页号区间 :math:`[\text{current},\text{end})` 上进行分配,我们分配它的
左端点 ``current`` ,同时将管理器内部维护的 ``current`` 加一代表 ``current`` 此前已经被分配过了。
左端点 ``current`` ,同时将管理器内部维护的 ``current`` 加一代表 ``current`` 此前已经被分配过了。在即将返回
的时候,我们使用 ``into`` 方法将 usize 转换成了物理页号 ``PhysPageNum`` 。
注意极端情况下可能出现内存耗尽分配失败的情况:即 ``recycled`` 为空且 :math:`\text{current}==\text{end}` 。
注意极端情况下可能出现内存耗尽分配失败的情况:即 ``recycled`` 为空且 :math:`\text{current}==\text{end}` 。
为了涵盖这种情况, ``alloc`` 的返回值被 ``Option`` 包裹,我们返回 ``None`` 即可。
为了涵盖这种情况, ``alloc`` 的返回值被 ``Option`` 包裹,我们返回 ``None`` 即可。
-
再
回收 ``dealloc`` 的时候,我们需要检查回收页面的合法性,然后将其压入 ``recycled`` 栈中。回收页面合法有两个
-
在
回收 ``dealloc`` 的时候,我们需要检查回收页面的合法性,然后将其压入 ``recycled`` 栈中。回收页面合法有两个
条件:
条件:
- 该页面之前一定被分配出去过,因此它的物理页号一定 :math:`<\text{current}` ;
- 该页面之前一定被分配出去过,因此它的物理页号一定 :math:`<\text{current}` ;
...
@@ -134,8 +135,183 @@
...
@@ -134,8 +135,183 @@
寻找一个与输入物理页号相同的元素。其返回值是一个 ``Option`` ,如果找到了就会是一个 ``Option::Some`` ,
寻找一个与输入物理页号相同的元素。其返回值是一个 ``Option`` ,如果找到了就会是一个 ``Option::Some`` ,
这种情况说明我们内核其他部分实现有误,直接报错退出。
这种情况说明我们内核其他部分实现有误,直接报错退出。
下面我们来创建 ``StackFrameAllocator`` 的全局实例:
下面我们来创建 ``StackFrameAllocator`` 的全局实例
``FRAME_ALLOCATOR``
:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
use spin::Mutex;
type FrameAllocatorImpl = StackFrameAllocator;
lazy_static! {
pub static ref FRAME_ALLOCATOR: Mutex<FrameAllocatorImpl> =
Mutex::new(FrameAllocatorImpl::new());
}
这里我们使用互斥锁 ``Mutex<T>`` 来包裹栈式物理页帧分配器。每次对该分配器进行操作之前,我们都需要先通过
``FRAME_ALLOCATOR.lock()`` 拿到分配器的可变借用。注意 ``alloc`` 中并没有提供 ``Mutex<T>`` ,它
来自于一个我们在 ``no_std`` 的裸机环境下经常使用的名为 ``spin`` 的 crate ,它仅依赖 Rust 核心库
``core`` 提供一些可跨平台使用的同步原语,如互斥锁 ``Mutex<T>`` 和读写锁 ``RwLock<T>`` 等。
.. note::
**Rust 语法卡片:在单核环境下使用 Mutex<T> 的原因**
在编写一个多线程的应用时,加锁的目的是为了避免数据竞争,使得里层的共享数据结构同一时间只有一个线程
在对它进行访问。然而,目前我们的内核运行在单 CPU 上,且 Trap 进入内核之后并没有手动打开中断,这也就
使得同一时间最多只有一条 Trap 执行流并发访问内核的各数据结构,此时应该是并没有任何数据竞争风险的。那么
加锁的原因其实有两点:
1. 在不触及 ``unsafe`` 的情况下实现 ``static mut`` 语义。如果读者还有印象,
:ref:`前面章节 <term-interior-mutability>` 我们使用 ``RefCell<T>`` 提供了内部可变性去掉了
声明中的 ``mut`` ,然而麻烦的在于 ``static`` ,在 Rust 中一个类型想被实例化为一个全局变量,则
该类型必须先告知编译器自己某种意义上是线程安全的,这个过程本身是 ``unsafe`` 的。
因此我们直接使用 ``Mutex<T>`` ,它既通过 ``lock`` 方法提供了内部可变性,又已经在模块内部告知了
编译器它的线程安全性。这样 ``unsafe`` 就被隐藏在了 ``spin`` crate 之内,我们无需关心。这种风格
是 Rust 所推荐的。
2. 方便后续拓展到真正存在数据竞争风险的多核环境下运行。
这里引入了一些新概念,比如什么是线程,又如何定义线程安全?读者可以先不必深究,暂时有一个初步的概念即可。
我们需要添加该 crate 的依赖:
.. code-block:: toml
# os/Cargo.toml
[dependencies]
spin = "0.7.0"
在正式分配物理页帧之前,我们需要将物理页帧全局管理器 ``FRAME_ALLOCATOR`` 初始化:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
pub fn init_frame_allocator() {
extern "C" {
fn ekernel();
}
FRAME_ALLOCATOR
.lock()
.init(PhysAddr::from(ekernel as usize).ceil(), PhysAddr::from(MEMORY_END).floor());
}
这里我们调用物理地址 ``PhysAddr`` 的 ``floor/ceil`` 方法分别下/上取整获得可用的物理页号区间。
然后是真正公开给其他子模块调用的分配/回收物理页帧的接口:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
pub fn frame_alloc() -> Option<FrameTracker> {
FRAME_ALLOCATOR
.lock()
.alloc()
.map(|ppn| FrameTracker::new(ppn))
}
fn frame_dealloc(ppn: PhysPageNum) {
FRAME_ALLOCATOR
.lock()
.dealloc(ppn);
}
可以发现, ``frame_alloc`` 的返回值类型并不是 ``FrameAllocator`` 要求的物理页号 ``PhysPageNum`` ,而是将其
进一步包装为一个 ``FrameTracker`` 。这里借用了 RAII 的思想,将一个物理页帧的生命周期绑定到一个 ``FrameTracker``
变量上,当一个 ``FrameTracker`` 被创建的时候,我们需要从 ``FRAME_ALLOCATOR`` 中分配一个物理页帧:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
pub struct FrameTracker {
pub ppn: PhysPageNum,
}
impl FrameTracker {
pub fn new(ppn: PhysPageNum) -> Self {
// page cleaning
let bytes_array = ppn.get_bytes_array();
for i in bytes_array {
*i = 0;
}
Self { ppn }
}
}
我们将分配来的物理页帧的物理页号作为参数传给 ``FrameTracker`` 的 ``new`` 方法来创建一个 ``FrameTracker``
实例。由于这个物理页帧之前可能被分配过并用做其他用途,我们在这里直接将这个物理页帧上的所有字节清零。这一过程并不
那么显然,我们后面再详细介绍。
当一个 ``FrameTracker`` 生命周期结束被编译器回收的时候,我们需要将它控制的物理页帧回收掉 ``FRAME_ALLOCATOR`` 中:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
impl Drop for FrameTracker {
fn drop(&mut self) {
frame_dealloc(self.ppn);
}
}
这里我们只需为 ``FrameTracker`` 实现 ``Drop`` Trait 即可。当一个 ``FrameTracker`` 实例被回收的时候,它的
``drop`` 方法会自动被编译器调用,通过之前实现的 ``frame_dealloc`` 我们就将它控制的物理页帧回收以供后续使用了。
.. note::
**Rust 语法卡片:Drop Trait**
Rust 中的 ``Drop`` Trait 是它的 RAII 内存管理风格可以被有效实践的关键。之前介绍的多种在堆上分配的 Rust
数据结构便都是通过实现 ``Drop`` Trait 来进行被绑定资源的自动回收的。例如:
- ``Box<T>`` 的 ``drop`` 方法会回收它控制的分配在堆上的那个变量;
- ``Rc<T>`` 的 ``drop`` 方法会减少分配在堆上的那个引用计数,一旦变为零则分配在堆上的那个被计数的变量自身
也会被回收;
- ``Mutex<T>`` 的 ``lock`` 方法会获取锁并返回一个 ``MutexGuard<'a, T>`` ,它可以被当做一个 ``&mut T``
来使用;而 ``MutexGuard<'a, T>`` 的 ``drop`` 方法会将锁释放,从而允许其他线程获取锁并开始访问里层的
数据结构。锁的实现原理我们先不介绍。
``FrameTracker`` 的设计也是基于同样的思想,有了它之后我们就不必手动回收物理页帧了,这在编译期就解决了很多
潜在的问题。
最后做一个小结:从其他模块的视角看来,物理页帧分配的接口是调用 ``frame_alloc`` 函数得到一个 ``FrameTracker``
(如果物理内存还有剩余),它就代表了一个物理页帧,当它的生命周期结束之后它所控制的物理页帧将被自动回收。下面是
一段演示该接口使用方法的测试程序:
.. code-block:: rust
:linenos:
:emphasize-lines: 9
// os/src/mm/frame_allocator.rs
#[allow(unused)]
pub fn frame_allocator_test() {
let mut v: Vec<FrameTracker> = Vec::new();
for i in 0..5 {
let frame = frame_alloc().unwrap();
println!("{:?}", frame);
v.push(frame);
}
v.clear();
for i in 0..5 {
let frame = frame_alloc().unwrap();
println!("{:?}", frame);
v.push(frame);
}
drop(v);
println!("frame_allocator_test passed!");
}
如果我们将第 9 行删去,则第一轮分配的 5 个物理页帧都是分配之后在循环末尾就被立即回收,因为循环作用域的临时变量
``frame`` 的生命周期在那时结束了。然而,如果我们将它们 move 到一个向量中,它们的生命周期便被延长了——直到第 11 行
向量被清空的时候,这些 ``FrameTracker`` 的生命周期才结束,它们控制的 5 个物理页帧才被回收。这种思想我们立即
就会用到。
多级页表实现
多级页表实现
-----------------------------------
-----------------------------------
\ No newline at end of file
docs/chapter4/2rust-dynamic-allocation.html
浏览文件 @
e5afbae8
...
@@ -128,7 +128,7 @@ commentsRunWhenDOMLoaded(addUtterances);
...
@@ -128,7 +128,7 @@ commentsRunWhenDOMLoaded(addUtterances);
<li
class=
"toctree-l2"
><a
class=
"reference internal"
href=
"1address-space.html"
>
地址空间
</a></li>
<li
class=
"toctree-l2"
><a
class=
"reference internal"
href=
"1address-space.html"
>
地址空间
</a></li>
<li
class=
"toctree-l2 current"
><a
class=
"current reference internal"
href=
"#"
>
Rust 中的动态内存分配
</a><ul>
<li
class=
"toctree-l2 current"
><a
class=
"current reference internal"
href=
"#"
>
Rust 中的动态内存分配
</a><ul>
<li
class=
"toctree-l3"
><a
class=
"reference internal"
href=
"#id1"
>
静态与动态内存分配
</a></li>
<li
class=
"toctree-l3"
><a
class=
"reference internal"
href=
"#id1"
>
静态与动态内存分配
</a></li>
<li
class=
"toctree-l3"
><a
class=
"reference internal"
href=
"#
id2
"
>
Rust 中的堆数据结构
</a></li>
<li
class=
"toctree-l3"
><a
class=
"reference internal"
href=
"#
rust-heap-data-structures
"
>
Rust 中的堆数据结构
</a></li>
<li
class=
"toctree-l3"
><a
class=
"reference internal"
href=
"#id4"
>
在内核中支持动态内存分配
</a></li>
<li
class=
"toctree-l3"
><a
class=
"reference internal"
href=
"#id4"
>
在内核中支持动态内存分配
</a></li>
</ul>
</ul>
</li>
</li>
...
@@ -267,8 +267,8 @@ commentsRunWhenDOMLoaded(addUtterances);
...
@@ -267,8 +267,8 @@ commentsRunWhenDOMLoaded(addUtterances);
<p>
而动态内存分配的缺点在于:它背后运行着连续内存分配算法,它相比静态分配会带来一些额外的开销。如果动态分配非常频繁,
<p>
而动态内存分配的缺点在于:它背后运行着连续内存分配算法,它相比静态分配会带来一些额外的开销。如果动态分配非常频繁,
它甚至可能会成为应用的性能瓶颈。
</p>
它甚至可能会成为应用的性能瓶颈。
</p>
</div>
</div>
<div
class=
"section"
id=
"
id2
"
>
<div
class=
"section"
id=
"
rust-heap-data-structures
"
>
<
h2>
Rust 中的堆数据结构
<a
class=
"headerlink"
href=
"#id2
"
title=
"永久链接至标题"
>
¶
</a></h2>
<
span
id=
"id2"
></span><h2>
Rust 中的堆数据结构
<a
class=
"headerlink"
href=
"#rust-heap-data-structures
"
title=
"永久链接至标题"
>
¶
</a></h2>
<p>
Rust 的标准库中提供了很多开箱即用的堆数据结构,利用它们能够大大提升我们的开发效率。
</p>
<p>
Rust 的标准库中提供了很多开箱即用的堆数据结构,利用它们能够大大提升我们的开发效率。
</p>
<p
id=
"term-smart-pointer"
>
首先是一类
<strong>
智能指针
</strong>
(Smart Pointer) 。智能指针和 Rust 中的其他两类指针也即裸指针
<code
class=
"docutils literal notranslate"
><span
class=
"pre"
>
*const
</span>
<span
class=
"pre"
>
T/*mut
</span>
<span
class=
"pre"
>
T
</span></code>
<p
id=
"term-smart-pointer"
>
首先是一类
<strong>
智能指针
</strong>
(Smart Pointer) 。智能指针和 Rust 中的其他两类指针也即裸指针
<code
class=
"docutils literal notranslate"
><span
class=
"pre"
>
*const
</span>
<span
class=
"pre"
>
T/*mut
</span>
<span
class=
"pre"
>
T
</span></code>
以及引用
<code
class=
"docutils literal notranslate"
><span
class=
"pre"
>
&
T/
&
mut
</span>
<span
class=
"pre"
>
T
</span></code>
一样,都指向地址空间中的另一个区域并包含它的位置信息。但不同在于,它们携带的信息数量不等,
以及引用
<code
class=
"docutils literal notranslate"
><span
class=
"pre"
>
&
T/
&
mut
</span>
<span
class=
"pre"
>
T
</span></code>
一样,都指向地址空间中的另一个区域并包含它的位置信息。但不同在于,它们携带的信息数量不等,
...
...
docs/chapter4/4sv39-implementation-2.html
浏览文件 @
e5afbae8
此差异已折叠。
点击以展开。
docs/objects.inv
浏览文件 @
e5afbae8
无法预览此类型文件
docs/searchindex.js
浏览文件 @
e5afbae8
因为 它太大了无法显示 source diff 。你可以改为
查看blob
。
source/chapter4/2rust-dynamic-allocation.rst
浏览文件 @
e5afbae8
...
@@ -56,6 +56,8 @@ Rust 中的动态内存分配
...
@@ -56,6 +56,8 @@ Rust 中的动态内存分配
而动态内存分配的缺点在于:它背后运行着连续内存分配算法,它相比静态分配会带来一些额外的开销。如果动态分配非常频繁,
而动态内存分配的缺点在于:它背后运行着连续内存分配算法,它相比静态分配会带来一些额外的开销。如果动态分配非常频繁,
它甚至可能会成为应用的性能瓶颈。
它甚至可能会成为应用的性能瓶颈。
.. _rust-heap-data-structures:
Rust 中的堆数据结构
Rust 中的堆数据结构
------------------------------------------------
------------------------------------------------
...
...
source/chapter4/4sv39-implementation-2.rst
浏览文件 @
e5afbae8
...
@@ -120,11 +120,12 @@
...
@@ -120,11 +120,12 @@
- 在分配 ``alloc`` 的时候,首先会检查栈 ``recycled`` 内有没有之前回收的物理页号,如果有的话直接弹出栈顶并返回;
- 在分配 ``alloc`` 的时候,首先会检查栈 ``recycled`` 内有没有之前回收的物理页号,如果有的话直接弹出栈顶并返回;
否则的话我们只能从之前从未分配过的物理页号区间 :math:`[\text{current},\text{end})` 上进行分配,我们分配它的
否则的话我们只能从之前从未分配过的物理页号区间 :math:`[\text{current},\text{end})` 上进行分配,我们分配它的
左端点 ``current`` ,同时将管理器内部维护的 ``current`` 加一代表 ``current`` 此前已经被分配过了。
左端点 ``current`` ,同时将管理器内部维护的 ``current`` 加一代表 ``current`` 此前已经被分配过了。在即将返回
的时候,我们使用 ``into`` 方法将 usize 转换成了物理页号 ``PhysPageNum`` 。
注意极端情况下可能出现内存耗尽分配失败的情况:即 ``recycled`` 为空且 :math:`\text{current}==\text{end}` 。
注意极端情况下可能出现内存耗尽分配失败的情况:即 ``recycled`` 为空且 :math:`\text{current}==\text{end}` 。
为了涵盖这种情况, ``alloc`` 的返回值被 ``Option`` 包裹,我们返回 ``None`` 即可。
为了涵盖这种情况, ``alloc`` 的返回值被 ``Option`` 包裹,我们返回 ``None`` 即可。
-
再
回收 ``dealloc`` 的时候,我们需要检查回收页面的合法性,然后将其压入 ``recycled`` 栈中。回收页面合法有两个
-
在
回收 ``dealloc`` 的时候,我们需要检查回收页面的合法性,然后将其压入 ``recycled`` 栈中。回收页面合法有两个
条件:
条件:
- 该页面之前一定被分配出去过,因此它的物理页号一定 :math:`<\text{current}` ;
- 该页面之前一定被分配出去过,因此它的物理页号一定 :math:`<\text{current}` ;
...
@@ -134,8 +135,183 @@
...
@@ -134,8 +135,183 @@
寻找一个与输入物理页号相同的元素。其返回值是一个 ``Option`` ,如果找到了就会是一个 ``Option::Some`` ,
寻找一个与输入物理页号相同的元素。其返回值是一个 ``Option`` ,如果找到了就会是一个 ``Option::Some`` ,
这种情况说明我们内核其他部分实现有误,直接报错退出。
这种情况说明我们内核其他部分实现有误,直接报错退出。
下面我们来创建 ``StackFrameAllocator`` 的全局实例:
下面我们来创建 ``StackFrameAllocator`` 的全局实例
``FRAME_ALLOCATOR``
:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
use spin::Mutex;
type FrameAllocatorImpl = StackFrameAllocator;
lazy_static! {
pub static ref FRAME_ALLOCATOR: Mutex<FrameAllocatorImpl> =
Mutex::new(FrameAllocatorImpl::new());
}
这里我们使用互斥锁 ``Mutex<T>`` 来包裹栈式物理页帧分配器。每次对该分配器进行操作之前,我们都需要先通过
``FRAME_ALLOCATOR.lock()`` 拿到分配器的可变借用。注意 ``alloc`` 中并没有提供 ``Mutex<T>`` ,它
来自于一个我们在 ``no_std`` 的裸机环境下经常使用的名为 ``spin`` 的 crate ,它仅依赖 Rust 核心库
``core`` 提供一些可跨平台使用的同步原语,如互斥锁 ``Mutex<T>`` 和读写锁 ``RwLock<T>`` 等。
.. note::
**Rust 语法卡片:在单核环境下使用 Mutex<T> 的原因**
在编写一个多线程的应用时,加锁的目的是为了避免数据竞争,使得里层的共享数据结构同一时间只有一个线程
在对它进行访问。然而,目前我们的内核运行在单 CPU 上,且 Trap 进入内核之后并没有手动打开中断,这也就
使得同一时间最多只有一条 Trap 执行流并发访问内核的各数据结构,此时应该是并没有任何数据竞争风险的。那么
加锁的原因其实有两点:
1. 在不触及 ``unsafe`` 的情况下实现 ``static mut`` 语义。如果读者还有印象,
:ref:`前面章节 <term-interior-mutability>` 我们使用 ``RefCell<T>`` 提供了内部可变性去掉了
声明中的 ``mut`` ,然而麻烦的在于 ``static`` ,在 Rust 中一个类型想被实例化为一个全局变量,则
该类型必须先告知编译器自己某种意义上是线程安全的,这个过程本身是 ``unsafe`` 的。
因此我们直接使用 ``Mutex<T>`` ,它既通过 ``lock`` 方法提供了内部可变性,又已经在模块内部告知了
编译器它的线程安全性。这样 ``unsafe`` 就被隐藏在了 ``spin`` crate 之内,我们无需关心。这种风格
是 Rust 所推荐的。
2. 方便后续拓展到真正存在数据竞争风险的多核环境下运行。
这里引入了一些新概念,比如什么是线程,又如何定义线程安全?读者可以先不必深究,暂时有一个初步的概念即可。
我们需要添加该 crate 的依赖:
.. code-block:: toml
# os/Cargo.toml
[dependencies]
spin = "0.7.0"
在正式分配物理页帧之前,我们需要将物理页帧全局管理器 ``FRAME_ALLOCATOR`` 初始化:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
pub fn init_frame_allocator() {
extern "C" {
fn ekernel();
}
FRAME_ALLOCATOR
.lock()
.init(PhysAddr::from(ekernel as usize).ceil(), PhysAddr::from(MEMORY_END).floor());
}
这里我们调用物理地址 ``PhysAddr`` 的 ``floor/ceil`` 方法分别下/上取整获得可用的物理页号区间。
然后是真正公开给其他子模块调用的分配/回收物理页帧的接口:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
pub fn frame_alloc() -> Option<FrameTracker> {
FRAME_ALLOCATOR
.lock()
.alloc()
.map(|ppn| FrameTracker::new(ppn))
}
fn frame_dealloc(ppn: PhysPageNum) {
FRAME_ALLOCATOR
.lock()
.dealloc(ppn);
}
可以发现, ``frame_alloc`` 的返回值类型并不是 ``FrameAllocator`` 要求的物理页号 ``PhysPageNum`` ,而是将其
进一步包装为一个 ``FrameTracker`` 。这里借用了 RAII 的思想,将一个物理页帧的生命周期绑定到一个 ``FrameTracker``
变量上,当一个 ``FrameTracker`` 被创建的时候,我们需要从 ``FRAME_ALLOCATOR`` 中分配一个物理页帧:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
pub struct FrameTracker {
pub ppn: PhysPageNum,
}
impl FrameTracker {
pub fn new(ppn: PhysPageNum) -> Self {
// page cleaning
let bytes_array = ppn.get_bytes_array();
for i in bytes_array {
*i = 0;
}
Self { ppn }
}
}
我们将分配来的物理页帧的物理页号作为参数传给 ``FrameTracker`` 的 ``new`` 方法来创建一个 ``FrameTracker``
实例。由于这个物理页帧之前可能被分配过并用做其他用途,我们在这里直接将这个物理页帧上的所有字节清零。这一过程并不
那么显然,我们后面再详细介绍。
当一个 ``FrameTracker`` 生命周期结束被编译器回收的时候,我们需要将它控制的物理页帧回收掉 ``FRAME_ALLOCATOR`` 中:
.. code-block:: rust
// os/src/mm/frame_allocator.rs
impl Drop for FrameTracker {
fn drop(&mut self) {
frame_dealloc(self.ppn);
}
}
这里我们只需为 ``FrameTracker`` 实现 ``Drop`` Trait 即可。当一个 ``FrameTracker`` 实例被回收的时候,它的
``drop`` 方法会自动被编译器调用,通过之前实现的 ``frame_dealloc`` 我们就将它控制的物理页帧回收以供后续使用了。
.. note::
**Rust 语法卡片:Drop Trait**
Rust 中的 ``Drop`` Trait 是它的 RAII 内存管理风格可以被有效实践的关键。之前介绍的多种在堆上分配的 Rust
数据结构便都是通过实现 ``Drop`` Trait 来进行被绑定资源的自动回收的。例如:
- ``Box<T>`` 的 ``drop`` 方法会回收它控制的分配在堆上的那个变量;
- ``Rc<T>`` 的 ``drop`` 方法会减少分配在堆上的那个引用计数,一旦变为零则分配在堆上的那个被计数的变量自身
也会被回收;
- ``Mutex<T>`` 的 ``lock`` 方法会获取锁并返回一个 ``MutexGuard<'a, T>`` ,它可以被当做一个 ``&mut T``
来使用;而 ``MutexGuard<'a, T>`` 的 ``drop`` 方法会将锁释放,从而允许其他线程获取锁并开始访问里层的
数据结构。锁的实现原理我们先不介绍。
``FrameTracker`` 的设计也是基于同样的思想,有了它之后我们就不必手动回收物理页帧了,这在编译期就解决了很多
潜在的问题。
最后做一个小结:从其他模块的视角看来,物理页帧分配的接口是调用 ``frame_alloc`` 函数得到一个 ``FrameTracker``
(如果物理内存还有剩余),它就代表了一个物理页帧,当它的生命周期结束之后它所控制的物理页帧将被自动回收。下面是
一段演示该接口使用方法的测试程序:
.. code-block:: rust
:linenos:
:emphasize-lines: 9
// os/src/mm/frame_allocator.rs
#[allow(unused)]
pub fn frame_allocator_test() {
let mut v: Vec<FrameTracker> = Vec::new();
for i in 0..5 {
let frame = frame_alloc().unwrap();
println!("{:?}", frame);
v.push(frame);
}
v.clear();
for i in 0..5 {
let frame = frame_alloc().unwrap();
println!("{:?}", frame);
v.push(frame);
}
drop(v);
println!("frame_allocator_test passed!");
}
如果我们将第 9 行删去,则第一轮分配的 5 个物理页帧都是分配之后在循环末尾就被立即回收,因为循环作用域的临时变量
``frame`` 的生命周期在那时结束了。然而,如果我们将它们 move 到一个向量中,它们的生命周期便被延长了——直到第 11 行
向量被清空的时候,这些 ``FrameTracker`` 的生命周期才结束,它们控制的 5 个物理页帧才被回收。这种思想我们立即
就会用到。
多级页表实现
多级页表实现
-----------------------------------
-----------------------------------
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录