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_call long 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

需求响应动态冰蓄冷系统需求响应策略的优化研究(Matlab代码实现)内容概要:本文围绕需求响应动态冰蓄冷系统及其优化策略展开研究,结合Matlab代码实现,探讨了在电力需求侧管理背景下,冰蓄冷系统如何通过优化运行策略参需求响应,以实现削峰填谷、降低用电成本和提升能源利用效率的目标。研究内容包括系统建模、负荷预测、优化算法设计(如智能优化算法)以及多场景仿真验证,重点分析不同需求响应机制下系统的经济性和运行特性,并通过Matlab编程实现模型求解结果可视化,为实际工程应用提供理论支持和技术路径。; 适合人群:具备一定电力系统、能源工程或自动化背景的研究生、科研人员及从事综合能源系统优化工作的工程师;熟悉Matlab编程且对需求响应、储能优化等领域感兴趣的技术人员。; 使用场景及目标:①用于高校科研中关于冰蓄冷系统需求响应协同优化的课题研究;②支撑企业开展楼宇能源管理系统、智慧园区调度平台的设计仿真;③为政策制定者评估需求响应措施的有效性提供量化分析工具。; 阅读建议:建议读者结合文中Matlab代码逐段理解模型构建算法实现过程,重点关注目标函数设定、约束条件处理及优化结果分析部分,同时可拓展应用其他智能算法进行对比实验,加深对系统优化机制的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值