Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024 秋冬季开源操作系统训练营第三阶段总结报告-戴志强 #659

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
title: 2024 秋冬季开源操作系统训练营第三阶段总结报告-戴志强
date: 2024-12-2 22:20:00
tags:
- author:Heliosly
---

# 2024 秋冬季开源操作系统训练营 项目基础阶段

---

## **进度情况:**

由于忙于区域赛和大创申请书,三阶段的时间并不充足,作业仅仅做到hypervisor前的部分,仅阅读了部分源码。

## Unikernel基础与框架

### 题1

只需要将

`println!("[WithColor]: !");`

改为
`println!("\x1b[33m[WithColor]: Hello, Arceos!\x1b[0m");`

可以打出黄色字体。

### 题2

要求实现HashMap,并且提供随机数,我还没办法理解随机数和HashMap的关系,所以写了一个简单的HashMap

```rust
struct KeyValue<K, V> {
key: K,
value: V,
}
pub struct HashMap<K, V> {
buckets: Vec<Vec<Option<KeyValue<K, V>>>>,
}
fn hash(&self, key: &K) -> usize {
let key_ptr = key as *const _ as usize;
key_ptr % self.buckets.len()
}
```

## Unikernel地址空间与分页、多任务支持(协作式)

**题目要求:**

只能修改modules/bump_allocator组件的实现,支持bump内存分配算法。不能改其它部分。

Bumpallocator 结构体

```rust
pub struct EarlyAllocator<const PAGE_SIZE: usize> {
heap_start: usize,
heap_end: usize,
next: usize,
allocations: usize,
page_pos: usize,
}
```

Base allocator需要的init 与 add_memory

```rust

impl<const PAGE_SIZE: usize> BaseAllocator for EarlyAllocator<PAGE_SIZE> {
fn add_memory(&mut self, start: usize, size: usize) -> AllocResult {

self.heap_end = start + size;
Ok(())
}

fn init(&mut self, start: usize, size: usize) {
self.heap_start = start;
self.heap_end = size + self.heap_start;
self.next = self.heap_start;
}

}
```

PageAllocator因没有dealloc所以未实现

```rust
fn alloc_pages(&mut self, num_pages: usize, align_pow2: usize) -> AllocResult<usize> {
let align_mask = (1 << align_pow2) - 1;
let required_size = num_pages * PAGE_SIZE;
let aligned_pos = (self.page_pos + align_mask) & !align_mask;
let allocated_pages = aligned_pos;
self.page_pos = aligned_pos + required_size;
Ok(allocated_pages)
}
```

ByteAllocator

```rust
impl<const PAGE_SIZE:usize> ByteAllocator for EarlyAllocator<PAGE_SIZE>{
fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>> {
let alloc_start = self.next;
self.next = alloc_start + layout.size();
self.allocations += 1;
Ok(unsafe { NonNull::new_unchecked(alloc_start as *mut u8) })

}
fn dealloc(&mut self, pos: NonNull<u8>, layout: Layout) {
self.allocations -= 1;
if self.allocations == 0 {
self.next = self.heap_start;
}
}
```

## 从Unikernel到宏内核

**题目要求:**

需要补全缺页加载代码。

观察m1.0.0 task中代码,发现用 current().task_ext()可以得到TaskExt,然后TaskExt中包含一个AddSpace 结构体,该结构体实现了处理缺页加载的方法。于是按照该思路需要补充的代码如下

```rust
use axhal::{mem::VirtAddr,paging::MappingFlags};
use axhal::trap::{register_trap_handler,PAGE_FAULT};
#[register_trap_handler(PAGE_FAULT)]
fn handle_page_fault(vaddr: VirtAddr, access_flags: MappingFlags, is_user: bool) -> bool {
if is_user {
if !axtask::current()
.task_ext()
.aspace
.lock()
.handle_page_fault(vaddr, access_flags)
{
ax_println!("handle_page_fault failed, vaddr={:#x}", vaddr);
axtask::exit(-1);
} else {
true
}

}
else{
false
}

}
```

