5.4: Timer_interrupts
Xv6 使用定时器中断来维护当前时间的概念,并在计算密集型进程之间进行切换。定时器中断来自连接到每个 RISC-V CPU 的时钟硬件。
Xv6 为每个 CPU 的时钟硬件编程,使其定期中断 CPU。
start.c(kernel/start.c:53)
中的代码设置了一些控制位,允许在超级用户模式下访问定时器控制寄存器,然后请求第一次定时器中断。
// ask each hart to generate timer interrupts.
void
timerinit()
{
// enable supervisor-mode timer interrupts.
w_mie(r_mie() | MIE_STIE);
// enable the sstc extension (i.e. stimecmp).
w_menvcfg(r_menvcfg() | (1L << 63));
// allow supervisor to use stimecmp and time.
w_mcounteren(r_mcounteren() | 2);
// ask for the very first timer interrupt.
w_stimecmp(r_time() + 1000000);
}
时间控制寄存器包含一个硬件以稳定速率递增的计数器,用于表示当前时间。stimecmp 寄存器包含一个时间点,CPU 将在此时间点触发定时器中断;将 stimecmp 设置为当前时间加上 x,则会在 x 时间单位后安排中断。对于 QEMU 的 RISC-V 模拟,1000000 时间单位大约是 1/10 秒。
定时器中断通过 usertrap 或 kerneltrap 和 devintr 传入,就像其他设备中断一样。定时器中断到达时,scause 的低位被设置为 5;trap.c 中的 devintr 检测到这种情况,并调用 clockintr(kernel/trap.c:164)。
void
clockintr()
{
if(cpuid() == 0){
acquire(&tickslock);
ticks++;
wakeup(&ticks);
release(&tickslock);
}
// ask for the next timer interrupt. this also clears
// the interrupt request. 1000000 is about a tenth
// of a second.
w_stimecmp(r_time() + 1000000);
}
该函数增加 ticks,使内核能够跟踪时间的流逝。这个增量只发生在一个 CPU 上,以避免在多个 CPU 的情况下时间过快地流逝。clockintr 唤醒任何在 sleep 系统调用中等待的进程,并通过写入 stimecmp 安排下一个定时器中断。
devintr 在处理定时器中断时返回 2,以便告诉 kerneltrap 或 usertrap 调用 yield,使得 CPU 可以在可运行进程之间进行多路复用。
内核代码可能会被定时器中断打断,并通过 yield 强制进行上下文切换,这也是为什么 usertrap 中的早期代码在启用中断前会小心地保存诸如sepc的状态。 上下文切换还意味着内核代码必须了解,它可能会在没有警告的情况下从一个 CPU 移动到另一个 CPU。