关于ucos_ii 就绪表的理解
ucos_ii 作为一个实时系统,最主要的任务就是为了实现任务的调度,为了实现任务的调度,使用了任务就绪表的方法来供ucos来查询(实时性)最高优先级的任务,并且切换到最高优先级任务去执行。
注意两个地方:
第二 任务的创建或者是其他需要任务切换过程中,就绪表就会得到更新,并供ucos服务程序os_sched()查询
第一 为了满足时间确定性,所以不能够使用for循环的遍历方式去遍历就绪表以找到最高优先级的任务,所以此处用了查找表的方式
1、就绪表的基础
系统存在多个任务(对于不同的版本任务个数不同,此处的任务个数最多不超过64个),ucos在做任务调度的时候,需要知道那些任务已经准备好可以运行,并且还要知道最高优先级的任务是哪个。
ucos使用就绪表的方式来实现这一功能。当前的ucos总共有64个任务,将64个任务分成8组来表征,每一组中用一个Uint8类型的数据的每一位来表征8个task。那么总共就可以表征64个任务。
ucos设定了两个变量来表征就绪表
uint8 OSRdyGrp;
uint8 OSRdyTbl[] ;
OSRdyTbl[]_bit0 | OSRdyTbl[]_bit1 | OSRdyTbl[]_bit2 | OSRdyTbl[]_bit3 | OSRdyTbl[]_bit4 | OSRdyTbl[]_bit5 | OSRdyTbl[]_bit6 | OSRdyTbl[]_bit7 | |
OSRdyGrp_bit0 | task_pri_0 | task_pri_1 | task_pri_2 | task_pri_3 | task_pri_4 | task_pri_5 | task_pri_6 | task_pri_7 |
OSRdyGrp_bit1 | task_pri_8 | task_pri_9 | task_pri_15 | |||||
OSRdyGrp_bit2 | task_pri_16 | task_pri_17 | task_pri_20 | task_pri_23 | ||||
OSRdyGrp_bit3 | task_pri_24 | task_pri_25 | task_pri_31 | |||||
OSRdyGrp_bit4 | task_pri_32 | task_pri_33 | task_pri_39 | |||||
OSRdyGrp_bit5 | task_pri_40 | task_pri_41 | task_pri_47 | |||||
OSRdyGrp_bit6 | task_pri_48 | task_pri_49 | task_pri_55 | |||||
OSRdyGrp_bit7 | task_pri_56 | task_pri_57 | task_pri_63 |
如上表所示 : 第一列的8位组合成为OSRdyGrp的值,用来表征任务的优先级实在哪个组,置一表示有任务就绪,置0表示无任务就绪
第一行的8bit组成一个OSRdyTbl[0..7]的值,OSRdyTbl[]中有8个值每一个值对应一个组的就绪任务,这个值的8bit又是对应上表的列名称
例如 我有一个优先级为20的任务准备就绪,并且如上表标识.
那么OSRdyGrp = 0x01;
OSRdyTbl[] = {0x00 , 0x00 , 0x10, 0x00 , 0x00 , 0x00, 0x00 , 0x00};
任务就绪可以通过下面的操作来完成
OSRdyGrp |= OSMapTbl[prio >> 3]; // prio 为一个8位的数据,假设prio为20,那么他的高三位表征了他是低2组中的某一个中断
OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; //prio 为一个8位的数据,假设prio为20,那么他的低三位表征了他是某一组中的第4个中断
OSMapTbl[] = {0x01 , 0x02 , 0x04 , 0x08 , 0x10 , 0x20 , 0x40, 0x80}; //这个查找表就是要让它对应到位上
查找就绪表中的最高优先级的任务:
由于要满足实时系统的实时性,那么查找到最高优先级的任务就不能通过遍历64个就绪表来确定,应为那样遍历的时间是不确定的。
所以重点使用OSRdyGrp 变量来实现。
OSRdyGrp 变量是包含了所有组存在就绪任务的信息的,如果OSRdyGrp_bit0(OSRdyGrp变量的最低位)为1,那么表征在整个就绪表中,第0组出现了已就绪的最高优先级任务,
那么在通过使用第0组中的OSRdyTbl[0]来获取最高优先级任务并执行。
假设OSRdyGrp = 0x05,
OSMapTbl[] = {0x01 , 0x00 , 0x10, 0x00 , 0x00 , 0x00, 0x00 , 0x00};
此时任务就绪表中就有两个任务是就绪的,分别是
第0组中的第0个任务 就是 task_pri_0。
第2组中的第4个任务 就是 task_pri_20。
所以会选择task_pri_0去执行。那么如何让ucos自动执行并且计算的时间是固定的,那么引入下面一种解码表:
这张表的目的就是通过变量OSRdyGrp 来确定那个组中存在最高优先级以就绪的任务。
原理如下所示:
OSRdyGrp 变量表征了所有组数存在的就绪任务,OSRdyGrp 为一个8位的变量,总共能够表征的可能性为255种。
假设OSRdyGrp_bit0 = 1.此时无论OSRdyGrp其他位为何值,最高优先级都应该在第0组才是对的,那么:
OSRdyGrp = 0b0000_0001 //1
OSRdyGrp = 0b0000_0011 //3
OSRdyGrp = 0b0000_0101 //5
OSRdyGrp = 0b0000_0111 //7
......
OSRdyGrp = 0b1111_1111 //255
只要OSRdyGrp的值满足为奇数,那么此时的最高优先级任务就出现在了第0组中,对应于OSUnMapTbl[]中,所有为奇数的值都是为0的,这就表征了这个OSRdyGrp得值
表征了最高优先级任务出现再第0组中。
假设OSRdyGrp_bit1 = 1并且OSRdyGrp_bit1 = 0.此时无论OSRdyGrp其他位为何值,最高优先级都应该在第1组才是对的,那么:
OSRdyGrp = 0b0000_0010 //2
OSRdyGrp = 0b0000_0110 //6
OSRdyGrp = 0b0000_1010 //10
OSRdyGrp = 0b0000_1110 //15
......
OSRdyGrp = 0b1111_1110 //254
只要OSRdyGrp的值满足为上述序列,那么此时的最高优先级任务就出现在了第1组中,对应于OSUnMapTbl[]中,所有满足上述序列的值都是为1的,这就表征了这个OSRdyGrp得值
表征了最高优先级任务出现再第1组中。
以此类推。。。。
所以ucos用来求解最高优先级任务的方法如下所示:
y = OSUnMapTbl[OSRdyGrp]; //首先通过OSUnMapTbl[]表来确定最高优先级任务在哪一个组中
x = OSUnMapTbl[OSRdyTbl[y]]; //通过取得对应组的OSRdyTbl[]中表针的8个任务,再通过OSUnMapTbl[]又获取到最高优先级的那个任务序号。
prio = (y << 3) + x; //合并对应组合对应个数的任务优先级,得到最终最高优先级任务的序号。
只用了三步,确定了查找时间。巧妙的很
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
下文是看到另外一位高手的文章,也转载过来:地址为:http://www.cnblogs.com/dengxiaojun/p/4322471.html
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
该函数在使能任务删除的情况下会先执行任务删除,删除自身,之后死循环延时,延时一般运行不到,因为任务删除的之后系统调度到新任务了.