**感想**:与 Rcore 相比,似乎 ArceOS显得更加复杂,因其为了实现模块之间的兼容性,采用了多层的封装、抽象和隔离机制。然而,从我的编程体验来看,从某个特定模块的视角来看,阅读 ArceOS 的代码反而比 Rcore 更加清晰。降低了局部代码阅读难度,是值得学习的编码习惯。同时,ArceOS 的课程内容十分丰富,极大地拓宽了我的操作系统视野。
219 changes: 219 additions & 0 deletions source/_posts/2024秋冬季训练营第四阶段总结-戴志强.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# 在训练营中做了什么

## rust的异步机制学习

在 Rust 的异步编程中,`async` 和 `await` 是不可忽略的核心概念。

当一个函数被标记为 `async` 时,它会被转换为一个返回 `Future` 的函数。实际上,`async` 函数并不是立即执行的,而是生成了一个 `Future`,表示一个可能尚未完成的异步计算。类似地,`async` 代码块的行为与 `async` 函数本质相同,都可以生成一个 `Future`。

调用 `async` 函数后,你需要使用 `await` 来获取其结果:

```rust
some_func().await;
```

### **await的作用**

`await` 的核心任务是对 `Future` 进行轮询(poll)。当调用 `await` 时,程序会检查 `Future` 是否已完成。如果尚未完成,当前任务会被暂停,并将控制权交还给执行器(executor)。此时,CPU 资源可以被其他任务使用。随后,执行器会在适当的时机再次轮询该 `Future`,直到其完成。

------

### **异步任务的顺序**

考虑以下代码:

```rust
func1().await;
func2().await;
```

如果 `func1` 中存在一个耗时的操作(例如 2 秒的延迟),在 `await` 首次轮询时,由于任务未完成,执行器会暂停该任务并继续寻找其他可以执行的任务,例如 `func2`。这样就实现了异步调度。

然而,如果你在异步代码中混合了同步操作,比如:

```rust
func1().await;
println!("1");
```

这种情况下,`func1` 的任务未完成时,程序会暂停整个任务链,而不会直接跳到同步代码。也就是说,`println!("1")` 的执行必须等到 `func1` 完成后才能继续。这表明异步任务的执行顺序可以调整,但同步代码的执行仍然是严格按顺序进行的。

------

### **async/await的底层原理**

`async` 和 `await` 的实现涉及编译器的转换。在编译时,所有 `async` 函数会被转换为状态机。这种状态机的实现方式类似于无栈协程。每个 `Future` 都维护着一个状态,记录其当前的执行进度。

```rust
enum PollResult {
Ready,
Pending,
}
enum State {
StateA,
StateB,
StateC,
}
struct Coroutine {
state: State,
}
impl Coroutine {
pub fn poll(&mut self) -> PollResult {
match self.state {
State::StateA => {
/* Do something */
self.state = State::StateB;
return PollResult::Pending;
}
State::StateB => {
/* Do something */
self.state = State::StateC;
return PollResult::Pending;
}
State::StateC => {
/* Do something */
return PollResult::Ready;
}
}
}
}
```

上述代码中的 poll 函数即为协程的具体运行函数,不难看出其是一个状态机模型,上层每次调用 poll 时都可能改变其状态,从而推进其运行;总的来说,无栈协程的调度是通过函数返回然后调用另一个函数实现的,而不是像有栈协程那样直接原地更改栈指针。

此外,编译器会为异步函数生成一个表,类似于 C++ 的虚函数表(vtable)。每当一个任务暂停时,执行器会通过这个表找到下一个可以执行的任务,并对其进行轮询。这种机制确保了异步任务之间的高效调度和协作。

通过这些特性,Rust 实现了高性能的异步编程,同时避免了传统回调方式的复杂性,保证了代码的可读性和可靠性。

## Phoenix 异步内核学习

在这个内核中,异步任务的执行流是通过 Rust 的 `async` 和 `await` 特性来实现的。以下是执行流的一个简要过程。

### **任务的核心调度**

