Open mapleFU opened 3 years ago
rCore 里面线程代码如下:
pub struct Thread {
/// 线程 ID
pub id: ThreadID,
/// 线程的栈
pub stack: Range<VirtualAddress>,
/// 所属的进程
pub process: Arc<Process>,
/// 用 `Mutex` 包装一些可变的变量
pub inner: Mutex<ThreadInner>,
}
/// 线程中需要可变的部分
pub struct ThreadInner {
/// 线程执行上下文
///
/// 当且仅当线程被暂停执行时,`context` 为 `Some`
pub context: Option<Context>,
/// 是否进入休眠
pub sleeping: bool,
/// 是否已经结束
pub dead: bool,
}
线程被停下的时候,内容会被丢到 context 里面,这个相当于 TCB 的内容了。进程这里表现为一个 MemorySet
。是不是很合理!
程序抽象出了一个 Processor 来表示进程现在的运行状况,包括:
然后创建了一个全局的调度器。注意这个调度器的逻辑和中断是有关的,要利用起来还需要考虑关中断。
这个程序,最开始的时候,是这样的,直接在内核栈上处理所有的程序,比如 Panic 什么的。但是现在肯定不行了。
原本的逻辑,处理 interrupt 的时候,在内核栈上保留之前的 sp
, 然后把所有内容压栈,然后处理. 现在丢到 a0
上,然后 __interrupt
和 __restore
的时候处理。
关于中断有:
sstatus 标志位的具体意义 spp:中断前系统处于内核态(1)还是用户态(0) sie:内核态是否允许中断。对用户态而言,无论 sie 取何值都开启中断 spie:中断前是否开中断(用户态中断时可能 sie 为 0) 硬件处理流程
在中断发生时,系统要切换到内核态。此时,切换前的状态会被保存在 spp 位中(1 表示切换前处于内核态)。同时,切换前是否开中断会被保存在 spie 位中,而 sie 位会被置 0,表示关闭中断。 在中断结束,执行 sret 指令时,会根据 spp 位的值决定 sret 执行后是处于内核态还是用户态。与此同时,spie 位的值会被写入 sie 位,而 spie 位置 1。这样,特权状态和中断状态就全部恢复了。
还比较重要的是 Thread::new
的代码:
pub fn new(
process: Arc<Process>,
entry_point: usize,
arguments: Option<&[usize]>,
) -> MemoryResult<Arc<Thread>> {
// 让所属进程分配并映射一段空间,作为线程的栈
let stack = process.alloc_page_range(STACK_SIZE, Flags::READABLE | Flags::WRITABLE)?;
// 构建线程的 Context
let context = Context::new(stack.end.into(), entry_point, arguments, process.is_user);
// 打包成线程
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)
}
这里索要了 STACK_SIZE 的内存来处理。
park
和 prepare
是一对对操作:
/// 准备执行一个线程
///
/// 激活对应进程的页表,并返回其 Context
pub fn prepare(&self) -> *mut Context {
// 激活页表
self.process.inner().memory_set.activate();
// 取出 Context
let parked_frame = self.inner().context.take().unwrap();
// 将 Context 放至内核栈顶
unsafe { KERNEL_STACK.push_context(parked_frame) }
}
/// 发生时钟中断后暂停线程,保存状态
pub fn park(&self, context: Context) {
// 检查目前线程内的 context 应当为 None
assert!(self.inner().context.is_none());
// 将 Context 保存到线程中
self.inner().context.replace(context);
}
就是相当于拿/取 PCB 和页表相关的配置
这里开了一个空的 KERNEL_STACK
, 每个线程 prepare
的时候,会有:
/// 准备执行一个线程
///
/// 激活对应进程的页表,并返回其 Context
pub fn prepare(&self) -> *mut Context {
// 激活页表
self.process.inner().memory_set.activate();
// 取出 Context
let parked_frame = self.inner().context.take().unwrap();
// 将 Context 放至内核栈顶
unsafe { KERNEL_STACK.push_context(parked_frame) }
}
然后最外层:
// 获取第一个线程的 Context
let context = PROCESSOR.lock().prepare_next_thread();
// 启动第一个线程
unsafe { __restore(context as usize) };
实际上,这里逻辑链路是这样的:一开始拿到第一个线程,然后拿到 prepare
, 这里会调用 KERNEL_STACK.push_context
.
# 离开中断
# 此时内核栈顶被推入了一个 Context,而 a0 指向它
# 接下来从 Context 中恢复所有寄存器,并将 Context 出栈(用 sscratch 记录内核栈地址)
# 最后跳转至恢复的 sepc 的位置
__restore:
# 从 a0 中读取 sp
# 思考:a0 是在哪里被赋值的?(有两种情况)
mv sp, a0
# 恢复 CSR
LOAD t0, 32
LOAD t1, 33
csrw sstatus, t0
csrw sepc, t1
# 将内核栈地址写入 sscratch
addi t0, sp, 34*8
csrw sscratch, t0
第一次写入的时候,内核栈 prepare
返回一个指向它的指针,然后 addi t0, sp, 34*8
完成初始化。
有些问题我现在感觉还是没有理的很清楚,比如内核栈的数量(2.4 之后好像是 部分在 per cpu,比如 IRQ 的;然后剩下的是每个
task_struct
一个 stack ),中断的开关,线程的栈在哪,那我们先理一下好了