Nucleus 实时操作系统中断(上)
Nucleus 实时操作系统中断(上)
Interrupts in the Nucleus SE RTOS
所有现代微处理器和微控制器都有某种中断设施。这种能力对于提供许多应用程序所需的响应能力是必不可少的。当然,响应性和可预测性也是使用实时操作系统背后的一个关键目标,因此这两个主题确实存在轻微的冲突。使用中断可能会损害操作系统的实时完整性。这一主题,以及冲突的解决方法,目前不讲。在这里,我们将了解Nucleus SE使用的中断处理策略。
在所有情况下,中断都不是由Nucleus SE控制的,它们是在中断发生时根据优先级进行处理的,并以通常的方式进行矢量化。它们的执行时间只是从运行主线应用程序代码和调度程序的可用时间中“偷走”的。显然,这意味着所有的中断服务程序都应该简单、简短和快速。
本机中断和托管中断
Nucleus SE确实提供了两种处理中断的方法:“本机”中断服务例程没有什么特别的,并且与操作系统交互的机会有限(至少在选择优先级调度器时是这样);“托管”中断服务例程可以进行更广泛的API调用。
通过一些进入/退出宏,与Nucleus SE应用程序一起使用的中断服务例程可以被指定为本机或托管的。
本机中断
Nucleus SE本机中断是标准的中断服务例程,您可以将其视为“非托管的”。当am中断可能以很高的频率发生并且需要以非常低的开销进行服务时,通常使用它们。由于许多现代嵌入式编译器都支持通过interrupt关键字编写中断服务例程,所以这个例程很可能是用C编写的。唯一保存的上下文信息是编译器认为必要的信息。这导致本机中断例程可以执行的操作有很大的限制,我们将很快看到。
要构造Nucleus SE本机中断服务例程,只需按通常的方式编写ISR,包括在开始时调用NUSE_NISR_Enter()宏,在末尾调用NUSE_NISR_Exit()。这些宏在nuse_types.h中定义,只需将全局变量nuse_Task_State设置为nuse_NISR_CONTEXT。
托管中断
如果您需要在ISR可以执行的操作方面有更大的灵活性,Nucleus SE管理的中断可能是解决方案。与本机中断的关键区别是上下文保存。托管中断不仅仅允许编译器堆叠几个寄存器,而是在输入时保存完整的任务上下文(在上下文块中)。然后从当前任务的上下文块中加载当前任务的上下文。这考虑到了当前任务可能被ISR代码的操作改变的可能性;当优先级调度器正在使用时,这是完全可能的。包含了对Nucleus SE上下文保存和恢复的完整描述。
显然,完整的上下文保存比由本机中断执行的几个寄存器的堆栈开销更高。这是额外灵活性的代价,也是为什么可以选择处理中断的方法的原因。
托管中断是使用NUSE_managed_ISR()宏构造的,该宏在NUSE_types.h中定义。此宏构造包含以下序列的函数:
- task context save
- set NUSE_Task_State to NUSE_MISR_CONTEXT
- call user-supplied ISR code function
- restore NUSE_Task_State to previous setting
- task context restore
宏接受两个参数:中断的名称,用作构造的例程的函数名;包含用户提供的ISR逻辑的函数名。
本文后面将介绍Nucleus SE实时时钟ISR,它是受管ISR的一个示例。
中断服务例程的API调用
可以从本机或托管ISR调用的API函数的范围取决于选择了哪个调度程序。一般来说,优先级调度程序的使用为在API函数调用的结果下调用调度程序提供了许多机会,这在本机ISR中是一个问题。
使用优先级调度程序从本机ISR调用API
允许使用优先级调度程序从本机ISR调用有限范围的API函数。这一限制是由于Nucleus SE API的灵活性造成的–许多调用都会导致任务准备就绪,并且本地ISR不可能调用调度器(因为任务上下文未被保存)。如果不启用任务阻塞,则具有更大的灵活性。
始终允许以下API调用:
NUSE_Task_Current()
NUSE_Task_Check_Stack()
NUSE_Task_Information()
NUSE_Task_Count()
NUSE_Partition_Pool_Information()
NUSE_Partition_Pool_Count()
NUSE_Mailbox_Information()
NUSE_Mailbox_Count()
NUSE_Queue_Information()
NUSE_Queue_Count()
NUSE_Pipe_Information()
NUSE_Pipe_Count()
NUSE_Semaphore_Information()
NUSE_Semaphore_Count()
NUSE_Event_Group_Information()
NUSE_Event_Group_Count()
NUSE_Signals_Send()
NUSE_Timer_Control()
NUSE_Timer_Get_Remaining()
NUSE_Timer_Reset()
NUSE_Timer_Information()
NUSE_Timer_Count()
NUSE_Clock_Set()
NUSE_Clock_Retrieve()
NUSE_Release_Information()
但是,唯一真正有用的是NUSE_Signals_Send(),因为这提供了一个很好的方法来指示任务需要一些工作。
如果禁用了阻塞(这意味着许多API调用可能无法使任务就绪),则可以使用许多其他API函数:
NUSE_Partition_Allocate()
NUSE_Partition_Deallocate()
NUSE_Mailbox_Send()
NUSE_Mailbox_Receive()
NUSE_Mailbox_Reset()
NUSE_Queue_Send()
NUSE_Queue_Receive()
NUSE_Queue_Jam()
NUSE_Queue_Reset()
NUSE_Pipe_Send()
NUSE_Pipe_Receive()
NUSE_Pipe_Jam()
NUSE_Pipe_Reset()
NUSE_Semaphore_Obtain()
NUSE_Semaphore_Release()
NUSE_Semaphore_Reset()
NUSE_Event_Group_Set()
NUSE_Event_Group_Retrieve()
但是,唯一真正有用的是NUSE_Signals_Send(),因为这提供了一个很好的方法来指示任务需要一些工作。
如果禁用了阻塞(这意味着许多API调用可能无法使任务就绪),则可以使用许多其他API函数:
NUSE_Task_Suspend()
NUSE_Task_Resume()
NUSE_Task_Sleep()
NUSE_Task_Relinquish()
NUSE_Task_Reset()
NUSE_Signals_Receive()
来自托管ISR或具有非优先级调度程序的本机ISR的API调用
当使用run-to-completion、round-robin或time-sliced调度器时,可以从ISR调用范围更广的API函数。如果使用类似的ISR优先级,则受管调度器有助于提高ISR的优先级。这是因为允许调用可能导致调度不同的任务。NUSE_Reschedule()中的代码有助于此功能,该代码检测调用的上下文是ISR,并禁止上下文切换(允许它在ISR结束时发生)。调度程序操作的完整细节在前面的一篇文章中介绍过。
一个关键的要求是ISR中的API调用不能导致当前任务的挂起,例如等待一个资源。换句话说,这种调用应该在suspend选项设置为NUSE_NO_suspend的情况下进行。
鉴于此规定,可使用以下API调用:
NUSE_Task_Current()
NUSE_Task_Check_Stack()
NUSE_Task_Information()
NUSE_Task_Count()
NUSE_Task_Suspend()
NUSE_Task_Resume()
NUSE_Task_Reset()
NUSE_Partition_Allocate()
NUSE_Partition_Deallocate()
NUSE_Partition_Pool_Information()
NUSE_Partition_Pool_Count()
NUSE_Mailbox_Send()
NUSE_Mailbox_Receive()
NUSE_Mailbox_Reset()
NUSE_Mailbox_Information()
NUSE_Mailbox_Count()
NUSE_Queue_Send()
NUSE_Queue_Receive()
NUSE_Queue_Jam()
NUSE_Queue_Reset()
NUSE_Queue_Information()
NUSE_Queue_Count()
NUSE_Pipe_Send()
NUSE_Pipe_Receive()
NUSE_Pipe_Jam()
NUSE_Pipe_Reset()
NUSE_Pipe_Information()
NUSE_Pipe_Count()
NUSE_Semaphore_Obtain()
NUSE_Semaphore_Release()
NUSE_Semaphore_Reset()
NUSE_Semaphore_Information()
NUSE_Semaphore_Count()
NUSE_Event_Group_Set()
NUSE_Event_Group_Retrieve()
NUSE_Event_Group_Information()
NUSE_Event_Group_Count()
NUSE_Signals_Send()
NUSE_Timer_Control()
NUSE_Timer_Get_Remaining()
NUSE_Timer_Reset()
NUSE_Timer_Information()
NUSE_Timer_Count()
NUSE_Clock_Set()
NUSE_Clock_Retrieve()
NUSE_Release_Information()
一些API调用是不允许的,因为它们与当前任务相关:
NUSE_Task_Relinquish()
NUSE_Signals_Receive()
NUSE_Task_Sleep()
实时时钟ISR
实时时钟(RTC)ISR是Nucleus SE提供的唯一完整的中断服务例程。除了为Nucleus SE提供所有所需的定时功能外,它还可以作为如何编写受管中断的示例。
RTC ISR操作
RTC ISR提供的设施已在前面的一篇文章中概述,其中涵盖了Nucleus SE中的系统时间这一广泛主题。根据应用程序的配置方式,所有功能都是可选的。这是RTC ISR的完整代码。
#if NUSE_TIMER_NUMBER != 0
{
U8 timer;
for (timer=0; timer
<nuse_timer_number; timer++)="">
{
if
(NUSE_Timer_Status[timer])
{
if (–NUSE_Timer_Value[timer] == 0)
{
NUSE_Timer_Expirations_Counter[timer]++;
#if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT ||
NUSE_INCLUDE_EVERYTHING
if (NUSE_Timer_Expiration_Routine_Address[timer]
!= NULL)
{
((PF1)NUSE_Timer_Expiration_Routine_Address[timer])
NUSE_Timer_Expiration_Routine_Parameter[timer]);
}
#endif
/* reschedule? */
if
(NUSE_Timer_Reschedule_Time[timer] != 0)
{
/* yes: set up time */
NUSE_Timer_Value[timer] =
NUSE_Timer_Reschedule_Time[timer];
}
else
{
/* no: disable */
NUSE_Timer_Status[timer] = FALSE;
}
}
}
}
}
#endif
#if NUSE_SYSTEM_TIME_SUPPORT || NUSE_INCLUDE_EVERYTHING
NUSE_Tick_Clock++;
#endif
#if NUSE_TASK_SLEEP || NUSE_INCLUDE_EVERYTHING
{
U8 task;
for (task=0; task
<nuse_task_number; task++)=""> {
if (NUSE_Task_Timeout_Counter[task]
!= 0)
{
NUSE_Task_Timeout_Counter[task]–;
if (NUSE_Task_Timeout_Counter[task] == 0)
{
NUSE_Wake_Task(task);
}
}
}
}
#endif
#if NUSE_SCHEDULER_TYPE == NUSE_TIME_SLICE_SCHEDULER
if (–NUSE_Time_Slice_Ticks == 0)
{
NUSE_Reschedule();
}
#endif
我们将研究RTC ISR的四个功能领域:
计时器
如果配置了任何应用程序计时器,ISR将通过递减其计数器值来循环为每个计时器提供服务。如果计时器过期(即计数器达到零),则两个操作将生效:
如果配置了计时器过期例程,并且计时器具有指向函数的有效(非空)指针(在NUSE_timer_expiration_Routine_Address[]),则执行例程,并从NUSE_timer_expiration_Routine_parameter[]接收参数。
如果计时器有一个重定时时间(即NUSE_timer_reschedule_time[]中的非零值),则计时器将重新加载该值。
应用程序计时器在上一篇文章中有更详细的描述。
系统时钟
如果配置了系统时钟,那么NUSE_Tick_clock的值只会递增。关于系统时间的进一步讨论可以在上一篇文章中找到。
任务睡眠
如果启用了任务休眠(即配置了API调用NUSE_task_sleep()),则会检查每个任务的超时计数器(NUSE_task_timeout_counter[]中的条目),如果不是零,则递减。如果任何计数器达到零,则相应的任务将被唤醒。
时间片调度
如果正在使用时间片调度程序,则时间片计数器(NUSE_time_slice_Ticks)将递减。如果达到零,则调用调度程序。对NUSE_Reschedule()的调用负责重置计数器。
有管理的中断
对RTC ISR是受管中断的原因进行一些解释可能是有用的,因为在正确的情况下,用户可能希望将其重新编码为本机中断,以减少开销。例如,如果只使用系统时间工具(即没有应用程序计时器、没有任务睡眠和时间片调度器),则本机中断就可以了。管理中断的需求如下:
如果使用了计时器并配置了过期例程,这些例程可能会(从中断上下文)进行API调用,从而导致重新调度。它们受到与从isr发出的API调用相同的限制(请参阅本章前面的部分)。
如果优先级调度程序正在使用中,任务休眠到期可能需要调度更高优先级的任务。
如果正在使用时间片调度程序,它肯定会从RTC ISR调用,因此必须使用托管中断。