news 2025/12/22 21:08:23

xv6与opensbi的定时器中断

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
xv6与opensbi的定时器中断

在实现了第一个系统调用myHelloWorld、虚存管理后,为了实现能够做到分时系统的进程管理,我们需要启用定时器中断。

寄存器

为了实现定时器中断,你需要知道(牢记)如下寄存器,这些寄存器是你在处理定时器中断时特别关心的。

scause

里面存放了中断/异常的原因。

scause存放的内容与对应的含义如下表

例如当发生S mode软件中断时,scause=0x8000000000000001;发生S mode定时器中断时,scause=0x8000000000000005

sip

用于表示S mode 当前挂起的是什么中断。sip的p表示挂起(pending)

SEIE STIE SSIE表示外部设备中断、定时器中断、软件中断是否被挂起。

sie

用于控制S mode是否启用对应中断

SEIE STIE SSIE表示是否启用(enable)外部设备中断、定时器中断、软件中断

mip

mip寄存器各标志位如下图。我们会注意到一件事情,这里面有sip的SEIP、STIP、SSIP位。原因是sip与mip在物理上是同一个寄存器,但是由于它们的视图view不同,一个是s mode的视角,一个是m mode的视角,所以sip并没有mip的MTIP等位,sip是看不到也访问不到的。

mtime

用于计时,mtime不是一个 CSR(控制状态寄存器),而是一个 内存映射 I/O(MMIO) 寄存器。

因此在xv6中查看这个寄存器中的时间不是通过csrw智能,而是访存。

// memlayout.h 相关宏定义 // 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.
// start.c->timerinit()中初始化定时器 // 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; ...

不过我们还有另一个方式来查看定时器中的时间,就是rdtime,这是一条非特权级指令,所以可以在用户态使用。rdtime对于使用opensbi实现的方法十分关键,因为opensbi方式下,我们总是没有m mode权限的,也就访问不了mtime。

定时器中断使能、配置、处理

虽然按照顺序应当启用→配置→处理,但由于有不同的方式,所以我们先来看看现有的处理方式。

xv6的定时器中断处理方式与sbi的方式有差异,先说xv6。不同xv6的代码有些许区别,这里使用的是2022年的xv6方案。

xv6定时器中断处理方案

xv6的时间中断处理是这样的。用户态发生定时器中断时,直接进入m mode,跳转到mtvec寄存器存放的入口,即timervec(位于kernelvec.S中),它会重置timer时间并写sip寄存器的SSIP位,将其转变成一个S mode的软件中断。

# timervec ... li a1, 2 csrw sip, a1 ... mret

在timervec最后执行mret后,按照我的理解,应当会回到sepc中存放的入口,如果是在用户态发生的定时器中断,那这个入口应该是usertrap函数。在usertrap函数里面通过scause并结合devintr函数分析。可以转到对应的处理位置,如在这里,我们希望能进行yield(),让当前进程状态变成RUNNABLE,并切换到另一个RUNNABLE进行,从而实现我们的分时,time slice时间片。

opensbi定时器中断处理方案

用户态发生定时器中断时,同样会到m mode对应的handler中去,下面展示的是将_trap_handler入口写入MTVEC的代码

/* handler的配置代码 ,位于固件中,需要通过open_sbi的仓库查看 */ /* Setup trap handler */ lla a4, _trap_handler csrr a5, CSR_MISA srli a5, a5, ('H' - 'A') andi a5, a5, 0x1 beq a5, zero, _skip_trap_handler_hyp lla a4, _trap_handler_hyp _skip_trap_handler_hyp: csrw CSR_MTVEC, a4

之后会来到

/* sbi_trap.c */ static int sbi_trap_nonaia_irq(unsigned long irq) { switch (irq) { case IRQ_M_TIMER: sbi_timer_process(); break; case IRQ_M_SOFT: sbi_ipi_process(); break; case IRQ_M_EXT: return sbi_irqchip_process(); default: if (irq == sbi_pmu_irq_bit()) { sbi_pmu_ovf_irq(); return 0; } return SBI_ENOENT; } return 0; }

其中sbi_timer_process()为

void sbi_timer_process(void) { csr_clear(CSR_MIE, MIP_MTIP); /* * If sstc extension is available, supervisor can receive the timer * directly without M-mode come in between. This function should * only invoked if M-mode programs the timer for its own purpose. */ if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(), SBI_HART_EXT_SSTC)) csr_set(CSR_MIP, MIP_STIP); }

我们注意到会将MIP_STIP位写入MIP寄存器的,根据上面有关MIP寄存器的介绍,我们知道也即写入了SIP.STIP位。所以变成了一个S mode的时间中断,而这便是与xv6中的不同,xv6中是视为S mode的软件中断,而这里是视为S mode的时间中断。

由于sbi实现方式下mtvec存放的是sbi的内容,而不像是xv6中我们自己定义的timervec。所以后续我们还需要在S mode识别出这是一个时间中断后,重置定时器。

定时器中断使能

以sbi方案为例,根据描述我们知道我们需要启用m mode的时间中断,s mode的时间中断。m mode我们是不用管的,所以我们只用配置sie寄存器就好了。由于之后可能还有其它中断,例如磁盘的外部设备中断等,所以我们sie寄存器还是启用了三个标志位。

w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE);

定时器中断配置

我们需要配置在sbi的_trap_handler执行mret后的跳转地址,按照我的理解应当是kernelvec、uservec。不过这一部分理论上应该在我们实现系统调用的中断处理时就已经完成了。

除了这部分配置,我们还要记得重置定时器。

首先是timerinit()中,第一次重置定时器,我是调用了自己实现的reset_timer

