基于TI-RTOS的CC2650DK开发(8)---任务

judy的头像

3.6 Tasks

SYS/BIOS task对象是由Task模块管理的线程。Tasks的优先级高于Idle Loop并低于硬件中断和软件中断。参阅video introducing Tasks进行概览。

Task模块基于task的优先级和当前task的运行状态动态地安排和抢占tasks。 这确保了处理器永远运行那些拥有最高优先级的线程。tasks的优先级总共有32个级别,默认优先级是16。MSP430和C28x的最高优先级是16。最低优先级0保留用于运行Idle Loop。

Task模块提供了一系列函数用于控制task对象。它们通过Task_Handle类型句柄访问Task对象。

内核为每个task对象维护了一个处理器寄存器拷贝。每个task都有自己的运行时栈用于存储本地变量以及下一层嵌套函数调用。见3.6.3节获取更多关于任务栈尺寸的输电网信息。

所有运行于单个程序中的tasks共享一个公共的全局变量集合,按照为C函数定义的标准规则范围进行访问。

3.6.1 创建Tasks

你即可以通过调用Task_create()来动态创建Task,也可以在配置文件中静态创建。动态创建的Tasks也可以在程序运行期间动态删除。

3.6.1.1 动态创建和删除Tasks

你可以通过调用Task_create()来产生SYS/BIOS tasks,它的参数包括一个新task开始时所执行的C函数地址。Task_create()的返回值是Task_Handle类型的句柄,它可以作为参数传递给其他任务函数。
下面是创建一个task的C代码:

Task_Params taskParams;
Task_Handle task0;
Error_Block eb;
Error_init(&eb);
/* Create 1 task with priority 15 */
Task_Params_init(&taskParams);
taskParams.stackSize = 512;
taskParams.priority = 15;
task0 = Task_create((Task_FuncPtr)hiPriTask, &taskParams, &eb);
if (task0 == NULL)
{
System_abort("Task create failed");
}

如果传递的不是一个实际的Task_Params结构体而是NULL,则使用默认参数集。“eb”是一个在Task对象创建期间可用于处理错误的错误块。见3.6.3节获取task栈尺寸相关信息。

当任务创建时拥有更高的优先级,它会抢占当前运行的task并成为活动task。

调用Task_delete()可回收Task对象所使用的内存的栈空间。Task_delete()将task从所有的内部队列中移除,并释放task对象和栈。

任何task所持有的信号量或其它资源将不会被释放。删除任务所持有的这类资源通常是一个设计错误,虽然不一定如此。大多数情况下,这类资源应在task删除前被释放。这是在结束或待用状态下唯一可以安全删除Task的方法。
Void Task_delete(Task_Handle *task);

3.6.1.2 静态创建Tasks

你也可以在配置脚本中静态地创建tasks。在配置中,你可以为每个task和Task管理器本身设置多个属性。

所有任务属性的完整描述,请参考在线文档中"ti.sysbios.knl"包文档的Task模块。(获取运行在线帮助的相关信息,请参考1.6.1节23页:Using the API Reference Help System)。

一个静态创建的task和动态创建的task在运行时的行为模式完全一样。你无法使用Task_delete()函数去删除静态创建的tasks。

Task模块自动创建Task_dle任务并赋于其最低优先级(0)。它在没有更高优先级的Hwi,Swi或Task在运行时,执行为Idle对象所定义的函数。

当你将任务配置为相同的优先级,它们将以在配置脚本中的创建顺序执行。Tasks的默认优先级为16,最高到32。MSP430和C28x的最高优先级为16。最高优先级序号为优先级数量-1,最低为0。优先级0保留用于系统Idle任务。你无法通过在单个优先级内排列task的运行顺序。

如果希望一个任务最开始为不活动状态,可将其优先级设为-1。这样的task将不会安排运行,直到它们的优先级在运行时提高。

3.6.2 Task运行状态和调度

每个Task对象总是处于以下几种状态之一:
Task_Mode_RUNNING:意味着这是一个系统CPU正在运行的task。
Task_Mode_READY:意味着此task在获取CPU资源后按计划运行。
Task_Mode_BLOCKED:意味着此task直到系统中的指定事件发生前都无法执行。
Task_Mode_TERMINATED:意味着任务“结束”,不会再被执行。
Task_Mode_INACTIVE:意味着task的优先级为-1,处于前期准备状态。在运行时可调用 Task_setPri()来设置已经被创建的task的优先级。

