riscv-11-分时多任务与抢占式调度策略实现
1.分时多任务系统
分时多任务系统:任务的切换不是通过用户程序来自行放弃cpu的使用权作为前提的,而是内核自己来决定何时切换任务,这个切换的原则就是每个任务一次只能运行一段时间,时间一到就会被操作系统强制切换到下一个任务执行
因此需要定时器来实现时钟中断
riscv的时钟中断
中断可以分为三类:
- 软件中断:由软件控制放出的中断
- 时钟中断:由时钟电路发出的中断
- 外部中断:由外设发出的中断
当scause的最高位为1时代表此次触发的异常为中断类型:
| Interrupt | Exception Code | Description |
|---|---|---|
| 1 | 1 | Supervisor software interrupt (S模式下软件中断) |
| 1 | 3 | Machine software interrupt(M模式下软件中断) |
| 1 | 5 | Supervisor timer interrupt(S模式下时钟中断) |
| 1 | 7 | Machine timer interrupt(M模式下时钟中断) |
| 1 | 9 | Supervisor external interrupt (S模式下外部中断) |
| 1 | 11 | Machine external interrupt(M模式下外部中断) |
可以看到这三种中断每一个都有 M/S 特权级两个版本。中断的特权级可以决定该中断是否会被屏蔽,以及需要 Trap 到 CPU 的哪个特权级进行处理。我们的目标是在S态使用时钟中断,这涉及到两个个在S态控制中断的寄存器sstatus,sie
sstatus的bit[2]用来使能S态模式下的所有中断

sie的bit[5]用来专门使能S态的时钟中断
- 当
STIE的bit[5]用来专门使能S态的时钟中断- 它的三个字段
ssie/stie/seie分别控制 S 特权级的软件中断、时钟中断和外部中断的中断使能

比如对于 S 态时钟中断来说,如果 CPU 不高于 S 特权级,需要 sstatus.sie 和 sie.stie 均为 1 该中断才不会被屏蔽;如果 CPU 当前特权级高于 S 特权级,则该中断一定会被屏蔽。 —- 例如当前CPU特权级为M模式
计时器:
- RISC-V 64 架构上,该计数器保存在一个 64 位的 CSR
mtime中,我们无需担心它的溢出问题,在内核运行全程可以认为它是一直递增的。这个计数器一般我们叫做RTC。 - 另外一个 64 位的 CSR
mtimecmp的作用是:一旦计数器mtime的值超过了mtimecmp,就会触发一次时钟中断。
OS/timer.c
在
timer_init()函数中,分别将sstatus.sie置 1 和sie.stie,操作sie寄存器的代码放在riscv.h中
1 |
|
OS/riscv.h
1 | /* Supervisor Interrupt Enable*/ |
获取mtime的值:
- 为了设置时钟中断的频率我们需要先读到
mtime的值,然后设置mtimecmp,这两个寄存器都是M模式下的,在S模式下不能直接访问 - 第一种是使用
rdtime这个伪指令,这里是在哪里找的呢,在opensbi的源码中,在lib/sbi/sbi_timer.c - 第二种方式是
asm volatime("csrr %0, 0x0C01" : "=r" (x) )来读取,mtime这个寄存器通过MMIO映射到了一个确定的地址,这个地址和平台有关,在opensbi源码的sbi_emulate_csr.c中,opensbi将mtime的值映射到了0xc01的地方,这是opensbi做了二次映射,用于S态的程序来读取,实际mtime的映射地址应该由qemu来做的
OS/sbi.c
mtimecmp的值可以通过opensbi提供的接口来设置
1 | /** |
OS/sbi.h
1 | enum sbi_ext_time_fid { |
- 在
qemu中rtc的频率为10mhz,即10^7 - 在上面的代码中,我将1s分成了1000个时间片,即每隔1us触发一次时钟中断,因此每次触发时钟中断设置的
mtimecmp值为:r_mtime() + CLOCK_FREQ / TICKS_PER_SEC
2.实现
OS/trap.c
修改:只需要在时钟中断到来时,设置下一次时钟中断的
mtimecmp的值,并切换一次任务
scause最高位为1时代表为中断则进入中断的判断分支,否则进入异常的处理分支。
1 | trap_Context* trap_handler(trap_Context* cx) |
3.测试
OS/app.c
- 注释掉
yield然后让任务自主根据定时器切换
1 | uint64_t sys_gettime() { |
OS/main.c
添加时钟初始化
1 |
|
总结:
其余的修改见commit,主要就是添加了timer.c文件 已经修改 中断与异常分发,区分中断与异常 为1则为中断,以及配置相应的时钟,获取RTL时间。
- RISC-V 64 架构上,该计数器保存在一个 64 位的 CSR
mtime中,我们无需担心它的溢出问题,在内核运行全程可以认为它是一直递增的。这个计数器一般我们叫做RTC。 - 另外一个 64 位的 CSR
mtimecmp的作用是:一旦计数器mtime的值超过了mtimecmp,就会触发一次时钟中断。
1 | /* 关键代码*/ |
sudo ./build.sh
sudo ./run.sh

