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