Tasks会按照应用程序所赋与的优先级顺序运行。正在运行的task不能超过一个。通常来说,处于准备状态的task的优先级不会大于正在运行的task的优先级,因为拥有更高优先级的准备task会抢占正在运行的task。不同于很多分时操作系统的给每个task“公平分享”CPU时间,无论何时,只要一个更高优先级的task准备运行,SYS/BIOS都会即时抢占当前task。

最大优先级为Task_numPriorities-1 (default=15; maximum=31),最小优先级为1。如果优先级小于0,task将禁止进一步执行,直到稍后它的优先级被其它task提升。如果优先级等于Task_numPriorities-1,则此task不能被其它task抢占。最高优先级的task仍可以通过调用 Semaphore_pend()、Task_sleep()以及其它阻塞调用来允许低优先级任务运行。可以在运行时调用Task_setPr()来改变一个任务的优先级。

在程序运行过程中,每个任务的运行模式都有可能被几种原因被改变。图3-7显示了运行模式是如何改变的。

基于TI-RTOS的CC2650DK开发(8)---任务

Task函数、信号量、事件和邮箱模块都会改变task对象的执行状态:阻塞或结束当前运行的task,准备好之前挂起的task,重新调度当前task等诸如此类。

只有一个task的运行模式可以处于Task_Mode_RUNNING。如果所有的tasks都被阻塞,并且没有Hwi或Swi在运行,Task会执行Task_idle task,它的优先级低于系统中所有其它的task。当task被Hwi或Swi抢占,通过Task_stat()返回的运行模式仍为Task_Mode_RUNNING,因为当抢占结束,task仍会运行。(这句啥意思呢?说说我的理解,Task_stat()要执行,也得等Hwi或Swi完成后才能执行,那时候task已经又开始运行了,所以会有这个结果)

注意:不要在Idle函数中做阻塞调用,如Semaphore_pend()或Task_sleep(),那会导致程序终止。

如果Task_Mode_RUNNING的task转化为任意其它三种状态,则控制会切换至处于准备状态的最高优先级的task去运行(运行模式为 Task_Mode_READY)。一个Task_Mode_RUNNING的task可通过以下几个途径转化为其它模式:

● 通过调用Task_exit()将运行中的task变成Task_Mode_TERMINATED状态,如果一个task从其最顶层函数返回,也会自去调用这个函数。所有tasks返回后,Task管理器通过调用状态码为0的System_exit()来中止程序运行。

● 当运行中的task调用一个导致其挂起的函数(如Semaphore_pend()或Task_sleep() ),会让其进入Task_Mode_BLOCKED状态。tasks可以执行某些特定I/O操作、等待某些共享资源可用性或空闲时进入此种状态。

● 无论何时,运行中的task被抢占后,都会变为Task_Mode_READY状态,更高优先级的task准备运行。 Task_setPri()可导致这种转变,如果当前task的优先级不再是系统中的最高优先级。一个task也可以使用Task_yield()将控制权移交给相同优先级的task。被转交的task将准备运行。

一个当前状态为Task_Mode_BLOCKED的task在收到某些特定事件时可将其状态变为准备运行状态:完成了一个I/O操作、共享资源可用、指定周期的时间消逝等诸如此类。变为Task_Mode_READY的好处是此任务可按照它的优先级预定执行;当然如果它的优先级高于正在运行的task,它会立即转为运行状态。相同优先级的task按照先到先行的准备运行。

3.6.3 Task栈

内核为每个Task对象维护了一份处理器寄存器的拷贝。每个Task都拥有自己的运行时栈,用于存储本地变量以及更深一层的函数调用嵌套。

你可以在静态或动态创建Task对象时单独为每个Task对象指定栈尺寸。
每个任务栈必须足够大,以处理它的普通函数调用以及两个满中断Hwi上下文。

下表的 "Maximum Stack Consumed"列显示了承担最坏情况的中断嵌套所需的栈空间。这些数字表现的两个满Hwi中断上下文加上本地变量在task调度器中所使用的空间。额外的嵌套中断上下文被压进公共的系统栈。

注意,当内核配置中没有Task Hook的配置(Task.minimizeLatency = false和BIOS.logsEnabled = false),每个任务栈除支持task调度器的本地变量外,仅需支持单个满Hwi中断上下文。这个配置等于表3-7中的 "Minimum Stack Consumed" 。

基于TI-RTOS的CC2650DK开发(8)---任务

当task被抢占,task栈可能即需要包含两个中断Hwi上下文(如果被Hwi抢占),又需要包含一个中断Hwi上下文和一个Task抢占上下文(如果被更高优先级Task抢占)。因为Hwi上下文大于Task上下文,所以数字给定的是两个Hwi上下文。如果任务被阻塞,仅有那些C函数必须保存的记录被保存在task栈。

