3.6 Tasks
Task函数、信号量、事件和邮箱模块都会改变task对象的执行状态:阻塞或结束当前运行的task,准备好之前挂起的task,重新调度当前task等诸如此类。
当task被抢占,task栈可能即需要包含两个中断Hwi上下文(如果被Hwi抢占),又需要包含一个中断Hwi上下文和一个Task抢占上下文(如果被更高优先级Task抢占)。因为Hwi上下文大于Task上下文,所以数字给定的是两个Hwi上下文。如果任务被阻塞,仅有那些C函数必须保存的记录被保存在task栈。
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)---任务 - 阿巴睇 - 阿巴睇的博客](http://img1.ph.126.net/eYa2IgRb4SEeUCn_mKwHUA==/6631577939097698887.jpg)
只有一个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)---任务 - 阿巴睇 - 阿巴睇的博客](http://img0.ph.126.net/uVE3ASvSndvby7VlaHJ6nw==/6631708780981425936.jpg)
![基于TI-RTOS的CC2650DK开发(8)---任务 - 阿巴睇 - 阿巴睇的博客](http://img2.ph.126.net/e-q89YiYkEpwVzkB8SixEw==/6631590033725619737.jpg)
另一个寻找正确栈尺寸的途径是让栈尺寸最大,使得Code Composer Studio软件去寻找实际使用的栈尺寸。
参阅3.4.2节获取有关Hwi使用的系统栈信息,参阅3.5.2节获取系统栈尺寸信息。
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);
|