
在
上一篇文章中,我们研究了RTOS支持的各种类型的计划以及Nucleus SE中的相关功能。 在本文中,我们将介绍Nucleus SE中的其他计划选项以及保存和还原上下文的过程。
该系列中的先前文章:
第9条。 调度程序:实施第8条 Nucleus SE:内部设计和部署第7条 Nucleus SE:简介第6条。 其他RTOS服务第5条 任务交互和同步第4条 任务,上下文切换和中断第3条 任务与计划第2条。 RTOS:结构和实时模式
第1条 RTOS:简介。
可选功能
在开发Nucleus SE的过程中,我将最大数量的功能设置为可选,从而节省了内存和/或时间。
挂起任务
如之前在
“规划器:实施”文章中所述 ,Nucleus SE支持各种用于暂停任务的选项,但是此功能是可选的,并且包含在
nuse_config.h中的
NUSE_SUSPEND_ENABLE符号中。 如果设置为
TRUE ,则数据结构定义为
NUSE_Task_Status [] 。 这种类型的暂停适用于所有任务。 该数组的类型为
U8 ,其中两个半字节分别使用。 低4位包含任务的状态:
NUSE_READY,NUSE_PURE_SUSPEND ,
NUSE_SLEEP_SUSPEND ,
NUSE_MAILBOX_SUSPEND等。 如果通过API调用挂起任务(例如
NUSE_MAILBOX_SUSPEND ),则高4位包含挂起任务的对象的索引。 当资源可用时将使用此信息,并调用API需要找出哪些挂起的任务需要恢复。
要执行任务挂起,需要使用一对调度程序函数:
NUSE_Suspend_Task()和
NUSE_Wake_Task() 。
NUSE_Suspend_Task()代码如下:

该函数保存任务的新状态(全部8位),该状态作为suspend_code参数获得。 启用锁定时(请参见下面的“锁定API调用”),将
保存NUSE_SUCCESS返回代码。 接下来,
调用NUSE_Reschedule()将控制权转移到下一个任务。
NUSE_Wake_Task()代码非常简单:

任务的状态设置为
NUSE_READY 。 如果未使用优先级调度程序,则当前任务将继续占用处理器,直到需要释放资源为止。 如果使用优先级调度程序,则
NUSE_Reschedule()会以任务索引作为完成指示,因为任务可能具有更高的优先级并且必须立即执行。
锁定API调用
Nucleus RTOS支持许多API调用,如果资源不可用,开发人员可以通过它们调用(暂停)任务。 资源再次可用时,任务将恢复。 此机制也已在Nucleus SE中实现,并且适用于许多内核对象:可以将任务锁定在内存部分,事件组,邮箱,队列,通道或信号量中。 但是,与Nucleus SE中的大多数工具一样,它是可选的,并且由
nuse_config.h中的
NUSE_BLOCKING_ENABLE符号定义。 如果设置为
TRUE ,则定义数组
NUSE_Task_Blocking_Return [] ,其中包含每个任务的返回码; 它可能是
NUSE_SUCCESS或代码
NUSE_MAILBOX_WAS_RESET ,指示在锁定任务时重置了对象。 启用锁定后,将使用条件编译将相应的代码包含在API函数中。
调度台
Nucleus RTOS计算自任务创建和上次重置以来已安排了多少次任务。 此功能也在Nucleus SE中实现,但它是可选的,由
nuse_config.h中的
NUSE_SCHEDULE_COUNT_SUPPORT符号定义。 如果设置为
TRUE ,
则创建U16类型的
NUSE_Task_Schedule_Count []数组,该数组将每个任务的计数器存储在应用程序中。
任务的初始状态
在Nucleus RTOS中创建任务后,可以选择其状态:就绪或暂停。 在Nucleus SE中,默认情况下,所有任务都可以在启动时准备好。 在
nuse_config.h中使用
NUSE_INITIAL_TASK_STATE_SUPPORT符号选择的选项允许您选择启动状态。
NUSE_Task_Initial_State []数组在
nuse_config.c中定义,并且需要为应用程序中的每个任务初始化
NUSE_READY或
NUSE_PURE_SUSPEND 。
保存上下文
第3条“任务和计划”中提出了使用RTC(运行至完成)以外的任何类型的计划程序维护任务上下文的想法。 如前所述,有几种维护上下文的方法。 鉴于Nucleus SE不是为32位处理器设计的,我选择使用表而不是堆栈来维护上下文。
ADDR类型
NUSE_Task_Context [] []的二维数组用于保存应用程序中所有任务的上下文。 行是
NUSE_TASK_NUMBER (应用程序中的任务数),列是
NUSE_REGISTERS (需要保存的寄存器数;取决于处理器,并在
nuse_types.h中设置
) 。
当然,维护上下文和恢复代码取决于处理器。 这是绑定到特定设备(和开发环境)的唯一Nucleus SE代码。 我将举例说明ColdFire处理器的保存/恢复代码。 尽管由于处理器过时,此选择可能看起来很奇怪,但其汇编程序比大多数现代处理器的汇编程序更易于阅读。 该代码非常简单,可以用作为其他处理器创建上下文切换的基础:

