ucosii实时操作系统的任务调度

摘要

嵌入式操作系统的任务调度算法好坏在很大程度上决定了该系统的运行效率,由于其执行的频率极高,所以在任务调度函数的实现上,对于效率的要求可以用苛刻来形容。基于优先级的任务调度总结下来就是做了两件事:

1 找到优先级最高的就绪态任务

2 切换任务上下文并开始执行该任务

第二步的切换上下文对个各个RTOS区别不大,而且是一套“固定动作”,真正有意思的地方是在第一步。本文以ucosii为蓝本,讲解任务调度中最重要的第1步,如何最快的找到优先级最高的就绪态任务。其实邵贝贝老师的那本书《嵌入式实时操作系统μC/OS-II 》(第2版)已经很详细的讲解了整个过程。但是楼主思考再三还是决定按照自己的理解重写一遍,尽量以浅显易懂的语言描述出来,让更多的人理解这个看起来有点神秘、理解了以后就觉得蛮有意思的过程。


准备工作

先说明和ucosii任务调度的一些基本情况。ucosii 2.52最多有64个任务,每个任务都要求有不同的优先级,优先级的数字越小代表优先级越高(优先级为0的task高于为1的task)。大家都知道任务有就绪、运行、睡眠、等待挂起等多个状态,对于调度器来说,任务只有两种状态:就绪和非就绪。于是很自然的,我们想到要定义个64字节长度的数组,里面的每个字节对应于一个任务,数组项为1来表示该任务就绪,0表示非就绪。

char taskarray[64];

举个例子:

taskarray[64] = {1,1,0,0,0,0,0,0,0,0.......0,1},就表示第1、2、64三个任务是就绪的,其余任务都是非就绪的。

有一定经验的程序员可能已经发现,用64字节太浪费了,每个任务只需要一个bit就够了,于是乎 OSRdyTbl就可以

就闪亮登场了,它是任务调度算法的第一个“关键人物”。

char OSRdyTbl[8]; 

这里需要注意,虽然这个数组是8个字节,每个字节有8个bit。

那么问题来了,这些bit位在计算机内存里是怎么排放的?下面这种排法毫无疑问是最舒服最直观的的方式:




但是很可惜,计算机程序对于数据的解读和人的习惯是有区别的。程序一般都是以左移(1<< X)来表明第X位的

例如,

OSRdyTbl[0] |= (1 << 3);           表明把第0个元素的第3位置1。

if (OSRdyTbl[4] & (1 << 5))       表示判断第4个元素的第5位是否为1

所以,这些bit位在内存里的存放方法应该如下所示:


     


为方面观看,我们把它表示为正方形的形式,如下所示:



右上角0表示最高优先级,左下角63表示最低优先级。在同一行里,右边的格比左边的格优先级要高。

图片里灰色的方块表示该优先级的任务就绪,其余的任务都是非就绪。

接下来,我们就可以集中火力开始“以最快的速度”查找优先级最高的任务了


第一步:直接遍历法

有了上面的这张表,大多数人第一反应就是通过循环遍历的方法来查找距离左上角最近的为1的方框,从第一行的左边开始,一格一格的查看,第一行查完了再查第二行,找到第一个为1的格就可以返回了,代码实现大概是这个样子的

for (int i = 0;i < 8;i++)
    for (int j = 0;j < 8;j++)
        if (OSRdyTbl[i] & (1 << j))
            break;

在楼主给出的这个例子中,最高优先级是15,需要从0开始一直循环到15,看起来还不算太多。但如果,系统中只有俩task就绪,优先级分别是62和63呢?那每次调度器岂不是都要循环六十多次就为找一个task执行,这样好像不太好吧惊恐

这里插讲一下,得到了行和列之后,怎么算出优先级。其实和数学计算一样

 priority = i*8 + j;         //第1行第7列,结果就是15


第二步:两级遍历法


很明显,前面那个查找算法太慢了,调度器本身就占用了太多的CPU,我们需要更快的查找方法

