问题描述
我对裸机编程非常陌生,以前从不中断,但是我一直在学习RISC-V FE310-G002 SOC驱动的开发板上。
我一直在阅读有关RISC-V WFI(等待中断)指令的信息,并且从手册中了解到,听起来好像您不能依靠它来使内核真正进入睡眠状态。相反,它仅建议可以中止系统执行,并且应将指令更像是nop。但是,这对我来说似乎毫无用处。考虑以下ASM程序片段:
wfi_loop:
WFI
J wfi_loop
由于不能依赖WFI,因此必须这样做。但是,从中断处理程序执行MRET时,您仍然会陷入循环。因此,您必须将其设置为以全局变量为条件,该变量的值在中断处理程序中更新。这看起来很混乱。
此外,如果您的实现实际上遵守WFI指令并且在执行WFI指令之前触发了中断,则整个内核将一直停顿,直到触发其他中断为止,因为它将在WFI指令之前返回
似乎该指令的唯一正确用法是在没有工作要做时在内核调度程序内部。但是即使那样,我也不认为您会希望从中断处理程序返回到此类代码中,而是从头开始重新启动调度程序算法。但这也是一个问题,因为您将不得不以某种方式回滚堆栈,等等。
我一直在脑海里转转,似乎无法找出安全的使用方法。也许,如果您是原子的,则可以使用CSRRS启用中断,然后像这样立即调用WFI:
CSRRSI zero,mie,0x80
wfi_loop:
WFI
J wfi_loop
nop
nop
然后在从中断处理程序调用MRET之前,确保将mepc寄存器增加8个字节。返回之前,还必须在中断处理程序内部的mie寄存器中再次禁用该中断。仅当WFI,J和nop都编码为4字节指令时,此解决方案才是安全的,无论是否使用压缩指令。它还由CSRRSI指令使能之后,在有可能触发中断之前取决于程序计数器到达WFI指令。这样一来,便可以在代码中的安全位置触发该中断,并以使中断脱离等待它的循环的方式返回。
我想我只是想了解我可以从硬件获得什么行为,因此,如何正确地从中断中调用和返回并使用WFI指令?
解决方法
因此,您必须将其设置为以全局变量为条件,该变量的值在中断处理程序中更新。
无论wfi
的实现如何,您都必须这样做,因为您不知道是什么事件导致了hart唤醒。
执行wfi
时,您可能启用了 n 个中断,并且其中的任何一个都已引发。
wfi
是优化,它可以节省功耗,直到发生某些情况为止。
如您所述,OS调度程序可能处于无法调度线程的情况(例如,它们都在等待IO或根本没有IO),在这种情况下,它必须执行类似的操作(具有所有必要的可见性和原子性语义): / p>
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的特定实现中可以使用该方法,但是正如从伪代码中看到的那样,它并不那么方便。
禁用除一个中断外的所有中断通常是操作系统无法承受的。
但是,嵌入式应用程序可以。
应该有一个空闲的任务/线程/进程,它应该看起来像您的第一段代码。
由于将空闲线程设置为具有最低优先级,因此,如果空闲线程正在运行,则意味着没有其他线程要运行,或者所有其他线程都被阻塞。
当发生释放某个其他线程的中断的中断时,中断服务例程应恢复该被阻塞的线程,而不是被中断的空闲线程。
请注意,在IO上阻塞的线程本身也被中断—通过自己使用scratch
被中断。该异常是对IO的请求,并导致该线程阻塞-在满足IO请求之前,无法恢复它。
因此,在IO上被阻塞的线程将被挂起,就像被中断一样—时钟中断或IO中断能够恢复与立即中断不同的进程,这种情况将在以下情况下发生:空闲进程正在运行,并且发生了某个进程正在等待的事件。
我要做的是使用scratch
csr指向当前正在运行的进程/线程的上下文块。在中断时,我保存了(开始)服务中断所需的最少数量的寄存器。如果中断导致其他进程/线程变得可运行,那么从中断恢复时,我将检查进程优先级,并可以选择上下文切换而不是恢复被中断的任何内容。如果我恢复被中断的内容,则可以快速恢复。并且要切换上下文,我完成了保存被中断线程的CPU上下文,然后恢复另一个进程/线程,切换了scratch
寄存器。
(对于嵌套中断,我不允许在恢复状态下进行上下文切换,但是在保存当前上下文后,在中断上,我确实将{{1}} csr设置为上下文块的中断堆栈,然后重新启用更高的优先级此外,作为一个非常小的优化,我们可以假设自定义的写入空闲线程不需要任何东西,只需要保存/恢复其pc。)