
在第三篇也是最后一篇任务文章中,我将研究Nucleus SE数据结构,并描述未在Nucleus SE中实现的RTOS API调用以及其他兼容性问题。
该系列中的先前文章:
第十二条 任务处理服务第11条 任务:API的配置和介绍第10条 计划程序:高级功能和上下文保留第9条。 调度程序:实施第8条 Nucleus SE:内部设计和部署第7条 Nucleus SE:简介第6条。 其他RTOS服务第5条 任务交互和同步第4条 任务,上下文切换和中断第3条 任务与计划第2条。 RTOS:结构和实时模式
第1条 RTOS:简介。
资料结构
任务使用各种数据结构(在RAM和ROM中),就像其他Nucleus SE对象一样,它们是一组表,其大小与所选任务和参数的数量相对应。
我强烈建议应用程序代码使用API函数而非直接访问这些数据结构。 这样可以避免不必要的副作用,与Nucleus SE的未来版本不兼容,并且还简化了将应用程序移植到Nucleus RTOS的过程。 为了更好地理解服务调用代码的操作和调试过程,下面对数据结构进行详细说明。
RAM中托管的内核数据结构
这些数据结构包括:
NUSE_Task_Context [] [] -类型为
ADDR的二维数组,每个任务都有一行。 列数取决于控制器体系结构,并且由
NUSE_REGISTERS符号确定,该符号在
nuse_types.h中定义。 调度程序使用此数组保存每个任务的上下文,并在文章#10的“保存上下文”一节中进行了详细说明。 如果使用RTC调度程序,则不会创建。
NUSE_Task_Signal_Flags []-U8类型的数组,如果启用了信号则创建该数组,并且每个任务包含8个信号标志。 信号将在以下文章之一中讨论。
NUSE_Task_Timeout_Counter []是
U16类型的数组,由每个任务的减法计数器组成,并且在激活对API
NUSE_Task_Sleep()的调用时创建。
NUSE_Task_Status [] -类型为U8的数组,包含每个任务的状态
-NUSE_READY或挂起状态。 仅在激活任务挂起时创建。
NUSE_Task_Blocking_Return []-U8类型的数组,如果激活了API调用阻止,则创建该数组。 它包含一个返回代码,将在阻止API调用后使用。 它通常包含
NUSE_SUCCESS或指示该对象已被重置的代码(例如
NUSE_MAILBOX_WAS_RESET )。
NUSE_Task_Schedule_Count []-U16类型的数组,包含每个任务的计数器,并且仅在调度程序计数已激活时才创建。
NUSE_Task_Context [] []主要由零初始化,除了与状态寄存器(状态寄存器,SR),程序计数器(程序计数器,PC)和堆栈指针(堆栈指针,SP)相对应的条目外,这些条目被分配了初始值(请参见“数据在“ ROM”下面),并且在启动Nucleus SE时,所有其他数据结构
NUSE_Init_Task()都分配为零。 以下文章之一将包含Nucleus SE启动过程的完整列表及其说明。
以下是nuse_init.c文件中包含的数据结构的定义。

RAM用户数据
用户必须为每个任务定义一个堆栈(如果未使用RTC调度程序)。 这些应该是
ADDR阵列,通常在
nuse_config.c中定义。 地址和堆栈大小应分别放在任务条目
NUSE_Task_Stack_Base []和
NUSE_Task_Stack_Size []中 (请参阅ROM中的数据)。
ROM数据
ROM存储与任务相关的一到四个数据结构。 确切数量取决于所选参数:
NUSE_Task_Start_Address []是
ADDR类型的数组,每个任务都有一个条目,它是指向该任务的代码入口点的指针。
NUSE_Task_Stack_Base []是
ADDR类型的数组,每个任务都有一个条目,它是指向该任务堆栈的基地址的指针。 如果使用RTC以外的任何调度程序,则创建此数组。
NUSE_Task_Stack_Size []是
U16类型的数组,每个任务都有一个条目,该数组显示任务的堆栈大小(以字为单位)。 如果使用RTC以外的任何调度程序,则创建此数组。
NUSE_Task_Initial_State []是
U8类型的数组,每个任务都有一个条目,它显示任务的初始状态。 它可以是
NUSE_READY或
NUSE_PURE_SUSPEND 。 如果选择了对任务初始状态的支持,则会创建此数组。
这些数据结构在
nuse_config.c中声明和初始化(静态):

