从零手写操作系统之RVOS硬件定时器-05
- RISC-V 定时器中断
- RISC-V CLINT 介绍
- 寄存器 (Timer 部分)
- 总体框架流程
- 硬件定时器的应用
- 时间管理
- 测试
本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而来,主要作为xv6操作系统学习的一个前置基础。
RVOS是本课程基于RISC-V搭建的简易操作系统名称。
课程代码和环境搭建教程参考github仓库: https://github.com/plctlab/riscv-operating-system-mooc/blob/main/howto-run-with-ubuntu1804_zh.md
前置知识:
- RVOS环境搭建-01
- RVOS操作系统内存管理简单实现-02
- RVOS操作系统协作式多任务切换实现-03
- RISC-V 学习篇之特权架构下的中断异常处理
- 从零手写操作系统之RVOS外设中断实现-04
RISC-V 定时器中断
定时器中断属于本地中断的一类:
Core Local INTerrupt:
Core Local Interrupt(CLINT)是一个与处理器核心相关的中断控制器,它负责处理特定核心的计时器中断和软件中断。
CLINT位于RISC-V系统中的物理内存地址空间,它是一个全局共享的设备,被所有的处理器核心共享和访问。CLINT的作用是为每个处理器核心提供计时器中断和软件中断的控制。
CLINT通常具有以下功能和组成部分:
-
Timer Interrupts(计时器中断):CLINT包含一个或多个计时器,用于生成定时中断。每个计时器都与特定的处理器核心关联,当计时器计数达到预设的值时,CLINT会生成一个中断信号,通知相应的处理器核心。
-
Software Interrupts(软件中断):CLINT可以接收来自处理器核心的软件中断请求。软件中断是由软件代码触发的一种中断请求,用于实现系统调用、任务切换和异常处理等功能。
-
中断控制寄存器(Interrupt Control Registers):CLINT包含一组用于配置和控制中断的寄存器,包括计时器设置寄存器、中断使能寄存器、中断优先级寄存器等。这些寄存器用于配置中断参数、使能或禁用中断,并设置中断的优先级。
总而言之,CLINT是一个处理器核心本地的中断控制器,它提供定时器中断和软件中断的功能,并通过相关的寄存器进行配置和控制。每个处理器核心都可以访问和配置CLINT,以实现对中断的管理和处理。
RISC-V CLINT 介绍
寄存器 (Timer 部分)
全局唯一,表示即使存在多个核,也只会存在一个mtime寄存器
mtime
是RISC-V架构中的一个特殊寄存器,用于表示机器模式下的计时器值。它是Machine Timer(机器计时器)的缩写。
mtime
寄存器通常由硬件提供,用于跟踪系统运行的时间。它的值会不断增加,可以用于测量程序的执行时间、进行时间相关的操作和调度等。
在RISC-V中,mtime
寄存器是一个64位的寄存器,可用于测量长时间间隔,通常以时钟周期或计时器滴答数的形式表示。它的精度和计时精度取决于硬件实现和操作系统的支持。
在操作系统或应用程序中,可以使用mtime
寄存器来实现计时器、延时函数、性能统计等功能。通过读取mtime
寄存器的值,可以获得当前的计时器数值,进而进行时间计算和处理。
需要注意的是,访问mtime
寄存器通常需要特权级别的权限。在特权级别较低的用户态,可能无法直接读取或写入mtime
寄存器,需要通过系统调用或特权级别切换来访问。具体的访问权限和操作方式取决于系统的实现和配置。
mtime
寄存器的递增原理是由硬件实现确定的,通常是由时钟或计时器驱动的。在一个基于时钟的系统中,系统时钟会以固定的频率进行振荡,产生一个稳定的时钟信号。这个时钟信号会被用作各种硬件模块和功能的时序控制。
mtime
寄存器会根据系统时钟信号的脉冲进行递增。每当一个时钟脉冲到达,mtime
寄存器的值会自动加1。这样,随着时钟信号的不断变化,mtime
寄存器的值也会不断地增加。递增速度取决于时钟的频率。如果系统时钟频率为1 MHz,那么每秒钟
mtime
寄存器的值就会增加1000000。因此,可以根据mtime
寄存器的递增速度来进行时间计算和测量。需要注意的是,
mtime
寄存器的递增是硬件自动完成的,无法通过软件或程序直接控制。程序可以通过读取mtime
寄存器的值来获取当前的计时器数值,但无法直接修改或控制其递增过程。递增过程是由硬件实现和时钟信号控制的,程序只能观察和利用其递增的结果。
mtimecmp
寄存器是RISC-V架构中的一个定时器比较寄存器(Timer Compare Register)。它用于与mtime
寄存器进行比较,以实现定时器中断的触发。
当mtime
寄存器的值与mtimecmp
寄存器的值相等时,会触发一个定时器中断。这种机制允许程序根据需要设置定时器中断的触发时机。
具体而言,程序可以通过向mtimecmp
寄存器写入一个比较值,来指定何时触发定时器中断。当mtime
寄存器的值达到或超过这个比较值时,定时器中断被触发,执行相应的中断处理程序。
通过使用mtimecmp
寄存器,程序可以实现定时器相关的功能,如定时任务调度、时间片轮转调度、精确延时等。它为程序提供了一种基于时间的触发机制,使得程序能够按照预定的时间间隔执行特定的操作。
需要注意的是,具体的定时器中断触发机制和中断处理程序的实现方式可能会有所不同,取决于具体的处理器和操作系统。程序需要根据所使用的平台和系统进行相应的配置和编程。
硬件定时器初始化代码如下:
/* 10000000 ticks per-second */
#define CLINT_TIMEBASE_FREQ 10000000
/* interval ~= 1s */
#define TIMER_INTERVAL CLINT_TIMEBASE_FREQ
void timer_init()
{
/*
* On reset, mtime is cleared to zero, but the mtimecmp registers
* are not reset. So we have to init the mtimecmp manually.
*/
//定时器模块初始化---传入interval间隔大约为1s
timer_load(TIMER_INTERVAL);
/* enable machine-mode timer interrupts. */
//开启次级中断中的定时器中断
w_mie(r_mie() | MIE_MTIE);
/* enable machine-mode global interrupts. */
//开启全局中断
w_mstatus(r_mstatus() | MSTATUS_MIE);
}
/* load timer interval(in ticks) for next timer interrupt.*/
void timer_load(int interval)
{
/* each CPU has a separate source of timer interrupts. */
//获取当前hartId
int id = r_mhartid();
//设置mtimecmp寄存器的值为mtime寄存器的值+interval
*(uint64_t*)CLINT_MTIMECMP(id) = *(uint64_t*)CLINT_MTIME + interval;
}
经过如上设置后,大约1秒后,会触发一次时钟中断。
当mtime
中断发生时,处理器核心(hart)会设置mip
寄存器的MTIP
位,表示发生了定时器中断。
在处理定时器中断时,通常需要在mtimecmp
寄存器中写入新的值以清除mip.MTIP
位。具体的操作步骤如下:
- 响应定时器中断,进入中断处理程序。
- 在中断处理程序中,读取
mtime
寄存器的当前值,可以使用类似于uint64_t curr_time = r_mtime();
的方式获取。 - 根据需要,计算下一个定时器中断应该发生的时间,得到一个新的比较值。
- 将新的比较值写入
mtimecmp
寄存器,以设置下一个定时器中断的触发时刻。 - 清除
mip
寄存器的MTIP
位,以告知处理器中断已经处理完毕。可以使用类似于w_mip(r_mip() & ~MIP_MTIP);
的方式清除。
通过在中断处理程序中更新mtimecmp
寄存器,程序可以实现周期性的定时器中断,不断触发指定时间间隔的操作。同时,清除mip.MTIP
位可以确保处理器核心在中断处理程序执行完毕后正确地处理下一个定时器中断。
需要注意的是,具体的操作方式可能因处理器和操作系统的不同而有所差异。因此,以上描述仅为一般情况下的操作流程,具体的实现方式需要参考所使用的平台和系统的文档或相关编程接口。
我们需要在trap_handler处理函数中新增对定时器中断的处理:
reg_t trap_handler(reg_t epc, reg_t cause)
{
reg_t return_pc = epc;
reg_t cause_code = cause & 0xfff;
if (cause & 0x80000000) {
/* Asynchronous trap - interrupt */
switch (cause_code) {
case 3:
uart_puts("software interruption!\n");
break;
//新增对定时器中断的处理
case 7:
uart_puts("timer interruption!\n");
timer_handler();
break;
case 11:
uart_puts("external interruption!\n");
external_interrupt_handler();
break;
default:
uart_puts("unknown async exception!\n");
break;
}
} else {
/* Synchronous trap - exception */
printf("Sync exceptions!, code = %d\n", cause_code);
panic("OOPS! What can I do!");
//return_pc += 4;
}
return return_pc;
}
- 定时器中断具体处理函数
void timer_handler()
{
//记录定时器中断触发次数
_tick++;
printf("tick: %d\n", _tick);
//设置下一次定时器中断触发时机
timer_load(TIMER_INTERVAL);
}
总体框架流程
硬件定时器的应用
时间管理
/* interval ~= 1s */
#define TIMER_INTERVAL CLINT_TIMEBASE_FREQ
static uint32_t _tick = 0;
测试
我们这里简单测试一下定时器中断运行效果:
void start_kernel(void)
{
uart_init();
uart_puts("Hello, RVOS!\n");
page_init();
trap_init();
plic_init();
//初始化定时器模块
timer_init();
sched_init();
os_main();
schedule();
uart_puts("Would not go here!\n");
while (1) {}; // stop here!
}