在训练营中做了什么
rust的异步机制学习
在 Rust 的异步编程中,async 和 await 是不可忽略的核心概念。
当一个函数被标记为 async 时,它会被转换为一个返回 Future 的函数。实际上,async 函数并不是立即执行的,而是生成了一个 Future,表示一个可能尚未完成的异步计算。类似地,async 代码块的行为与 async 函数本质相同,都可以生成一个 Future。
调用 async 函数后,你需要使用 await 来获取其结果:
1 | some_func().await; |
await的作用
await 的核心任务是对 Future 进行轮询(poll)。当调用 await 时,程序会检查 Future 是否已完成。如果尚未完成,当前任务会被暂停,并将控制权交还给执行器(executor)。此时,CPU 资源可以被其他任务使用。随后,执行器会在适当的时机再次轮询该 Future,直到其完成。
异步任务的顺序
考虑以下代码:
1 | func1().await; |
如果 func1 中存在一个耗时的操作(例如 2 秒的延迟),在 await 首次轮询时,由于任务未完成,执行器会暂停该任务并继续寻找其他可以执行的任务,例如 func2。这样就实现了异步调度。
然而,如果你在异步代码中混合了同步操作,比如:
1 | func1().await; |
这种情况下,func1 的任务未完成时,程序会暂停整个任务链,而不会直接跳到同步代码。也就是说,println!("1") 的执行必须等到 func1 完成后才能继续。这表明异步任务的执行顺序可以调整,但同步代码的执行仍然是严格按顺序进行的。
async/await的底层原理
async 和 await 的实现涉及编译器的转换。在编译时,所有 async 函数会被转换为状态机。这种状态机的实现方式类似于无栈协程。每个 Future 都维护着一个状态,记录其当前的执行进度。
1 | enum PollResult { |
上述代码中的 poll 函数即为协程的具体运行函数,不难看出其是一个状态机模型,上层每次调用 poll 时都可能改变其状态,从而推进其运行;总的来说,无栈协程的调度是通过函数返回然后调用另一个函数实现的,而不是像有栈协程那样直接原地更改栈指针。
此外,编译器会为异步函数生成一个表,类似于 C++ 的虚函数表(vtable)。每当一个任务暂停时,执行器会通过这个表找到下一个可以执行的任务,并对其进行轮询。这种机制确保了异步任务之间的高效调度和协作。
通过这些特性,Rust 实现了高性能的异步编程,同时避免了传统回调方式的复杂性,保证了代码的可读性和可靠性。
Phoenix 异步内核学习
在这个内核中,异步任务的执行流是通过 Rust 的 async 和 await 特性来实现的。以下是执行流的一个简要过程。
任务的核心调度
首先,从内核的主循环开始:
1 | loop { |
这里的 run_until_idle 是任务调度的入口,其核心逻辑如下:
1 | pub fn run_until_idle() -> usize { |
它从任务队列中提取任务并运行,直到队列为空。这里的任务可以是一个用户线程,也可以是其他异步任务。
线程的定义
内核将线程与进程分开设计。线程的定义如下:
1 | pub struct Thread { |
虽然内部资源管理方式与传统内核略有不同,但核心思想是一致的:通过 Thread 表示一个具体的任务实体。
在内核中,当加载一个 ELF 文件并创建线程时,会生成一个 Thread 实例。随后,这些线程会被包装为异步任务进行管理。
线程生命周期的实现
线程的执行过程通常包括以下几步:
- 加载用户态寄存器(
ld regs)。 - 返回用户态执行(
sret)。 - 用户态发生中断时,陷入内核处理(
trap)。 - 继续返回用户态,循环往复,直到线程终止。
在一些传统内核中,这个循环过程可能直接用汇编实现(通常写在 .S 文件中)。但在该内核中,将线程生命周期抽象为 Rust 异步函数来实现:
1 | pub async fn threadloop(thread: Arc<Thread>) { |
这个设计的关键在于使用 async 将线程的整个生命周期建模为一个 Future,使得线程的运行和中断处理都可以以异步方式进行,而无需依赖汇编实现。这种方式可以充分利用 Rust 异步编程的特性。
任务的 Future 封装
内核将每个线程包装为一个 Future,具体定义如下:
1 | pub struct UserTaskFuture<F: Future + Send + 'static> { |
其中,task_ctx 包含 CPU 上下文和线程指针,负责管理任务的运行状态。
在初始化时,线程会被封装成 Future,并推入调度队列:
1 | pub fn spawn_thread(thread: Arc<Thread>) { |
此处的 threadloop 被作为一个 Future 传递给 UserTaskFuture::new,从而将线程的整个生命周期管理交由异步调度器。
异步的实现
我尝试对 rCore 内核进行异步化改造,利用 Rust 提供的 async 和 await 实现异步编程。参考了 Phoenix 的实现思路,将进出用户态等流程封装为一个协程。由于这是我第一次接触操作系统相关知识,在重构内核时,需要通过大量的调试来有机地理解和掌握内核的整体工作流程。然而,受限于期末周和其他大作业的时间压力,目前仅完成了将进出用户态等过程迁移到协程中的初步工作,仍有许多细节尚未完善。