用于存储任务数据的内存量(任务数据足迹)
像所有Nucleus SE核心对象一样,存储数据所需的内存量是可以预测的。
所有应用程序任务所需的ROM大小(以字节为单位):
NUSE_TASK_NUMBER * sizeof(ADDR)另外,如果选择了除RTC以外的任何调度程序:
NUSE_TASK_NUMBER *(sizeof(ADDR)+2)另外,如果选择了对任务初始状态的支持:
NUSE_TASK_NUMBER要将数据存储在RAM中,内存量(以字节为单位)由所选参数确定,如果没有选择任何参数,则内存量可以为零。
如果选择了RTC以外的调度程序:
NUSE_TASK_NUMBER * NUSE寄存器* sizeof(ADDR)另外,如果选择了信号支持:
NUSE_TASK_NUMBER另外,如果激活了对NUSE_Task_Sleep()API的调用:
NUSE_TASK_NUMBER * 2此外,如果激活了任务挂起:
NUSE_TASK_NUMBER另外,如果激活了API调用阻止功能:
NUSE_TASK_NUMBER另外,如果调度程序计数器已激活:
NUSE_TASK_NUMBER * 2Nucleus SE中未实现的API调用
下面列出了Nucleus SE中未实现的Nucleus RTOS中可用的七个API调用。
创建任务
此API调用创建一个应用程序任务。 Nucleus SE不需要此功能,因为任务是静态创建的。
调用原型:
状态NU_Create_Task(NU_TASK *任务,CHAR *名称,VOID(* task_entry)(UNSIGNED,VOID *),UNSIGNED argc,VOID * argv,VOID * stack_address,UNSIGNED stack_size,OPTION优先级,UNSIGNED time_slice,OPTION优先级参数:
任务 -指向用户任务控制块的指针,可以用作其他API调用中任务的句柄/链接(“句柄”);
name-指向任务名称的指针,一个由7个字符组成的字符串,结尾为零;
task_entry-指示任务的输入功能;
argc-可用于将初始信息传递给任务的
UNSIGNED数据元素;
argv-可用于将信息传输到任务的指针;
stack_address-设置任务堆栈的内存的初始扇区;
stack_size-指示堆栈中的字节数;
优先级 -表示任务的优先级值:从0到255,其中较低的数字表示最高的优先级;
time_slice-指示此任务期间可以经过的最大时间
片数。 值为“ 0”将禁用此任务的时间片。
preempt-指示任务是否被取代。 可以具有值
NU_PREEMPT和
NU_NO_PREEMPT ;
auto_start-显示任务的初始状态。
NU_START表示任务已准备好执行,而
NU_NO_START意味着任务已挂起。
返回值:
NU_SUCCESS-表示服务成功完成;
NU_INVALID_TASK-指示指向任务控制单元的指针为
NULL ;
NU_INVALID_ENTRY-指示任务输入功能的指针为
NULL ;
NU_INVALID_MEMORY-指示由stack_address参数分配的内存扇区为零(
NULL );
NU_INVALID_SIZE-指示指定的堆栈大小不足;
NU_INVALID_PREEMPT-指示
抢占参数设置不正确;
NU_INVALID_START-指示
auto_start参数设置不正确。
删除任务
此API调用将删除先前创建的必须
完成或
终止的应用程序任务。 Nucleus SE也不需要此调用,因为任务是静态创建的,无法删除。
调用原型:
状态NU_Delete_Task(NU_TASK *任务);参数:
task-指向任务控制块的指针
返回值:
NU_SUCCESS-表示服务成功完成;
NU_INVALID_TASK-指示指向任务的指针设置不正确;
NU_INVALID_DELETE-表示任务未处于“已完成”或“已终止”状态。
获取任务指针
该API调用构成了指向系统中所有任务的指针的顺序列表。 Nucleus SE不需要它,因为任务是使用简单的索引而不是指针来标识的。
调用原型:
UNSIGNED NU_Task_Pointers(NU_TASK **指针列表,UNSIGNED maximum_pointers);参数:
pointer_list-指向
NU_TASK指针数组的指针。 该数组将填充指向系统中安装的任务的指针。
maximum_pointers-可以放置在数组中的最大指针数。
返回值:
放置在数组中的
NU_TASK指针的数量。
更改任务优先级
此API调用为任务赋予了新的优先级。 在Nucleus SE中,由于任务优先级是恒定的,因此不需要。
调用原型:
OPTION NU_Change_Priority(NU_TASK *任务,OPTION new_priority);参数:
任务 -指向任务控制块的指针;
new_priority-将优先级从0设置为255。
返回值:
先前的任务优先级值。
更改任务抢占算法
此API调用更改了正在进行的任务排挤的顺序。 Nucleus SE不需要它,因为它使用了更简单的调度算法。
调用原型:
OPTION NU_Change_Preemption(OPTION抢占);参数:
preempt-新的
抢占算法,接受
NU_PREEMPT或
NU_NO_PREEMPT返回值:
用于挤出任务的先前算法。
更改任务时间片
此API调用更改特定任务的时间片。 Nucleus SE不需要它,因为任务时间片是固定的。
调用原型:
UNSIGNED NU_Change_Time_Slice(NU_TASK *任务,UNSIGNED time_slice);参数:
任务 -指向任务控制块的指针;
time_slice-在此任务期间可以经过的最大时间
片数;此字段的值为零将禁用此任务的时间量化。
返回值:
任务时间量的先前值。
终止任务
此API调用完成特定任务。 Nucleus SE不需要此功能,因为不支持
终止状态。
调用原型:
状态NU_Terminate_Task(NU_TASK *任务);参数:
task-指向任务控制块的指针。
返回值:
NU_SUCCESS-表示服务成功完成;
NU_INVALID_TASK-指示任务指针不正确。
兼容Nucleus RTOS
开发Nucleus SE时,主要目标之一是确保与Nucleus RTOS的代码高度兼容。 任务也不例外,从用户的角度来看,它们的实现方式与Nucleus RTOS中的实现方式几乎相同。 我得出了一些不兼容的结论,认为这种不兼容是可以接受的,因为最终代码更容易理解,并且可以更有效地使用内存。 但是,除了这些不兼容之外,其余的Nucleus RTOS API调用几乎都可以直接用作Nucleus SE调用。 以下文章之一将提供有关从Nucleus RTOS到Nucleus SE过渡的更多详细信息
对象标识符
在Nucleus RTOS中,所有对象均由特定类型的数据结构(控制单元)描述。 指向该控制单元的指针用作任务的标识符。 在Nucleus SE,我决定需要一种不同的方法来有效利用内存。 所有内核对象均由RAM和/或ROM中的一组表描述。 这些表的大小由对象类型的数量决定。 特定对象的标识符是这些表中的索引。 因此,我将
NUSE_TASK定义为与
U8等效。 这种类型的变量(不是指针)用作任务的标识符。 这是一个很小的不兼容性,很容易弄清楚代码是移植到Nucleus RTOS还是从Nucleus RTOS移植出来。 对象标识符通常被存储和发送不变。
Nucleus RTOS还支持任务命名。 这些名称仅用于调试。 我从Nucleus SE中排除了它们以节省内存。
任务状态
在Nucleus RTOS中,任务可以处于以下几种状态之一:正在
执行 ,
准备就绪 ,
已挂起 (这导致不确定性:任务处于待机状态或被API调用阻止),已
终止或已完成。
Nucleus SE还支持
执行和
就绪状态。 所有三个“
挂起”选项均受支持。 不支持
终止和完成。 没有API调用即可完成任务。 外部任务函数绝不应显式或隐式返回值(这将在Nucleus RTOS中导致
完成状态)。
未实现的API调用
Nucleus RTOS支持16个办公室呼叫以处理任务。 其中有7个未在Nucleus SE中实现。 上面描述了它们的描述以及它们被排除的原因。
在下一篇文章中,我们将开始研究RTOS内存管理。
关于作者: Colin Walls在电子行业工作了30多年,大部分时间用于固件。 他现在是Mentor Embedded(Mentor Graphics的一个部门)的固件工程师。 Colin Walls经常在会议和研讨会上发表演讲,他撰写了许多技术文章并撰写了两本有关固件的书。 居住在英国。
Colin的专业
博客 ,电子邮件:colin_walls@mentor.com。