实验四(上):线程
实验题
-
原理:线程切换之中,页表是何时切换的?页表的切换会不会影响程序 / 操作系统的运行?为什么?
页表是在
Process::prepare_next_thread()
中调用Thread::prepare()
,其中换入了新线程的页表。它不会影响执行,因为在中断期间是操作系统正在执行,而操作系统所用到的内核线性映射是存在于每个页表中的。
-
设计:如果不使用
sscratch
提供内核栈,而是像原来一样,遇到中断就直接将上下文压栈,请举出(思路即可,无需代码):-
一种情况不会出现问题
-
一种情况导致异常无法处理(指无法进入
handle_interrupt
) -
一种情况导致产生嵌套异常(指第二个异常能够进行到调用
handle_interrupt
,不考虑后续执行情况) -
一种情况导致一个用户进程(先不考虑是怎么来的)可以将自己变为内核进程,或以内核态执行自己的代码
-
只运行一个非常善意的线程,比如
loop {}
-
线程把自己的
sp
搞丢了,比如mv sp, x0
。此时无法保存寄存器,也没有能够支持操作系统正常运行的栈 -
运行两个线程。在两个线程切换的时候,会需要切换页表。但是此时操作系统运行在前一个线程的栈上,一旦切换,再访问栈就会导致缺页,因为每个线程的栈只在自己的页表中
-
用户进程巧妙地设计
sp
,使得它恰好落在内核的某些变量附近,于是在保存寄存器时就修改了变量的值。这相当于任意修改操作系统的控制信息
-
-
实验:当键盘按下 Ctrl + C 时,操作系统应该能够捕捉到中断。实现操作系统捕获该信号并结束当前运行的线程(你可能需要阅读一点在实验指导中没有提到的代码)
1 | fn supervisor_external(context: &mut Context) -> *mut Context { |
- 实验:实现进程的
fork()
。目前的内核线程不能进行系统调用,所以我们先简化地实现为“按 F 进行 fork”。fork 后应当为目前的进程复制一份几乎一样的拷贝。
1 | fn supervisor_external(context: &mut Context) -> *mut Context { |
1 | pub fn fork_current_thread(&mut self, context: &Context){ |
1 | // fork thread and return |