实验四(上):线程

实验题

  1. 原理:线程切换之中,页表是何时切换的?页表的切换会不会影响程序 / 操作系统的运行?为什么?

    页表是在 Process::prepare_next_thread() 中调用 Thread::prepare(),其中换入了新线程的页表。

    它不会影响执行,因为在中断期间是操作系统正在执行,而操作系统所用到的内核线性映射是存在于每个页表中的。

  2. 设计:如果不使用 sscratch 提供内核栈,而是像原来一样,遇到中断就直接将上下文压栈,请举出(思路即可,无需代码):

    • 一种情况不会出现问题

    • 一种情况导致异常无法处理(指无法进入 handle_interrupt

    • 一种情况导致产生嵌套异常(指第二个异常能够进行到调用 handle_interrupt,不考虑后续执行情况)

    • 一种情况导致一个用户进程(先不考虑是怎么来的)可以将自己变为内核进程,或以内核态执行自己的代码

    • 只运行一个非常善意的线程,比如 loop {}

    • 线程把自己的 sp 搞丢了,比如 mv sp, x0。此时无法保存寄存器,也没有能够支持操作系统正常运行的栈

    • 运行两个线程。在两个线程切换的时候,会需要切换页表。但是此时操作系统运行在前一个线程的栈上,一旦切换,再访问栈就会导致缺页,因为每个线程的栈只在自己的页表中

    • 用户进程巧妙地设计 sp,使得它恰好落在内核的某些变量附近,于是在保存寄存器时就修改了变量的值。这相当于任意修改操作系统的控制信息

  3. 实验:当键盘按下 Ctrl + C 时,操作系统应该能够捕捉到中断。实现操作系统捕获该信号并结束当前运行的线程(你可能需要阅读一点在实验指导中没有提到的代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn supervisor_external(context: &mut Context) -> *mut Context {
let mut c = console_getchar();
if c <= 255 {
if c == 3 {
PROCESSOR.lock().kill_current_thread();
PROCESSOR.lock().prepare_next_thread();
}
if c == '\r' as usize {
c = '\n' as usize;
}
STDIN.push(c as u8);
}
context
}
  1. 实验:实现进程的 fork()。目前的内核线程不能进行系统调用,所以我们先简化地实现为“按 F 进行 fork”。fork 后应当为目前的进程复制一份几乎一样的拷贝。

handler.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn supervisor_external(context: &mut Context) -> *mut Context {
let mut c = console_getchar();
if c <= 255 {
// interrupt current thread
if c == 3 {
PROCESSOR.lock().kill_current_thread();
PROCESSOR.lock().prepare_next_thread();
}
// fork
if c == 'F' as usize{
PROCESSOR.lock().fork_current_thread(context);
}
if c == '\r' as usize {
c = '\n' as usize;
}
STDIN.push(c as u8);
}
context
}

processor.rs

1
2
3
4
pub fn fork_current_thread(&mut self, context: &Context){
let thread = self.current_thread().fork(*context).unwrap();
self.add_thread(thread);
}

thread.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// fork thread and return
pub fn fork(&self, current_context: Context) -> MemoryResult<Arc<Thread>> {
println!("fork!");
let stack = self
.process
.alloc_page_range(STACK_SIZE, Flags::READABLE | Flags::WRITABLE)?;

for i in 0..STACK_SIZE{
*VirtualAddress(stack.start.0 + i).deref::<u8>() = *VirtualAddress(self.stack.start.0 + i).deref::<u8>();
}

let mut context = current_context.clone();
context.set_sp(usize::from(stack.start) - usize::from(self.stack.start) + current_context.sp());

let process = self.process.clone();

let thread = Arc::new(Thread{
id : unsafe{
THREAD_COUNTER += 1;
THREAD_COUNTER
},
stack,
process,
inner : Mutex::new(ThreadInner{
context: Some(context),
sleeping: false,
dead: false
})

});
Ok(thread)

}