另一个寻找正确栈尺寸的途径是让栈尺寸最大,使得Code Composer Studio软件去寻找实际使用的栈尺寸。

3.6.4 栈溢出测试

当任务使用的内存大于它的栈所分配的内存,它可能写入到其它task或数据的内存区域。这会导致无法预知的潜在致使后果。因此经常会用到一种检测栈溢出的手段。

默认情况下,Task模块在每个Task切换时会检查Task栈是否溢出。这会提高任务切换的延迟,你可以将Task.checkStackFlag设置为false来禁用此项功能。

Task_stat()函数可用于查看栈尺寸。它返回的结构体包含栈的尺寸和在此栈上曾经使用的最大MAUs数量,这样,此代码段可用于警告几乎所有的满栈:

Task_Stat statbuf; /* declare buffer */
Task_stat(Task_self(), &statbuf); /* call func to get status */
if (statbuf.used > (statbuf.stackSize * 9 / 10))
{
System_printf("Over 90% of task's stack is in use.\n")
}

请参阅在线文档中的 "ti.sysbios.knl"包获取Task_stat()相关信息。
你可以使用RTOS Object View (ROV)来检测运行时Task栈的使用情况。获取更多信息,请参考7.5.3节。

3.6.5 Task Hooks

Task模块支持以下Hook函数:

Register:在任意静态创建Tasks前调用,在运行时初始化。register hook在启动时,main()和中断使能前调用。

Create:在Task创建时调用。包括静态创建和使用Task_create()或Task_construct()动态创建的Task。

Create hook在Task_disable/enable块之外调用,并且在task被加入准备列表前调用。

Ready:Task将要运行时调用。ready hook在中断使用时的Task_disable/enable块内调用。

Switch:在task切换时调用。“prev”和“next” task句柄作为参数传递给切换hook。“prev”在SYS/BIOS启动期间初始化task切换时被设为NULL。切换hook在中断使用时Task_disable/enable块中调用。

Exit:当task使用Task_exit()退出时调用。exit hook是退出的task传递的句柄。exit hook在Task_disable/enable块之外调用,并在task从内核列表中被移除前调用。

Delete:task通过Task_delete()在运行时删除时调用。

以下HookSet结构体类型囊括了Task模块支持的所有hook函数:

typedef struct Task_HookSet
{
Void (*registerFxn)(Int); /* Register Hook */
Void (*createFxn)(Handle, Error.Block *); /* Create Hook */
Void (*readyFxn)(Handle); /* Ready Hook */
Void (*switchFxn)(Handle, Handle); /* Switch Hook */
Void (*exitFxn)(Handle); /* Exit Hook */
Void (*deleteFxn)(Handle); /* Delete Hook */
};

当定义超过一个hook集,则需使用hook ID序号来调用公共类型的单个hook函数。
Task hook函数只能静态配置。

3.6.5.1 Register函数

Register函数用于存储hook所对应的hook ID。此ID可传递给Task_setHookContext()和Task_getHookContext()用于设置或获取指定hook的上下文。如果hook的执行需要使用Task_setHookContext()或Task_getHookContext(),则必须指定Register函数。

register函数在系统初始化期间中断使能前调用。
Register函数语法如下:
Void registerFxn(Int id);

3.6.5.2 Create和Delete函数

无论何时,Task创建和删除时都会调用Create和Delete函数。Create函数所传递的Error_Block由应用程序的Memory_alloc()所传递,这需要额外的存储空间。

createFxn和deleteFxn函数在中断可用时调用(启动时或main()除外)。

它们的语法如下:

Void createFxn(Task_Handle task, Error_Block *eb);
Void deleteFxn(Task_Handle task);

3.6.5.3 Swith函数

如果指定了Swith函数,它会在切换至新task时调用。switch函数在中断可用时调用。
此函数可用于保存/恢复附加的task上下文(例如外部硬件寄存器)、检查task栈溢出和监测每个task使用时间等目的。

switchFxn语法如下:
Void switchFxn(Task_Handle prev, Task_Handle next);

3.6.5.4 Ready函数

如果指定了Ready函数,它会在task将要运行时调用。Ready函数在中断可用时调用(启动时或main()除外)。

readyFxn语法如下:
Void readyFxn(Task_Handle task);

3.6.5.5 Exit函数

如果指定了Exit函数,它在task退出时调用(当task通过调用Task_exit()返回它的main函数)。exitFxn函数在中断可用时调用。

exitFxn语法如下:
Void exitFxn(Task_Handle task);

来源: abatei的博客