ucos任务调度原理及任务就绪表
之前我们说到,系统在运行的时候会直接依靠任务的优先级来找到任务的控制块从而实现任务的调用切换等功能,那么接下来的问题就是,系统是怎么找到并确定某一个特定的最高优先级任务并确定他的优先级的呢
为了解决这个问题,ucos采用了一种比较巧妙地方式,叫做就绪任务表,定义如下
OS_EXT OS_PRIO OSRdyTbl[OS_RDY_TBL_SIZE];
可以见到,就绪任务表的大小为OS_RDY_TBL_SIZE, OS_RDY_TBL_SIZE展开就是
#define OS_RDY_TBL_SIZE ((OS_LOWEST_PRIO) / 8u + 1u)
也就是系统(最低优先级任务/8)+1,我们可以认为当有63个任务的时候,就绪表的数据大小为8,每个元素的大小为
#if OS_LOWEST_PRIO <= 63u
typedef INT8U OS_PRIO;
#else
typedef INT16U OS_PRIO;
#endif
当小于63的时候为八位,大于63为16位,为便于分析,我们假设当前系统任务数量小于63
另外为了解析任务就绪表,还需要知道一个变量
OS_EXT OS_PRIO OSRdyGrp;
该变量是将任务控制表分组的变量
任务控制表的原理为使用数据的位标识特定的优先级的任务reday,假如有63个优先级,那么一共就有8个OSRdyTbl元素,每个元素八位,正好有64个位来存放,同时,则64个位分为八组,每组就是一个字节,对应OSRdyGrp的一个位,这样,通过OSRdyGrp中的位就能找到相应的reday任务位于哪个数组元素中,通过元素的哪一位为1就能确定哪一位中断发生,此时,我们就有了两个值
此时,我们就可以根据优先级找到任务在就绪表中的位置,因为prio最大为63,即二进制00111111,将345位指明变量OSRdyGrp,000b-111b分别代表0-7位 012位指明该数组元素的具体数据位,也是000b-111b分别代表0-7位,就能直接确定当前这个优先级两个数据中的位置
举例说明如果某个优先级00001100(12)的任务准备好,那么OSRdyGrp的第一位为1, OSRdyTbl[1]的第4位为1,同样可以反推.
当遇到多个准备好的任务的时候,因为系统总会选用最高优先级的任务,而最高优先级的任务数值是最小的,所以能迅速选择出当前最高优先级的任务
下面看代码,当系统设置某个任务为ready的时候,代码如下
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
这里面用到了几个变量,是存放在任务的tcb中的
OSTCBBitY OSTCBBitX OSTCBY初始化任务的时候被赋值,如下
ptcb->OSTCBY = (INT8U)(prio >> 3u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
可以看出, OSTCBY是针对于OSRdyGrp的变量,他将优先级的345位变成了bcd码的数据OSTCBBitY, OSTCBX是针对于OSRdyTbl的,将012位解析成为OSTCBBitX,这样置位的时候只要相互或一下就能将reday设置好,举例来说,还是刚才的00001100(12),计算出来
OSTCBY = 1 OSTCBBitY = 0b00000010 OSTCBX = 4 OSTCBBitX = 0b00010000
经过下面的运算
OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
和之前我们饿推理是不是一模一样?满巧妙地,
设置1的方法有了清零就显而易见的通过与非实现
pevent->OSEventTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Remove task from wait list */
if (pevent->OSEventTbl[y] == 0u) {
pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
当从reday分组中根据表取出最高优先级的任务优先级的方法为
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
首先获取分组,使用了一个静态数组,数据如下
INT8U const OSUnMapTbl[256] = {
0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F */
6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F */
7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF */
6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF */
5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF */
4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u /* 0xF0 to 0xFF */
};
乍一看数组OSUnMapTbl的真实意义不明显,但是仔细分析就可以看出,这个数组实际上是将一个八位数据中的为1的最低位的位置找出来,所以他的设置规律我们可以看到,凡是下标的二进制码的第0为为1设置为0,第一位为1设置为2…第七位为1设置为7,大家可以验证,我验证一下7,第七位为1而且是最低位的话只有一个数0b10000000(其他位都必须是0),这个时候对应的数组元素应该是7,元素位置0x80,可以正好对上.要分析明白这一段始终要记得一个原则,ucos的优先级数值越低,优先级越高,所以寻找最高优先级必然是从最低位开始找起.
相当于就是说这个数据能够快速将bcd码转换为ascii码,从而避免的循环,避开了实时操作系统的大忌(运行时间不可测).
在得到两个数据(一个优先级分组一个分组优先级)之后,将两个数据相互组合,就能得到系统的最低优先级了.
所以
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
很巧妙地方式.
Ucos的任务创建采用两个函数
OSTaskCreate
OSTaskCreateExt
之前我们已经说了创建任务先做堆栈初始化,然后做tcb任务控制块初始化,接下来的工作如下
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) {
OS_Sched();
}
} else {
检测系统是否处在运行状态,在运行状态就触发一次任务调度,这次调度的基础还是在tcb_init中,如下
OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run*/
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; /* Increment the #tasks counter */
创建任务的时候默认将任务设置为运行状态,在rdytab中挂好标志,这样接下来调度器就能根据任务的优先级和当前操作系统状态来决定是否执行该任务,如果创建任务优先级低于当前正在执行的任务,那么就会等待
使用ext方法创建数据的时候,比普通的创建方法多了几个参数,这些参数分别是
任务的标示id 任务堆栈栈底指针 任务堆栈容量 附加数据域的指针 操作选项option
这些参数的功能在于运行时可以实时的监控任务
另外,千万记得很重要的一点,ucos不能在中断中创建任务
任务的挂起和恢复采用两个函数
OSTaskSuspend 任务挂起
OSTaskResume 任务恢复
任务挂起的流程如下
检测要挂起的是不是空闲任务,空闲任务不能挂起
检测当前挂起任务时挂起其他别的任务还是挂起自身(挂其自身需要设置挂起优先级为0xff OS_PRIO_SELF),接着按照优先级从系统tcb表中获取需要挂起的任务tcb,,通过ptcb快速的将系统就绪表中的挂起任务的就绪状态清除,然后引发一次调度,从而实现挂起,同时要设置tcb中的OSTCBStat为OS_STAT_SUSPEND,防止在后面的调度中任务又被恢复,这里面有一个需要判断挂起的是不是自身的问题,如果挂起的是自身,需要在挂起之后调度一次,从而完成真正的骨气,而如果不是当前运行的任务本身,则挂起之后是不需要引发调度的,设置好标志即可
知道了挂起流程,恢复流程相反就可以了,首先检测优先级的合法性,然后获取要挂起的任务的tcb指针,判断任务存在,并且任务当前处于被挂起状态并且等待时间为0,条件全部符合的话,取消任务在控制块中的挂起记录,并且调用一次调度器,因为可能恢复的任务优先级比当前优先级高,需要切换
修改任务优先级
OSTaskChangePrio
该函数能实时的修改任务的优先级,主要修改了几个部分,tcb中的优先级,重新修改了系统的就绪表(优先级和就绪表是有着一一对应关系的),以及tcb中用于快定位的几个元素.另外,对于与自身相关的时间信号灯都做了操作,最后使用调度器调度
删除任务
OSTaskDel
OSTaskDelReq
前一个用来任务删除自身或者除了空闲任务之外的其他任务
后一个用来a任务向b任务请求删除b任务
第一个函数的流程是首先从系统就绪表中删除就绪的该优先级任务,然后将删除任务的ptcb清零(堆栈不用,因为下一次设置堆栈的时候会重新赋值),最后从系统tcb控制链表中删除掉指定任务的tcb,并将这个tcb切换到空闲tcb控制块链表中,最后调用调度器,执行调度
而第二个任务的删除则不是实时进行的,他只是将tcb中的状态设置了一下,当被删除任务被调用的时候,再去执行的删除过程,代码如下
ptcb->OSTCBDelReq = OS_ERR_TASK_DEL_REQ;
如果任务自身向自身发送请求删除的话,系统会返回当前任务请求删除的状态
stat = OSTCBCur->OSTCBDelReq;
如果是OS_TASK_DEL_REQ,那么任务应当在合适的时机删除自身
这两种情况是为了更好地解决资源管理的问题,有时候删除的任务被分配了某些资源,但是操作系统删除任务的时候并没有释放这些资源,会造成资源的丢失或者系统的运行故障,所以用请求删除的方式,何时删除有任务自己决定,就不会出现这些问题了,而被删除放应当在程序中调用OSTaskDelReq函数,参数使用OS_PRIO_SELF,获取其他任务对自身的删除请求,从而决定某些操作
例子
请求方 while(OSTaskDelReq(44)==OS_TASK_NOT_EXIST){OSTimeDly(1);}循环等待任务删除
被请求方if(ISTaskDelReq(OS_PRIO_SELF==OS_TASK_DEL_REQ))
{
//释放系统资源
OSTaskDel(OS_PRIO_SELF);//执行真正的任务删除
}
查询任务的信息
OSTaskQuery
用于获得指定任务的tcb状态信息,,该函数返回值为IS_NO_ERR则返回信息可用
任务返回
OS_TaskReturn
该函数在使能任务删除的情况下会先执行任务删除,删除自身,之后死循环延时,延时一般运行不到,因为任务删除的之后系统调度到新任务了.