ucos Ⅱ 多道系统分析——任务管理
1 进程存储和识别
uCOS的进程被命名为task,由OSTCB作为封装其的数据结构;而其在系统中的唯一标识是task的优先级(TCB中的OSTCBPrio属性),即一个优先级代表着一个任务,没有相同的优先级存在。而优先级高低的判断准则是值越小优先级越高,0是最高优先级。因此系统中的进程的最大数量由优先级最低决定。在1992-1999的版本中,最低优先级默认是12;不过具体值可以根据需求自行修改,只要不要高于63。而不能高于63的原因是受就绪队列存储的方式和数据类型长度限制,就绪和阻塞队列中最多记录63个;后续版本中,通过条件编译定义宏OS_PRIO来选择数据长度进而动态支持64-255数量的优先级。任务的优先级导出一些TCB中其他的成员X(省略了OSTCB前缀),Y,X和Y的值为优先级第三位和高三位,该设计的目的后面陈述;而X,Y也导出了两个成员BitX和BitY,它两分别是以X,Y为索引,在OSMapTbl的映射。
uCOS通过OSTCB链表(线性数组)存储task(实质是通过其中的任务栈存储);通过优先级表(OSTCB线性数组指针结构)识别、管理task。之所以用优先级表识别task,是因为task的唯一标识是优先级,而不是OSTCB数组中的下标。
在系统初始化时,链表完成链接;而优先级表初始化为全空,意味暂且系统中没有运行的进程。每一次任务创建或销毁都是,在链表的头进行删除或插入操作表示存储或销毁;将任务在优先级表中对应的指针指向TCB或空(NULL)。
链表的长度由用户定义,即OS_MAX_TASKS的值,但不能小于2(目前不知道为什么),且不能大于最低优先级值减去系统进程数量OS_N_SYS_TASKS(闲置任务和可不存在的统计进程),不过最好不要等于,因为互斥锁的存在会降低利用效率。
优先级表的长度等于最低优先级值+1(0是最高优先级设计的好处),以此做到对每一个优先级的控制。值得一提的是,该表项除了空闲(空,NULL)和已占用(指向一个具体的TCB)状态,还有就绪状态——指针值为1;该状态标识即将或未来可能会使用这个优先级,用于互斥锁和task建立等方面。
2 进程调度
uCOS的就绪和阻塞队列存储和寻找最高优先级的方式让进程调度的代价降到极致,即Ɵ(1)。
进程由等待、休眠类型的阻塞状态转到就绪状态是通过时钟节拍函数OSTimeTick实现。而该函数必须在规定的每一段时间启动一次,因此其调用者是定时器中断服务子程序。而OSTimeTick的功能是将等待时间(等待节拍数)大于0的进程等待时间减一,并若减一后为0,且不是悬挂状态,则将该进程插入就绪队列中;但若为悬挂状态,则置OSTCBDly为1,即等待时间置1,直到取消悬挂。
2.1 状态队列的记录
进程状态队列只需要维护阻塞和就绪队列,在单核体系,执行就只有一个,只需要用一个指针记录即可。
先说明一下记录的前置条件——OSMapTbl,这个数组中元素的值是精心设计的,每个元素的值在二进制层次没有一个位是重叠的,这给用BitX/Y记录任务提供了无二义性的前置条件。
就绪队列主要由OSRdyTbl(长度为最低优先级值除以8+1的八位二进制数组)、OSRdyGrp(八位二进制数)一起记录。OSRdyGrp负责记录任务组(以bitY分组),而OSRdyTbl记录每组任务的BitX! OSRdyGrp通过其二进制位记录就绪队列中优先级的BitY来记录任务组,由于BitY是OSMapTbl上的映射,且其值的范围是0-7,所以每一位代表的优先级组是无二义性的;而OSRdyTbl相应每个元素的值以二进制位的形式记录相同OSTCBY(由于BitY相同,所以OSTCBY必相同)任务的BitX,同样由于BitX也是OSMapTbl的映射,记录方式无二义性。
由于OSRdyTbl种元素和OSRdyGrp的数据类型都是八字节;且OSRdyTbl数组长度最长为8,这种就绪队列能够明确地记录至多64个优先级(不大于63,且无不重复),即至多记录64个就绪进程。
对于阻塞队列,uCOS并没有把所有等待(阻塞)的task记录在一个地方,而是在每一个事件中维护一个等待队列以此记录等待的任务,记录方式与就绪队列一致。事件结构体(除FLAG_GRP以外)中有OSEvenTbl、OSEvenGrp发挥OSRdyTbl、OSRdyGrp一样的作用,即以相同的方式记录任务。除了事件以外,因休眠或悬挂进入阻塞状态的任务没有设立队列,前者仅通过时钟唤醒才能转变成就绪,而后者仅通过其他任务取消悬挂状态才能转变成就绪。
2.2 最高优先级任务的检索
最高优先级任务的检索是通过对最高优先级任务查找表——OSUnMapTbl,的二级查表实现的。
OSUnMapTbl中每个元素的值被设计成能保证每次以OSRdyGrp为索引在OSUnMapTbl中找值,都能找到就绪队列中BitY最小的一组任务的Y值(任务优先级高三位);进而用求得到的Y获得OSRdyTbl[Y]中相应的映射值,将其作为OSUnMapTbl的索引取得映射值中最小的索引,即X。
OSUnMapTbl这个设计特点,使得按照上述的过程可以得到最小的高三位Y和低三位优先级X,这也相应的求出了值最小的优先级,即最高优先级;而该操作系统任务的id(在任务队列中的索引)就是优先级,所以这样就实现了最高优先级任务的检索。
而这个过程没有迭代就绪队列,代价是Ɵ(1),且后面的保存CPU信息之类操作也是Ɵ(1)的复杂度,因此任务调度的总体代价为Ɵ(1)!
2.3 调度的时机
调度的时机有两个,一个是计时中断服务,即节拍计时中断服务,其进行完一次OSTimeTick后,若就绪列表中优先级最高的进程比当前执行进程的高,则发生一次任务调度;另一个是由当前执行的任务创造的,以根据当前进程在调度后的状态分为两种情况——转变成就绪状态和阻塞状态。
① 转变成就绪状态
a) 当前进程成功创立了一个新进程或改变一个任务的优先级时,发生调度。若操作任务的优先级高于自己,则当前任务被抢占,进入就绪队列;否则继续执行。
b) 当前进程释放了一个事件,即释放了一个资源,发生调度。若等待这个事件进程中最高优先级的进程的优先级比自己高,则当前任务被抢占,进入就绪队列;否则继续执行。
c) 取消一个不等待事件的悬挂任务。
② 转变成阻塞状态
a) 当前任务悬挂自身。
b) 当前任务等待一个事件。
c) 当前任务自我休眠。
如果一个任务不想被打断,就会涉及到加锁的行为,为防止在运行的时候任务被打断下次回来无法执行的情况。