diff --git "a/source/_posts/2024 \347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\346\200\273\347\273\223\346\212\245\345\221\212-\346\210\264\345\277\227\345\274\272.md" "b/source/_posts/2024 \347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\346\200\273\347\273\223\346\212\245\345\221\212-\346\210\264\345\277\227\345\274\272.md" new file mode 100644 index 0000000000..46697dfbb1 --- /dev/null +++ "b/source/_posts/2024 \347\247\213\345\206\254\345\255\243\345\274\200\346\272\220\346\223\215\344\275\234\347\263\273\347\273\237\350\256\255\347\273\203\350\220\245\347\254\254\344\270\211\351\230\266\346\256\265\346\200\273\347\273\223\346\212\245\345\221\212-\346\210\264\345\277\227\345\274\272.md" @@ -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 { + key: K, + value: V, +} +pub struct HashMap { + buckets: Vec>>>, +} +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 { +heap_start: usize, +heap_end: usize, +next: usize, +allocations: usize, +page_pos: usize, +} +``` + +Base allocator需要的init 与 add_memory + +```rust + +impl BaseAllocator for EarlyAllocator { + 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 { +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 ByteAllocator for EarlyAllocator{ + fn alloc(&mut self, layout: Layout) -> AllocResult> { + 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, 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 的课程内容十分丰富,极大地拓宽了我的操作系统视野。 \ No newline at end of file diff --git "a/source/_posts/2024\347\247\213\345\206\254\345\255\243\350\256\255\347\273\203\350\220\245\347\254\254\345\233\233\351\230\266\346\256\265\346\200\273\347\273\223-\346\210\264\345\277\227\345\274\272.md" "b/source/_posts/2024\347\247\213\345\206\254\345\255\243\350\256\255\347\273\203\350\220\245\347\254\254\345\233\233\351\230\266\346\256\265\346\200\273\347\273\223-\346\210\264\345\277\227\345\274\272.md" new file mode 100644 index 0000000000..eeec0fb899 --- /dev/null +++ "b/source/_posts/2024\347\247\213\345\206\254\345\255\243\350\256\255\347\273\203\350\220\245\347\254\254\345\233\233\351\230\266\346\256\265\346\200\273\347\273\223-\346\210\264\345\277\227\345\274\272.md" @@ -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, + mailbox: Mailbox, + pub sig_trampoline: SignalTrampoline, + pub process: Arc, + pub sig_queue: SpinNoIrqLock, + pub inner: UnsafeCell, +} +``` + +虽然内部资源管理方式与传统内核略有不同,但核心思想是一致的:通过 `Thread` 表示一个具体的任务实体。 + +在内核中,当加载一个 ELF 文件并创建线程时,会生成一个 `Thread` 实例。随后,这些线程会被包装为异步任务进行管理。 + +------ + +### **线程生命周期的实现** + +线程的执行过程通常包括以下几步: + +1. 加载用户态寄存器(`ld regs`)。 +2. 返回用户态执行(`sret`)。 +3. 用户态发生中断时,陷入内核处理(`trap`)。 +4. 继续返回用户态,循环往复,直到线程终止。 + +在一些传统内核中,这个循环过程可能直接用汇编实现(通常写在 `.S` 文件中)。但在该内核中,将线程生命周期抽象为 Rust 异步函数来实现: + +```rust +pub async fn threadloop(thread: Arc) { + 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 { + task_ctx: Box, + task_future: F, +} + +impl Future for UserTaskFuture { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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) { + 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) 的实现思路,将进出用户态等流程封装为一个协程。由于这是我第一次接触操作系统相关知识,在重构内核时,需要通过大量的调试来有机地理解和掌握内核的整体工作流程。然而,受限于期末周和其他大作业的时间压力,目前仅完成了将进出用户态等过程迁移到协程中的初步工作,仍有许多细节尚未完善。 + +