关于RTOS的全部真相。 第十三条 任务数据结构和不受支持的API调用



在第三篇也是最后一篇任务文章中,我将研究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_READYNUSE_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 * 2

Nucleus 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_PREEMPTNU_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_PREEMPTNU_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。

Source: https://habr.com/ru/post/zh-CN425353/


All Articles