Skip to content

Commit

Permalink
Merge pull request #206 from MiaoHao-oops/master
Browse files Browse the repository at this point in the history
add MiaoHao-oops' stage3 summary
  • Loading branch information
ZhiyuanSue authored Dec 1, 2023
2 parents 16cf680 + bda9fd5 commit 49619c8
Showing 1 changed file with 120 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: 2023开源操作系统训练营第三阶段总结报告-郝淼
date: 2023-11-29 14:30:43
categories:
- oscamp 2023fall arceos unikernel
tags:
- author:MiaoHao-oops
- 2023秋冬季开源操作系统训练营
- 第三阶段总结报告
---

## 第一周练习总结

第一周的 5 个练习的主要目的是了解 ArceOS 的基本代码组织结构,为后面的任务打下基础。

### 练习 1、2

**练习 1** 要求实现彩色打印 `println!`,这个任务我采用了修改 `axstd::println!` 来实现,这是出于架构兼容性的考虑,不应修改太过底层的代码。具体实现是利用了 ANSI 转义序列。

**练习 2** 要求支持 `HashMap` 并通过测试。之所以 Rust 核心库中没有 `HashMap`,是因为 `HashMap` 需要调用产生随机数的接口,而这个接口是架构相关的。因此,练习 2 的核心是实现 `axstd` 可调用的 `fn random() -> u128` 接口。

自底向上实现 `random` 的过程:
- `axhal::random::random()`:在 `axhal` 层实现架构相关的 `random()` 接口,它调用了 `axhal::time::current_ticks()`
- `arceos_api::random::ax_random()`:定义 `ax_random` 接口,通过 `pub use axhal::random::random as ax_random;` 将其直接实现为 `axhal::random::random()`
-`axstd` 中可调用 `arceos_api::random::ax_random()`

### 练习 3、4

**练习 3** 要求实现内存分配器 `early`。ArceOS 中包含两个内存分配器 `palloc``balloc``palloc` 用于以页单位的内存分配,`balloc` 被指定为 Rust 的 `#[global_allocator]`,这样我们就可以使用 Rust 中如 `Vec``String` 等这些堆上可变长的数据类型。这两个内存分配器都包含在一个全局内存分配器 `GLOBAL_ALLOCATOR` 中。

`early` 分配器既是 `palloc` 又是 `balloc`,因此这里遇到一个问题:`GLOBAL_ALLOCATOR` 中使用了一对智能指针分别指向了 `palloc``balloc` 实例,但由于 `early` 分配器的特点,`early` 在全局只有一个实例,并且其中的 `palloc``balloc` 还需要共享数据。

因此,我将 `GLOBAL_ALLOCATOR` 可能的类型分为两种:

- `SeparateAllocator`:分离式内存分配器,`palloc``balloc` 是两个,当 `balloc` 可用内存不足时,向 `palloc` 请求内存
- `AIOAllocator`:一体式内存分配器,`palloc``balloc` 是同一个,两个内存分配器中共享剩余可用内存

此外,我定义了 `trait GlobalAllocator`,与原来的 `GlobalAllocator` 类中实现的方法保持一致。而后,为 `SeparateAllocator``AIOAllocator` 分别实现 `GlobalAllocator`,然后在 `toml` 中增加关于 `early` 的选项,即可达成练习目标。

**练习 4** 要求通过 `rust_main()` 的参数 `dtb` 解析 dtb。这一练习的关键是找到一个合适的 crate,我使用的是 `hermit-dtb = "0.1.1"`

### 练习 5

练习 5 要求将 FIFO 算法改变为抢占式的。FIFO 算法原本维护了一个队列,靠进程主动放弃 CPU 触发调度。在抢占式中,需要将原本的 `List` 修改为双端队列 `VecDequeue`,这样在 `remove_task(&mut self, task: &Self::SchedItem)` 中,修改为先找到 `task` 的下标,然后根据下标将其从队列中删除;在 `put_prev_task(&mut self, prev: Self::SchedItem, preempt: bool)` 如果此时是可以抢占的,就将 `prev` 放入队尾,否则将其放在队头,相当于调度器选择的还是当前的任务;最后,将 `task_tick` 的返回值恒置为 `true` 这样每次时钟中断发生时都能进行调度。

## 第二周练习总结

第二周在一个个小练习后逐渐实现了一个可以加载二进制文件的 loader,并且加载的应用有独立的地址空间。

### 练习 1、2

实际上练习1、2的任务是实现一个简易的文件系统。我的设计如下:

```Rust
struct ImgHeader {
app_num: usize,
}

struct AppHeader {
app_size: usize,
}
```

将 1 个 `ImgHeader` 放在 pflash 头部,而后紧接着`app_num``AppHeader` 用于指示每个应用的大小。

生成二进制镜像,添加这写头部信息我是用 C 语言实现的,在此不再赘述。

### 练习 3

批处理的方式下,先加载第一个应用,然后运行;而后加载第二个应用,然后运行。这两个应用均被加载到一个固定的地址。

在 loader 中,使用 `jalr` 指令通过函数调用执行一个应用,因此,在进入应用程序的主函数时,`ra` 寄存器已经被加载为返回地址,想要从应用程序返回,只需要将函数签名返回值部分的 `-> !` 改为 `-> ()` 或者直接删除即可。这样,应用程序生成的汇编代码最后是一条 `ret` 指令,恰好能返回到 loader。

### 练习 4、5

练习 4 将 `sys_terminate` 封装为对 `axstd::process::exit` 的调用即可。

练习 5 我将所有的 abi 调用和为一个分发函数:

```rust
fn abi_entry(abi_num: usize, arg0: usize) {
match abi_num {
SYS_HELLO => abi_hello(),
SYS_PUTCHAR => abi_putchar(arg0 as u8 as char),
SYS_TERMINATE => abi_terminate(arg0 as i32),
_ => panic!("unsupport abi call!"),
}
}
```

然后将这个函数的地址作为第一个参数传入应用,即通过 `a0` 传参:

```rust
unsafe extern "C" fn _start(abi_entry: usize) -> !
```

在应用中,封装 `putchar` 等函数:

```rust
fn putchar(c: u8) {
unsafe {
core::arch::asm!("
li a0, {abi_num}
la t0, {abi_entry_addr}
ld t0, (t0)
jalr t0",
abi_num = const SYS_PUTCHAR,
abi_entry_addr = sym ABI_ENTRY,
in("a1") c,
clobber_abi("C"),
)
}
}
```

实际上,所有 abi 函数都是跳转到 `abi_entry` 执行,不同之处在与参数的传递,我规定第一个参数 `a0` 是 abi 号,后面的是 abi 函数的参数。在 `putchar` 中,`a0` 被设置为 `SYS_PUTCHAR``a1` 被设置为 `c` 的值。注意,在内联汇编的结尾要加上 `clobber_abi("C")`,保持寄存器在执行这段汇编代码前后的一致,否则将出现混乱。

### 练习 6

目前,在批处理且每个应用地址空间相同的情况下,练习 6 实现起来比较简单,保持每个应用复用全局的应用页表 `APP_PT_SV39` 即可。

0 comments on commit 49619c8

Please sign in to comment.