内核线程初始化
回忆一下我们如何进行启动线程的初始化?无非两步:设置栈顶地址、跳转到内核入口地址。从而变为启动线程的初始状态,并准备开始运行。
其他线程的初始化也差不多。事实上我们要构造一个停止的线程状态,使得一旦其他的进程切换到它,就立刻变为我们想要的该线程的初始状态,并可以往下运行。
构造线程状态信息
首先是要新建一个内核栈,然后在栈上压入我们精心构造的线程状态信息。
1. // src/context.rs
2. impl ContextContent {
3. // 为一个新内核线程构造栈上的初始状态信息
4. // 其入口点地址为 entry ,其内核栈栈顶地址为 kstack_top ,其页表为 satp
5. fn new_kernel_thread(
6. entry: usize,
7. kstack_top: usize,
8. satp: usize,
9. ) -> ContextContent {
11. let mut content = ContextContent {
12. ra: __trapret as usize,
13. satp,
14. s: [0; 12],
15. tf: {
16. let mut tf: TrapFrame = unsafe { zeroed() };
17. tf.x[2] = kstack_top;
18. tf.sepc = entry;
19. tf.sstatus = sstatus::read();
20. tf.sstatus.set_spp(sstatus::SPP::Supervisor);
21. tf.sstatus.set_spie(true);
22. tf.sstatus.set_sie(false);
23. tf
24. }
25. };
26. content
27. }
28. }
首先

在 switch_to 中被正确设置。这里

的值为 __trapret ,因此当 switch_to 使用 ret 退出后会跳转到 __trapret 。而它是我们在中断处理返回时用来恢复中断上下文的!实际上这里用 __trapret 仅仅是利用它来设置寄存器的初始值,而不是说它和中断有什么关系。
从 switch_to 返回之后,原栈顶的

被回收掉了。因此现在栈顶上恰好保存了一个中断帧。那么我们从中断返回的视角来看待:栈顶地址会被正确设置为 kstack_top ,由于将中断帧的

设置为线程入口点,因此中断返回后会通过 sret 跳转到线程入口点。
注意中断帧中

的设置:
- 将

设置为 Supervisor ,使得使用 sret 返回后 CPU 的特权级为 S Mode 。
- 设置

,这里的作用是 sret 返回后,在内核线程中使能异步中断。详情请参考RISC-V 特权指令集文档。
我们还希望能够给线程传入参数,这只需要修改中断帧中的

(即参数

)即可,__trapret 函数可以协助完成参数传递。
1. // src/context.rs
3. impl Context {
4. pub unsafe fn new_kernel_thread(
5. entry: usize,
6. kstack_top: usize,
7. satp: usize
8. ) -> Context {
9. ContextContent::new_kernel_thread(entry, kstack_top, satp).push_at(kstack_top)
10. }
11. pub unsafe fn append_initial_arguments(&self, args: [usize; 3]) {
12. let contextContent = &mut *(self.content_addr as *mut ContextContent);
13. contextContent.tf.x[10] = args[0];
14. contextContent.tf.x[11] = args[1];
15. contextContent.tf.x[12] = args[2];
16. }
17. }
18. impl ContextContent {
19. // 将自身压到栈上,并返回 Context
20. unsafe fn push_at(self, stack_top: usize) -> Context {
21. let ptr = (stack_top as *mut ContextContent).sub(1);
22. *ptr = self;
23. Context { content_addr: ptr as usize }
24. }
25. }
创建新线程
接下来就是线程的创建:
1. // src/process/structs.rs
2. impl Thread {
3. // 创建一个新线程,放在堆上
4. pub fn new_kernel(entry: usize) -> Box<Thread> {
5. unsafe {
6. let kstack_ = KernelStack::new();
7. Box::new(Thread {
8. // 内核线程共享内核资源,因此用目前的 satp 即可
9. context: Context::new_kernel_thread(entry, kstack_.top(), satp::read().bits()), kstack: kstack_,
10. })
11. }
12. }
13. // 为线程传入初始参数
14. pub fn append_initial_arguments(&self, args: [usize; 3]) {
15. unsafe { self.context.append_initial_arguments(args); }
16. }
17. }
下一节我们终于能拨云见日,写一个测试看看我们的线程实现究竟有无问题了!