void reset_timer() { // ask the CLINT for a timer interrupt. int interval = 1000000; // cycles; about 1/10th second in qemu. uint64 n; asm volatile("rdtime %0" : "=r"(n)); set_timer(n + interval); }

这里面的set_timer是我的自定义函数,本质使用的是sbi_calllong sbi_set_timer(uint64_t stime_value )想要实现这个sbi_call,我们需要知道它的EID以及FID

参考 RISC-V Supervisor Binary Interface Specification这份pdf

然后按照sbi call规范进行调用就好了

long set_timer(uint64 next_time) { return sbi_call_wrapper( SBI_EXT_TIME, // EID: 0, // FID: 0 next_time, // arg0 (a0): 绝对时间值 0, // arg1 (a1): 忽略 0 // arg2 (a2): 忽略 ); }
static inline int sbi_call_wrapper( uint64 ext_id, uint64 func_id, uint64 arg0, uint64 arg1, uint64 arg2) { register uint64 a0 asm("a0") = arg0; register uint64 a1 asm("a1") = arg1; register uint64 a2 asm("a2") = arg2; register uint64 a6 asm("a6") = func_id; // FID 放入 a6 register uint64 a7 asm("a7") = ext_id; asm volatile("ecall" : "=r"(a0) // 输出:a0 寄存器的值作为返回值 : "r"(a0), "r"(a1), "r"(a2), "r"(a6), "r"(a7) // 输入:a0, a1, a2, a6, a7 : "memory"); // 告诉编译器内存可能被修改 // 返回值 a0 通常包含错误码(0 表示成功) return a0; }

见下图,然后是之后每次发生定时器中断的时候,都要重置定时器。我是放在devintr里实现的。因为不管是u mode 还是 s mode,发生定时器中断的时候都会使用这个函数,

之所以if中为scause == 0x8000000000000001L || scause == 0x8000000000000005L,是因为原来xv6中的实现方案仅仅为0x8000000000000001L,但我的为0x8000000000000005L为了兼容旧版本(其实时不敢轻易动这部分代码),所以我并没有删掉0x8000000000000001L

int devintr() { uint64 scause = r_scause(); // 定时器中断、软件中断 if (scause == 0x8000000000000001L || scause == 0x8000000000000005L) { // software interrupt from a machine-mode timer interrupt, // forwarded by timervec in kernelvec.S. reset_timer(); if (cpuid() == 0) { clockintr(); } // acknowledge the software interrupt by clearing // the SSIP bit in sip. w_sip(r_sip() & ~2); return 2; } else { // 其他外设中断全部忽略 return 0; } }

阶段性总结

如是,我们便应该能实现分时系统。但是怎么测试呢,感觉最让人有把握的方式应该是能成功运行fork系统调用。

相关资料

RISC-V Supervisor Binary Interface Specification
The RISC-V Instruction Set Manual: Volume II
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/13 18:58:20

Code Surfer终极指南:让代码演示生动起来的完整教程

Code Surfer终极指南&#xff1a;让代码演示生动起来的完整教程 【免费下载链接】code-surfer Rad code slides <&#x1f3c4;/> 项目地址: https://gitcode.com/gh_mirrors/co/code-surfer 还在为枯燥的代码演示而烦恼吗&#xff1f;&#x1f914; 想要让你的技…

作者头像 李华
网站建设 2025/12/19 5:48:39

统计接口耗时的6种常见方法

为什么统计接口耗时如此重要&#xff1f;在深入方法之前&#xff0c;我们先聊聊为什么接口耗时统计这么关键。从架构师的角度看&#xff0c;这不仅仅是“记录一个时间”那么简单。接口耗时直接反映了系统性能&#xff0c;它是&#xff1a;性能优化的基石&#xff1a;没有耗时数…

作者头像 李华
网站建设 2025/12/13 18:57:12

最新版!Python从入门到全栈开发的保姆级路线图

作为一名软件测试工程师&#xff0c;掌握一门高效、灵活的编程语言对职业发展至关重要。Python 因其简洁的语法、强大的生态和广泛的应用场景&#xff0c;成为测试人员进阶自动化测试、性能测试乃至全栈开发的首选语言。 一、为什么测试工程师必须学 Python&#xff1f; ‌自动…

作者头像 李华
网站建设 2025/12/13 18:57:09

从零到一:用LangChain + Ollama搭建你的专属本地知识库

一、为何测试工程师需要本地知识库&#xff1f; 软件测试过程中产生的文档&#xff08;如测试用例、需求说明书、缺陷报告&#xff09;通常分散在多个平台&#xff0c;导致知识复用困难。通过本地知识库可实现&#xff1a; 隐私保障&#xff1a;敏感测试数据无需上传云端&…

作者头像 李华
网站建设 2025/12/13 18:56:07

路由器的5G和手机上的5G是一个意思吗?深度解析两大区别

5G这个词&#xff0c;它既出现在路由器的宣传中&#xff0c;也贯穿于手机的标志上。那这两个5G是相同的吗&#xff1f;本文将带你深入了解。本质上的不同尽管名称相似&#xff0c;但这两是完全不同的两种技术。手机上的5G指的是第五代移动通信技术&#xff0c;是继4G之后的下一…

作者头像 李华
网站建设 2025/12/13 18:56:06

React(一):使用react-router构建导航应用

前言由于C#用的真的太舒服了&#xff0c;导致我其它语言其它框架都只是浅浅的了解一下&#xff0c;没写过多少代码&#xff0c;就偶尔用下Python。现在AI时代其实熟练了一门语言&#xff0c;去学习其它语言其它框架其实是很方便的&#xff0c;AI就是最好的老师&#xff0c;基础…

作者头像 李华