rCore 学习笔记 - 第三章
上下文切换
当前接触的上下文切换有三类:
- 第一类是普通的函数调用,不需要平台或者系统提供什么特殊指令,实际上用户程序自己在用户栈上操作,操作系统不用管,这是编译器的事。操作系统只需要为用户程序准备好用户栈就可以了。
- 第二类是由于用户程序触发异常需要到内核处理导致的,需要利用硬件的机制,保存在进入内核进行处理前,应用的运行信息,涉及到到了特权级切换和换栈的问题。
- 第三类是这一章新增的,切换应用导致的,现在每个程序都有单独的用户栈和内核栈(可能是因为这样比较简单),这类上下文切换和第一类函数调用的唯一区别是换栈。
时钟中断
RISC-V 的中断分为软件中断、时钟中断、外部中断三类,每类都有 Machine / Supervisor 两个版本。在 mtime
计数器到达 mtimecmp
之后就会触发一次时钟中断,时钟中断来自外部,不受处理器控制,所以对于 CPU 来说是「异步」的。
考虑中断前 CPU 特权级为 A,中断需要在特权级 B 中处理,关于中断是否被屏蔽,总结规则如下:
- B 高于 A,原有指令被抢占,Trap 到 B 中处理
- B 等于 A,以内核的 S 特权级为例,如果不屏蔽中断,首先需要将
sstatus.sie
设为 1,接着按sie
的三个字段ssie/stie/seie
决定是否屏蔽软件中断 / 时钟中断 / 外部中断。 - B 低于 A,中断被屏蔽。
作业实现
这次实验本身没什么难的,但因为我看的是 v3 的文档,测试代码用的是 2024A 的,有不少不一样的地方。比如 sys_task_info
的参数,那几个结构体的定义等。在新版的实现中似乎会让 println!("string from task info test\n");
做两次 sys_write
的调用,卡了好久才在评论区发现,这并不是我的问题。
还有一个需要留意的地方,记录一下 Debug 过程:刚开始实现记录系统调用次数的时候,发现 assert!(3 <= info.syscall_times[SYSCALL_GETTIMEOFDAY]);
这里就爆了,打印发现值为 0. 我检查了一下代码的逻辑,没有发现问题。于是我在测试代码中完整打印了 info.syscall_times
,发现个别地方是有值的。这就更奇怪了,明明有值,值只可能是内核里记录的,并且看数字大小也没什么问题。于是把有值的地方下标和值一起打印出来,发现了问题所在:下标比实际系统调用的 id 大了 1,也就是说有一个 u32
的偏移。我怀疑是系统调用的 id 写错了,检查后发现没问题,就算有问题也应当是个别偏移而不是整体偏移。我想,是不是 Rust 中 Option 的实现会带来内存布局的问题,想到内存布局问题,我就恍然大悟了。内核中在相关结构体定义时用了 #[repr(C)]
,而测试代码那里是没有的。