第三章 实时多任务
1. 非实时应用系统可以利用单进程或多进程结构按事件发生的顺序依次处理,而实时系统则要求按照发生事件的轻重缓急(优先级)来安排处理顺序。对某些事件,强实时系统甚至要求在给定的时间内处理完成。
2. 从系统角度看,任务是竞争系统资源的最小运行单元。
3. VxWorks的每个任务拥有一个任务控制块TCB(Task Control Block),主要用于记录任务的状态、资源和参数。
4. 任何任务,在某一时刻只能处于一种状态中,基本状态包括:挂起状态(Suspend)、准备状态(Ready)、阻塞状态(Pend)、延迟状态(Delay)和运行状态(Running)。
5. 延迟状态实际也是一种特殊的阻塞状态,只不过延迟状态是在等待时间资源。
6.
7. 任务被创建后处于挂起状态,必须经过进一步激活之后,任务才可能进入准备状态。
8. 除了运行状态外,任务在其他状态之间进行迁移并不会引起上下文切换。
9. VxWorks支持256个优先级(0~255,0优先级最高,255最低)。一般来说,程序的优先级在其生存期中是固定的,如果必要,也可调用taskPrioritySet()来动态改变任务的优先级。
10.决定Ready任务调度的一个因素是调度算法。VxWorks支持两种调度算法。第一种算法为基于优先级的抢占式调度算法,第二种微同一优先级任务可选择采用时间片轮转法。系统默认第一种算法。
11.基于优先级的抢占式调度算法:该算法的基本思想是,一个具有更高优先级的任务一旦进入Ready状态,将抢占当前运行任务的CPU资源,进行上下文切换后进入运行状态。这种算法优点是很明显的,而且符合大多数实时系统的调度需求,因而也是最常见的一种选择。但其缺点是:在多个任务具有相同的优先级情况下,如果当前正在执行的任务一直不被阻塞(阻塞可能是因为申请某种未准备好的资源进入Pend状态,或主动暂时放弃CPU进入Delay状态),它将一直占用CPU,从而造成其他同优先级或低优先级的任务不能被执行。在任务结构设计中:要使每个任务都尽可能短的占用CPU,并且必须保证每个任务都有被阻塞或主动阻塞自身之处,只有系统中唯一一个最低优先级的任务除外。
12.同一优先级的时间片轮转调度算法:对于多个具有相同优先级且都处于Ready状态的任务来说,时间片轮转法能使他们分时共享CPU。在这种调度算法下,VxWorks把任务分为多个组,每组任务具有相同的优先级。从组内第一个任务开始,每个任务在执行一个时间片(Time Slice)后让出CPU,下一个任务开始执行。。。。直到所有具有相同优先级的任务都执行过了之后,同组中第一个任务再重新得到CPU。通过函数KernelTimeSlice(int Ticks)来允许时间片轮转,参数ticks表示轮转时间,即每个任务得到的CPU时间。当某个任务在运行状态下被一个更高优先级的任务抢占,它的时间片定时器将被保存。当它重新得到CPU时,定时器继续计数。在被更高优先级任务抢占的情况下,他会排在相应优先级队列的队头。而在阻塞后,这个任务将被放在相应优先级队列的队尾,同时计时器清0。
13.根据应用需要,任务可以使用taskLock()暂时关闭VxWorks的优先级抢占调度机制。一旦调度机制被某个任务关闭,在其后继的执行过程中,任何高优先级的任务都不能抢占CPU。只有在这个任务被阻塞或者被挂起后,高优先级的任务才可能被执行。一旦原任务重新开始执行,调度机制再次处于关闭状态。使用taskUnlock()恢复正常调度。
14.taskLock()只能关闭调度任务,但不能关闭中断,一旦中断发生,中断服务程序同样会暂时打断任务的执行(但不发生上下文切换)。可以使用intLock()和intUnlock()来暂时关闭和打开中断。
15.taskSpawn():用来完成任务的创建和最基本的任务的资源分配、初始化和激活操作。
16. taskInit():创建的任务处于挂起状态,必须随后调用taskActivate()函数来激活这个任务,而taskSpawn()则同时完成这两步动作。
17.taskActivate()函数入参中的任务ID可以通过强制转换taskInit()中的pTCB参数得到。
18.任务中一些重要资源和参数的说明:
(1):堆栈:创建任务的时候需要预先估计任务所使用的堆栈大小,这里有个简便的方法。首先,在初始化时可以先将堆栈分配足够大,然后在任务运行过程中不时调用函数checkStack()来观察堆栈的实际使用状况,再适当进行调整。一般情况下,建议安全配置的堆栈空间要超过实际最大使用空间的10%-20%。
(2):任务ID和任务名:在VxWorks5.x中,约定可以用值为0的ID表示当前任务,代替当前任务的实际ID值。
taskName():由Task ID得到任务名。
taskNameToId():由任务名得到任务ID。
taskIdSelf():得到调用该函数的任务的ID号。
taskIdVerify():判断是否存在具有某个ID的任务。
(3):任务信息:下面是相关函数
taskIdListGet():获取所有活动任务的ID
taskInfoGet():得到指定任务的信息
taskPriorityGet():查看指定任务的优先级
taskRegsGet():查看指定任务的寄存器(当前任务出外)
taskRegsSet():设置指定任务的寄存器(当前任务出外)
taskIsSuspended():查看指定任务是否处于Suspend状态
taskIsReady():查看指定任务是否处于Ready状态
taskTcb():得到指定任务的任务控制模块的指针
(4)任务的删除:任务可以自行删除,也可以被其他任务删除,任务删除时自动释放在其创建时申请的系统资源。但不会自动释放任务执行中所申请的其他系统资源。任务自行删除可以使用return语句推出任务函数或者调用exit()函数;删除其他任务可以调用taskDelete()函数(实际上此函数也可以用于删除当前任务)。
(1):钩子函数:钩子函数是与其他函数配套使用的函数。例如在任务被删除时,可以设置某个钩子函数func_abc(),则当这个任务被删除时,会自动调用 函数func_abc()。
(2):任务在调用taskSafe()之后可以防止被其他任务删除。一旦某个任务调用taskDelete()试图删除已被保护的另外一个任务,调用者将被阻塞,直到被删除任务解除删除保护。
(3):在任务访问临界区之前最好使用taskSafe()来保护。系统中某些资源同时只允许一个进程使用,这类资源称为临界资源。进程访问临界资源的那一段代码称为临界区。例如,为了保护临界资源,一般需要先申请一个互斥信号量再进入临界区。如果再临界区中任务被删除,他所使用的互斥信号量就可能无法释放,从而造成其他任务不能访问这段临界区。使用互斥信号也可以通过参数设置自动具备任务删除保护功能,相当于隐含调用了taskSafe()与taskUnsafe()。
19.taskSuspend():挂起指定的任务。在操作前应该判断任务被挂起可能产生的影响,比如挂起时是否占用临界资源。
20.taskRestart():重启指定任务。如果在任务执行过程中,原始参数被改变(如优先级),则使用改变后的参数重新启动任务。
21.钩子(hook)函数允许当任务创建、任务删除或发生上下文切换时调用附加函数。
taskCreateHookAdd():增加一个在每个任务创建时调用的函数
taskCreateHookDelete():删除先前增加的任务在创建时调用的函数
taskSwitchHookAdd():增加一个在每次任务切换时调用的函数
taskSwitchHookDelete():删除先前增加的在任务切换时调用的函数
taskDeleteHookAdd():增加一个在每个任务删除时调用的函数
taskDeleteHookDelete():删除先前增加的在任务删除时调用的函数
22.一个被多个任务调用的单个代码拷贝称为共享代码。
23.如果一个子程序可以被多个任务同时调用而不发生冲突,则这个子程序就是可重入的。而共享代码必须是可重入的。
24.大部分的VxWorks的程序使用下面的可重入机制:
1:局部变量。
2:由信号量保护的全局变量或者静态变量。
3:任务变量。
3.3VxWorks的任务系统
1.根任务:tUsrRoot。内核执行的第一个任务就是tUsrRoot。该任务完成初始化功能并且负责创建其他必要的系统任务,初始化内容包括:I/O系统、设备驱动程序、配置网络等等。正常情况下,在所有初始化工作完成之后,tUsrRoot任务终止。
2.日志任务:tLogTask。在其他任务中,可以调用函数LogMsg()向tLogTask发送日志消息。日志任务的优先级被设置为最高0。中断服务程序中可以使用LogMsg(),但不能使用printf()。
3.异常处理任务:tExcTask(优先级为0)。异常处理任务。不能被挂起、删除或者改变优先级。
4.网络任务:tNetTask。可以通过配置INCLUDE_NET_LIB来决定是否创建网络任务。