首先,从内核的主循环开始:

```rust
loop {
executor::run_until_idle();
}
```

这里的 `run_until_idle` 是任务调度的入口,其核心逻辑如下:

```rust
pub fn run_until_idle() -> usize {
let mut n = 0;
loop {
if let Some(task) = TASK_QUEUE.fetch() {
task.run();
n += 1;
} else {
break;
}
}
n
}
```

它从任务队列中提取任务并运行,直到队列为空。这里的任务可以是一个用户线程,也可以是其他异步任务。

------

### **线程的定义**

内核将线程与进程分开设计。线程的定义如下:

```rust
pub struct Thread {
tid: Arc<TidHandle>,
mailbox: Mailbox,
pub sig_trampoline: SignalTrampoline,
pub process: Arc<Process>,
pub sig_queue: SpinNoIrqLock<SigQueue>,
pub inner: UnsafeCell<ThreadInner>,
}
```

虽然内部资源管理方式与传统内核略有不同,但核心思想是一致的:通过 `Thread` 表示一个具体的任务实体。

在内核中,当加载一个 ELF 文件并创建线程时,会生成一个 `Thread` 实例。随后,这些线程会被包装为异步任务进行管理。

------

### **线程生命周期的实现**

线程的执行过程通常包括以下几步:

1. 加载用户态寄存器(`ld regs`)。
2. 返回用户态执行(`sret`)。
3. 用户态发生中断时,陷入内核处理(`trap`)。
4. 继续返回用户态,循环往复,直到线程终止。

在一些传统内核中,这个循环过程可能直接用汇编实现(通常写在 `.S` 文件中)。但在该内核中,将线程生命周期抽象为 Rust 异步函数来实现:

```rust
pub async fn threadloop(thread: Arc<Thread>) {
thread.set_waker(async_utils::take_waker().await);
loop {
trap::user_trap::trap_return();
trap::user_trap::trap_handler().await;

if thread.is_zombie() {
break;
}
}
handle_exit(&thread);
}
```

这个设计的关键在于使用 `async` 将线程的整个生命周期建模为一个 `Future`,使得线程的运行和中断处理都可以以异步方式进行,而无需依赖汇编实现。这种方式可以充分利用 Rust 异步编程的特性。

------

### **任务的 Future 封装**

内核将每个线程包装为一个 `Future`,具体定义如下:

```rust
pub struct UserTaskFuture<F: Future + Send + 'static> {
task_ctx: Box<LocalContext>,
task_future: F,
}

impl<F: Future + Send + 'static> Future for UserTaskFuture<F> {
type Output = F::Output;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let hart = processor::local_hart();
hart.push_task(&mut this.task_ctx);

let ret = unsafe { Pin::new_unchecked(&mut this.task_future).poll(cx) };
hart.pop_task(&mut this.task_ctx);

ret
}
}
```

其中,`task_ctx` 包含 CPU 上下文和线程指针,负责管理任务的运行状态。

在初始化时,线程会被封装成 `Future`,并推入调度队列:

```rust
pub fn spawn_thread(thread: Arc<Thread>) {
let future = UserTaskFuture::new(thread.clone(), threadloop(thread));
let (runnable, task) = executor::spawn(future);
runnable.schedule();
task.detach();
}
```

此处的 `threadloop` 被作为一个 `Future` 传递给 `UserTaskFuture::new`,从而将线程的整个生命周期管理交由异步调度器。

## 异步的实现

我尝试对 rCore 内核进行异步化改造,利用 Rust 提供的 `async` 和 `await` 实现异步编程。参考了 [Phoenix](https://github.com/AsyncModules/async-os/tree/main/modules/trampoline) 的实现思路,将进出用户态等流程封装为一个协程。由于这是我第一次接触操作系统相关知识,在重构内核时,需要通过大量的调试来有机地理解和掌握内核的整体工作流程。然而,受限于期末周和其他大作业的时间压力,目前仅完成了将进出用户态等过程迁移到协程中的初步工作,仍有许多细节尚未完善。