多看几眼那个正方形的优先级方格矩阵,我们不难想到,能不能先定位到最高优先级的task在哪一行,在从这行的开头找过去呢?

这是个不错的思路,于是乎我们需要请出下一位“关键人物”:

char OSRdyGrp;

它是一个8比特的数值,每个比特可以对应正方形矩阵里的每一行,如果该bit为1,则表明这一行里有任务就绪。

当OSRdyTbl[0]里至少有一位为1时,OSRdyGrp的bit 0位1,当OSRdyTbl[1]里至少有一位为1时,OSRdyGrp的bit 1位1,以此类推。

楼主的这个例子中,第1,2,3,4,7都有任务就绪,其余行没有,所以OSRdyGrp应该为0x10011110.

有了这个结构,新的查找算法看起应该是这个样子的

for (int i = 0; i < 8;i++)
    if (OSRdyGrp & (1 << i))
         break;

for (int j = 0;j < 8;j++) 
    if (OSRdyTbl[i] & (1 << j))
        break;

 

第一个循环用于定位task在哪一行,第二个循环用于定位在哪一列。这个算法比前一个要快了很多,即使在最坏的情况下,只有优

先级为63的一个task需要调度,需要16次循环。

我们完成了从 8*8 到 8+8 的进步

第三步:两级查表法

前面的两级遍历查找法,看起来还不错,在一般的应用场景,已经够用了。但是在调度器这种对效率极为敏感的地方,还需要再努把力。再分析一下这两个循环都干了什么:

第一个循环,从右向左遍历一个8位字段,找出最近的1作为结果;第二个循环,也是一个从类似的过程。两次循环的“输入”都是一个字节的数字,“输出”是一个结果“距离最右边最近的1是在第几位”。8位字段的输入,一共就256种可能性,如果我们把这256种结果都提前算好存储在一个表里,那不是可以省去遍历的过程,直接查表就能得出结果了?

想到这里,该请出今天的最后一位“关键人物”了,就是OSUnMapTbl

它是一个const型的数组,说明它里面的每一项都是提前算好的。正如前面分析,这个表里的每一项都是表达一个该索引代表的数“距离最右边最近的1是在第几位”。

以楼主画圈的两个数为例,第一个是第4排第1列,其索引为48,二进制00110000,距离最右边最近的1就是第4位

第二个是第7排第9列,其索引为104,二进制10011000,距离最右边最近的1就是第3位。

现在我们可以很清楚的知道,在调度器执行函数OS_Sched()里的下面这几句话是干什么的了:

        y             = OSUnMapTbl[OSRdyGrp];        /* Find highest priority's task priority number   */
        x             = OSUnMapTbl[OSRdyTbl[y]];
        OSPrioHighRdy = (INT8U)((y << 3) + x);


第一行:以OSRdyGrp为索引,查找OSUnMapTbl以定位到OSRdyTbl里的距离0最近的有bit位为1的行

第二行:以OSRdyTbl里的这一行(它也是一个8位的数字)为索引,查出这一行里,距离最右边最近的有bit位为1的列

第三行:将行和列组合起来,得到的就是最终我们需要的“优先级”。
由于ucosii里每个优先级只能有一个任务,所以有了优先级就等于定位到了该任务。

就这样,是不是还蛮简单的大笑

如果您读到这里,还觉得意犹未尽,请看下面几个思考题,答案在这篇博文里FreeRTOS的任务调度算法优化实现

1 ucosii 2.52系统内部设定了最多只支持64个优先级,如果换了一个RTOS,任务一共有256个优先级,还是有这个查找方法,应该做什么修改?

2 ucosii 2.52系统强行规定了每个优先级都只能有一个任务,所以找到了最高优先级数,就等于找到了任务。如果换了一个RTOS,每个优先级上允许有多个任务,那么在查找到最高优先级数之后,在相同优先级的多个任务中如何确定你本次要调度的那个任务?

3 ucosii里是认为优先级数值越大,其任务优先级越小,如果反过了,数值越大则优先级越大应该怎么处理?

Over


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值