当需要上下文切换时,在NUSE_Context_Swap中调用此代码。 使用两个变量:
NUSE_Task_Active ,当前任务的索引,必须保留其上下文;
NUSE_Task_Next ,要加载其上下文的任务的索引(请参见“全局数据”部分)。
上下文保存过程如下:
- 寄存器A0和D0临时存储在堆栈中;
- A0配置为指向上下文块数组NUSE_Task_Context [] [] ;
- D0使用NUSE_Task_Active加载并乘以72(ColdFire有18个寄存器,需要72个字节来存储);
- 然后将D0添加到A0 ,它现在指向当前任务的上下文块;
- 然后将寄存器存储在上下文块中; 首先是A0和D0 (来自堆栈),然后是D1-D7和A1-A6 ,然后是SR和PC (来自堆栈,我们将研究快速启动的上下文切换),最后保存堆栈指针。
上下文加载过程是相反的操作序列:
- A0配置为指向上下文块数组NUSE_Task_Context [] [] ;
- 使用NUSE_Task_Active加载D0 ,并乘以72。
- 然后将D0添加到A0 ,它现在指向新任务的上下文块(由于应该在保存序列的相反过程中完成上下文加载,因此首先需要堆栈指针);
- 然后从上下文块中恢复寄存器; 首先,将堆栈指针,然后将PC和SR压入堆栈,然后装入D1-D7和A1-A6 ,并在D0和A0的末尾。
实现上下文切换的困难在于,许多处理器都很难访问状态寄存器(对于ColdFire,这是
SR )。 常见的解决方案是中断,即程序中断或条件分支中断,这导致
SR与
PC一起加载到堆栈上。 这就是Nucleus SE在ColdFire上的工作方式。 宏
NUSE_CONTEXT_SWAP()在
nuse_types.h中设置,该宏扩展为:
asm(“陷阱#0”);以下是上下文块的初始化代码(
nuse_init.c中的NUSE_Init_Task() ):

这就是堆栈指针,
PC和
SR初始化的方式。 前两个具有由用户在
nuse_config.c中设置的值。
SR的值在
nuse_types.h中定义为
NUSE_STATUS_REGISTER字符。 对于ColdFire,此值为
0x40002000 。
全球数据
Nucleus SE调度程序只需要很少的内存来存储数据,但是,当然,它使用与任务相关联的数据结构,下面的文章将对此进行详细讨论。
RAM数据
调度程序不使用ROM中的数据,而是将2到5个全局变量放置在RAM中(所有变量都在
nuse_globals.c中设置),具体取决于所使用的调度程序:
- NUSE_Task_Active - U8类型的变量,包含当前任务的索引;
- NUSE_Task_State - U8类型的变量,包含一个值,该值指示当前正在执行的代码的状态,该状态可以是任务,中断处理程序或启动代码; 可能的值是: NUSE_TASK_CONTEXT , NUSE_STARTUP_CONTEXT , NUSE_NISR_CONTEXT和NUSE_MISR_CONTEXT ;
- NUSE_Task_Saved_State - U8类型的变量,用于在托管中断中保护NUSE_Task_State的值;
- NUSE_Task_Next - U8类型的变量,包含下一个任务的索引,应该为除RTC之外的所有调度程序进行调度;
- NUSE_Time_Slice_Ticks-类型为U16的变量,其中包含时间片的计数器; 仅与TS调度程序一起使用。
调度程序数据足迹
Nucleus SE调度程序不使用ROM数据。 RAM数据的确切数量取决于所使用的调度程序:
- 对于RTC-2个字节( NUSE_Task_Active和NUSE_Task_State );
- 用于RR和优先级-4个字节( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State和NUSE_Task_Next );
- 对于TS,为6个字节( NUSE_Task_Active , NUSE_Task_State , NUSE_Task_Saved_State , NUSE_Task_Next和NUSE_Time_Slice_Ticks )。
实施其他计划机制
尽管Nucleus SE提供了涵盖大多数情况的4种调度程序供您选择,但开放式体系结构允许您为其他情况实现机会。
与后台任务进行时间分片
如
第3条“任务和调度”中所述,简单的时间片调度程序具有局限性,因为它限制了处理器可以执行任务的最大时间。 一个更复杂的选项是增加对后台任务的支持。 可以在分配给已暂停任务的任何插槽上安排此类任务,并在部分释放插槽时运行。 这种方法使您可以按预定的时间间隔安排任务,并以预计的处理器核心时间百分比完成任务。
优先与循环(RR)
在大多数实时内核中,优先级调度程序在每个优先级级别上支持多个任务,这与Nucleus SE不同,在Nucleus SE中,每个任务都有唯一的级别。 我优先选择后者,因为它大大简化了数据结构,从而简化了调度程序代码。 为了支持更复杂的体系结构,将需要大量的ROM和RAM表。
关于作者: Colin Walls在电子行业工作了30多年,大部分时间用于固件。 他现在是Mentor Embedded(Mentor Graphics的一个部门)的固件工程师。 Colin Walls经常在会议和研讨会上发表演讲,他撰写了许多技术文章并撰写了两本有关固件的书。 居住在英国。
Colin的专业
博客 ,电子邮件:colin_walls@mentor.com。
关于翻译:尽管尽管在某些地方已经描述了过时的方法,但该系列文章似乎很有趣,作者向训练有素的读者介绍了实时OS的功能。 我本人属于
俄罗斯RTOS的创建者团队,我们
打算免费提供它 ,我希望这一周期对新手开发人员有用。