处理中断和使用 WFI risc-v cpu 指令的预期/正确方法是什么?

Ech*_*Ray 7 assembly interrupt interrupt-handling riscv riscv32

我对裸机编程非常陌生,以前从未接触过中断,但我一直在 RISC-V FE310-G002 SOC 供电的开发板上学习。

我一直在阅读有关 RISC-V WFI(等待中断)指令的内容,并且从手册中来看,听起来您不能依靠它来实际休眠核心。相反,它仅建议系统可以停止执行,并且应该将指令视为 NOP。然而,这对我来说似乎毫无用处。考虑以下 ASM 程序片段:

wfi_loop:
WFI
J wfi_loop
Run Code Online (Sandbox Code Playgroud)

由于不能依赖 WFI,因此必须执行此操作。然而,从中断处理程序中执行 MRET 后,您仍然会陷入循环中。因此,您必须使其以全局变量为条件,该全局变量的值在中断处理程序中更新。这看起来非常混乱。

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

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

我一直在脑子里思考这个问题,但似乎无法找到安全的用途。也许,如果您以原子方式使用 CSRRS 启用中断,然后立即调用 WFI,如下所示:

CSRRSI zero, mie, 0x80
wfi_loop:
WFI
J wfi_loop
NOP
NOP
Run Code Online (Sandbox Code Playgroud)

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

我想我只是想了解我可以从硬件中获得什么行为,以及如何正确调用中断并返回并使用 WFI 指令?

Mar*_*oom 5

因此,您必须使其以全局变量为条件,该全局变量的值在中断处理程序中更新。

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

wfi是一种优化,它可以节省电量,直到发生某些事情。正如您所指出的,操作系统调度程序可能会发现自己处于没有线程可调度的情况(例如,它们都等待 IO 或者根本没有),在这种情况下它必须执行类似的操作(具有所有必要的可见性和原子性语义):

while ( ! is_there_a_schedulable_thread());
Run Code Online (Sandbox Code Playgroud)

那只是等待
但调度程序可以使用以下方法,而不是旋转一个紧密的循环(这可能会损害性能和功耗):

while ( ! is_there_a_schedulable_thread())
{
  __wfi();
}
Run Code Online (Sandbox Code Playgroud)

在最坏的情况下,它就像紧密循环一样,在最好的情况下,它会暂停 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();
}
Run Code Online (Sandbox Code Playgroud)

考虑到 RISC-V 的特定实现,它可以以这种方式使用,但正如您从伪代码中看到的那样,它并不是那么方便。
禁用除一个中断之外的所有中断通常是操作系统无法承受的。
不过,嵌入式应用程序可以。


Eri*_*idt 4

应该有一个用于空闲的任务/线程/进程,并且它应该看起来像您的第一段代码。

\n

由于空闲线程被设置为具有最低优先级,因此如果空闲线程正在运行,则意味着要么没有其他线程可以运行,要么所有其他线程都被阻塞。

\n

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

\n

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

\n

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

\n
\n

我所做的是使用scratchcsr 指向当前正在运行的进程/线程的上下文块。\xc2\xa0 在中断时,我保存(开始)服务中断所需的最少数量的寄存器。\xc2\xa0 如果中断导致其他进程/线程变得可运行,然后当从中断恢复时,我检查进程优先级,并且可以选择上下文切换而不是恢复被中断的内容。\xc2\xa0 如果我恢复被中断的内容,它会很快Restore.\xc2\xa0 为了切换上下文,我完成保存中断线程的 CPU 上下文,然后恢复另一个进程/线程,切换寄存器scratch

\n

(对于嵌套中断,我不允许在恢复时进行上下文切换,但在保存当前上下文后的中断中,我确实将 csr 设置scratch为上下文块的中断堆栈,然后再重新启用更高优先级的中断。\xc2\xa0 另外,作为一个非常小的优化,我们可以假设自定义编写的空闲线程除了保存/恢复其 PC 之外不需要任何东西。)

\n