Skip to content

Latest commit

 

History

History
135 lines (100 loc) · 5.86 KB

chapter.05.3.traps_sys_call_3.md

File metadata and controls

135 lines (100 loc) · 5.86 KB

5.4: Timer interrupts

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.

1. timerinit函数

// 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);
}

2. timervec 汇编函数

        #
        # 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 汇编函数