在ucos系统中,任务调度按照优先级调度算法,从就绪队列中选取优先级最高的任务进行调度。那么首先我们就要解决如何找到最高优先级的任务。
一种方法,我们可以从头到尾遍历就绪队列,找到优先级最高的任务。
另一种方法,我们要维护优先队列的顺序,按照优先级从高到低的顺序排列。
这两种方法效率不高,花费的时间也和当前任务数有关,具有不确定性。优先级位图算法就是一种确定性的就绪任务处理算法。
首先我们需要一个二维表来保存各个优先级的任务,在ucos中,每个优先级只允许对应一个任务。
我们可以看到这个优先级就绪表的特点,越往上优先级越高,越往右优先级越高。我们可以这样我们可以利用位运算提升计算效率,例如35的二进制为00 100 011,我们取3-5位作为列坐标,6-8位作为行坐标,列坐标为100就是4,行坐标011就是3,可以看到正好对应就绪表中的35号窗格。
// OSRdyGrp:优先级就绪组 OSMapTbl:优先级映射表
OSRdyGrp |= OSMapTbl[priority >> 3];
// OSRdyTbl:优先级就绪表
OSRdyTbl[priority >> 3] |= OSMapTbl[priority & 0x07];
前面我们说到,优先级就绪表越往上优先级越高,所以首先要标记一下目前的优先级,最上面的一列是哪列。这个标记的数据结构就是优先级就绪组。
首先将优先级右移三位,取出列坐标,假设优先级为35的话,列坐标为4,我们需要将优先级就绪组第四位置1,表示第四列有优先级,方便后面查找最高优先级。
那么如何将优先级就绪组第四位 置1呢,这里就用到了位运算,和0000 1000相或,就能将第四位置1。相应的如果将第5位置1,就和0000 0100相或,根据这个规律,我们事先创建了一个映射表,将第n位置1表示成和一个8位二进制数相或。
0x07的二进制为0000 0111。priority & 0x07操作也就是取出后三位。OSMapTbl[priority & 0x07]再通过优先级映射表转换成二进制。
假如优先级为35,取出后三位就是3,映射为二进制就为0000 1000。这时再和OSRdyTbl[priority >> 3]相或,就能将优先级就绪表的对应为设置为1了。
if((OSRdyTbl[priority >> 3] &= ~OSMapTbl[priority & 0x07]) == 0)
OSRdyGrp &= ~OSMapTbl[priority >> 3];
首先,将优先级就绪表对应位 置0,如果优先级就绪表对应行全为0,则将优先级就绪组对应位 置0。这里就是位运算,大家仔细分析一下,就不赘述了。
// 获取行坐标 OSUnMapTbl:优先级判定表
high3Bit = OSUnMapTbl[OSRdyGrp];
// 获取列坐标
low3Bit = OSUnMapTbl[OSRdyTbl[high3Bit]];
// 计算出优先级
priority = (high3Bit << 3) + low3Bit;
得益于前面优秀的数据结构,我们只需要找出优先级就绪组,找到最上面为1的行,再从这个行中找到最右边为1的优先级,就为最高的优先级了。
假设优先级就绪表中有优先级为35、37、53的任务。首先看优先级就绪组,从上往下看,第4位为1,所以我们看优先级就绪表第4行,从右往左看,找到第一个为1的任务,这样就找到了最高优先级的位置。之后我们将优先级计算出来,列坐标为0000 1000,行坐标为0000 1000,使用公示(high3Bit << 3) + low3Bit就能算出,最高优先级为35。
那么问题来了,我们如何找到优先级就绪组和每一行中最高的优先级并转换为列坐标和行坐标呢?位运算么?
位运算确实可以解决问题,但是每次都计算会导致时间效率不高,这里我们使用了空间换时间的办法。事先计算好不同的数组的值对应的最低位坐标保存起来,称作优先级判定表。我们可以理解为优先级判定表的功能是,查找8位数据中为1的位中最小的坐标。
为了兼容查询行坐标和列坐标,在查询行坐标的时候,要将优先级就绪组倒序组成8位。
比如上文的例子,优先级就绪组为0000 1010,查询的时候我们要转化成0101 0000,十六进制0x50,查表值为4。第四行的数据为0010 1000,十六进制0x28,查表值为3。之后计算4*8+3=35,即当前最高优先级为35。