RISC-V requires that timer interrupts be taken in machine mode, not supervisor mode. RISC-V machine mode executes without paging, and with a separate set of control registers, so it’s not practical to run ordinary xv6 kernel code in machine mode. As a result, xv6 handles timer interrupts completely separately from the trap mechanism laid out above.
timer interrupts 必须执行在machine mode, 而不是supervisor mode, 所以需要依赖 trap mechanism
A timer interrupt can occur at any point when user or kernel code is executing; there’s no way for the kernel to disable timer interrupts during critical operations. Thus the timer interrupt handler must do its job in a way guaranteed not to disturb interrupted kernel code. The basic strategy is for the handler to ask the RISC-V to raise a “software interrupt” and immediately return. The RISC-V delivers software interrupts to the kernel with the ordinary trap mechanism, and allows the kernel to disable them. The code to handle the software interrupt generated by a timer interrupt can be seen in devintr (kernel/trap.c:205).
Timer interrupt 可能会在任何时候产生,为了不影响内核的代码,所以最好的方式是用产生一个软件中断,然后立即返回。
The machine-mode timer interrupt handler is timervec (kernel/kernelvec.S:95). It saves a few registers in the scratch area prepared by start, tells the CLINT when to generate the next timer interrupt, asks the RISC-V to raise a software interrupt, restores registers, and returns. There’s no C code in the timer interrupt handler.
timervec 是一种machine mode 下的中断handler,
it would be more convenient if xv6 could ask the RISC-V hardware for the current hartid whenever needed, but RISC-V allows that only in machine mode, not in supervisor mode.
RISC_V 只允许在machine下 获取hardware hartid.
// core local interruptor (CLINT), which contains the timer.
#define CLINT 0x2000000L
#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 8*(hartid))
#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot.
#define NCPU 8 // maximum number of CPUs
// a scratch area per CPU for machine-mode timer interrupts.
uint64 timer_scratch[NCPU][5];
// arrange to receive timer interrupts.
// they will arrive in machine mode at
// at timervec in kernelvec.S,
// which turns them into software interrupts for
// devintr() in trap.c.
void
timerinit()
{
// each CPU has a separate source of timer interrupts.
int id = r_mhartid(); // 在machine 下才可以获取
// ask the CLINT for a timer interrupt.
int interval = 1000000; // cycles; about 1/10th second in qemu.
*(uint64*)CLINT_MTIMECMP(id) = *(uint64*)CLINT_MTIME + interval;
// prepare information in scratch[] for timervec.
// scratch[0..2] : space for timervec to save registers.
// scratch[3] : address of CLINT MTIMECMP register.
// scratch[4] : desired interval (in cycles) between timer interrupts.
uint64 *scratch = &timer_scratch[id][0];
// scratch 是一个长度为5的uint64_t数组, 总长度是 40字节
// 这里仅填充3,4位置的元素,也就是后16个字节,前24个
scratch[3] = CLINT_MTIMECMP(id);
scratch[4] = interval;
// 这里将scratch指针放在 mscratch 寄存器中,后面在中断时有用到
w_mscratch((uint64)scratch);
// set the machine-mode trap handler.
w_mtvec((uint64)timervec);
// enable machine-mode interrupts.
w_mstatus(r_mstatus() | MSTATUS_MIE);
// enable machine-mode timer interrupts.
w_mie(r_mie() | MIE_MTIE);
}
#
# machine-mode timer interrupt.
#
.globl timervec
.align 4
timervec:
# start.c has set up the memory that mscratch points to:
# scratch[0,8,16] : register save area.
# scratch[24] : address of CLINT's MTIMECMP register.
# scratch[32] : desired interval between interrupts.
// 将 a0 和 mscratch 的值进行交换,获取到 数组scratch 指针
csrrw a0, mscratch, a0
// 先缓存a1, a2, a3 里面的寄存器数据,到对应的地址中
sd a1, 0(a0)
sd a2, 8(a0)
sd a3, 16(a0)
# schedule the next timer interrupt
# by adding interval to mtimecmp.
// 将a0地址偏移24的字段放在a1中, 保存 CLINT_MTIMECMP(id); 的值
ld a1, 24(a0) # CLINT_MTIMECMP(hart)
// a2 放置的是intervel
ld a2, 32(a0) # interval
// a3 拷贝了一次a1地址里面的值,保存的是 CLINT_MTIME 的时间戳应该
ld a3, 0(a1)
// 将a3设置为 a3 + a2, 也就是 时间戳 + intervel
add a3, a3, a2
// 然后将a3写回 a1的地址, 也就是原始 CLINT_MTIME 的时间戳,也就是更新时间戳
sd a3, 0(a1)
# arrange for a supervisor software interrupt
# after this handler returns.
// 上面a1 用完后,将a1设置为2,
li a1, 2
// 在RISC-V架构中,csrw(Control and Status Register Write)指令用于写入控制和
// 状态寄存器(CSR,Control and Status Registers)。
// sip(Supervisor Interrupt Pending)寄存器. 写入2,
// 触发或模拟一个监督模式 Supervisor 下的软件中断
csrw sip, a1
// 还原a3, a2, a1
ld a3, 16(a0)
ld a2, 8(a0)
ld a1, 0(a0)
// 还原a0
csrrw a0, mscratch, a0
// 使得处理器从中断处理程序返回到中断前的程序执行点
mret
也就是说,实际上的 定时器中断,最终会触发:kerneltrap函数的执行,链路如下:
kernelvec 汇编函数 -- > kerneltrap 函数 --> yield 中断(等待其他CPU也触发中断,或者当前CPU遍历完一次进程列表)获取scheduler的执行流 --> 继续 kerneltrap函数 --> 继续 kernelvec 汇编函数