intended/correct 处理中断和使用 WFI risc-v cpu 指令的方法是什么?

What is intended/correct way to handle interrupts and use the WFI risc-v cpu instruction?

我是裸机编程的新手,以前从未对中断感到厌烦,但我一直在 RISC-V FE310-G002 SOC 供电的开发板上学习。

我一直在阅读有关 RISC-V WFI(等待中断)指令和手册,听起来您不能依靠它来真正让核心休眠。相反,它只是建议可以暂停系统的执行,并且该指令应该更像 NOP 一样对待。但是,这对我来说似乎毫无用处。考虑以下 ASM 程序片段:

wfi_loop:
WFI
J wfi_loop

必须这样做,因为不能依赖 WFI。但是,在中断处理程序发出 MRET 时,您仍然会陷入循环。所以你必须让它成为一个全局变量的条件,该变量的值在中断处理程序中更新。这看起来很乱。

此外,如果您的实现确实遵循 WFI 指令并且在执行 WFI 指令之前触发了中断,则整个内核将停止,直到触发其他中断,因为它将 return 在 WFI 指令之前。

当没有工作要做时,似乎该指令的唯一正确用法是在内核调度程序内部。但即便如此,我认为您也不会想要从中断处理程序 return 进入此类代码,而是从头开始重新启动调度程序算法。但这也会成为一个问题,因为您将不得不以某种方式回滚堆栈等....

我脑子里一直在想这个,但我似乎想不出一个安全的用法。也许,如果您以原子方式,使用 CSRRS 启用中断,然后立即像这样调用 WFI:

CSRRSI zero, mie, 0x80
wfi_loop:
WFI
J wfi_loop
NOP
NOP

然后确保在从中断处理程序调用 MRET 之前将 mepc 寄存器递增 8 个字节。在 returning 之前,还必须在中断处理程序内部的 mie 寄存器中再次禁用中断。该解决方案只有在 WFI、J 和 NOP 都被编码为 4 字节指令时才是安全的,无论是否使用压缩指令。它还取决于程序计数器在被 CSRRSI 指令启用后可能触发中断之前达到 WFI 指令。然后,这将允许在代码中的安全位置触发中断,并以中断等待它的循环的方式return。

我想我只是想了解我可以从硬件中得到什么行为,因此,如何正确地从中断中调用和 return 并使用 WFI 指令?

So you would have to make it conditional against a global variable whose value is updated in the interrupt handler.

无论 wfi 的实现如何,你都必须这样做,因为你不知道是什么事件导致了哈特醒来。
您可能在执行 wfi 时启用了 n 个中断,并且可能已引发其中任何一个中断。

wfi 是一个 优化,它会在发生某些事情之前节省电量。 正如您所指出的,OS 调度程序可能会发现自己处于没有线程可调度的状态(例如,它们都在等待 IO 或者只是有 none),在这种情况下它必须做类似的事情(所有必要的可见性和原子性语义):

while ( ! is_there_a_schedulable_thread());

那只是等待
但是调度程序可以使用:

而不是旋转一个紧密的循环(这可能会损害性能和功率)
while ( ! is_there_a_schedulable_thread())
{
  __wfi();
}

在最坏的情况下,它就像紧密循环一样,在最好的情况下,它会暂停 hart 直到发生外部中断(这意味着可能已完成 IO,因此线程可能会空闲 运行)。

即使在没有线程的情况下,每 x 微秒唤醒一次(由于定时器中断)也比浪费功率循环要好。

wfi 如果您碰巧在中断处理程序上进行了所有工作(例如,当按下按钮或类似操作时),那么对于嵌入式编程也很有用。
在这种情况下,main 函数将永远循环,就像调度程序一样,但没有退出条件。
wfi 指令将大大提高电池寿命。

你不能在任何地方使用 wfi,否则你可能会发现自己正在等待一个从未发生过的中断(事实上,这是一个特权指令)。

将其视为与硬件协调的优化。

特别是,它并不是设计为确保触发中断的方法:

void wait_for_int(int int_num)
{
   //Leave only interrupt int_num enabled
   enable_only_int(int_num);
   __wfi();
   restore_interrupts();
}

如果 RISC-V 的 特定 实现,它可以这样使用,但正如您从 pseudo-code 中看到的那样,它并不是那么方便。
禁用除一个中断之外的所有中断通常是 OS 无法承受的。
不过,嵌入式应用程序可以。

应该有一个 task/thread/process 用于空闲,它应该看起来像您的第一段代码。

由于空闲线程设置为具有最低优先级,如果空闲线程是 运行ning,这意味着没有其他线程可以 运行 或者所有其他线程都是已屏蔽。

当中断发生并解除对其他线程的阻塞时,中断服务例程应该恢复被阻塞的线程而不是被中断的空闲线程。

请注意,在 IO 上阻塞的线程本身也会被中断 — 它通过自己使用 ecall 被中断。该异常是对 IO 的请求并导致此线程阻塞 — 在 IO 请求得到满足之前无法恢复。

因此,在 IO 上阻塞的线程就像被中断一样被挂起 — 时钟中断或 IO 中断能够恢复与立即被中断的进程不同的进程,这将发生在空闲进程正在 运行ning 并且进程正在等待的某些事件发生的情况。


我所做的是使用 scratch csr 指向当前 运行ning process/thread 的上下文块。在中断时,我保存了(开始)服务中断所需的最少数量的寄存器。如果中断导致其他 process/thread 变为 运行able,那么当从中断中恢复时,我会检查进程优先级,并且可能会选择上下文切换而不是恢复被中断的任何内容。如果我恢复被打断的东西,那是快速恢复。为了切换上下文,我完成了保存中断线程的 CPU 上下文,然后恢复另一个 process/thread,切换 scratch 寄存器。

(对于嵌套中断,我不允许在恢复时进行上下文切换,但在保存当前上下文后的中断中,我确实将 scratch csr 设置为 [=34 之前的上下文块中断堆栈=] 更高优先级的中断。此外,作为一个非常小的优化,我们可以假设自定义写入的空闲线程除了它的 pc saved/restored 之外不需要任何东西。)