最近开始花时间去学习uc/OS-II,一方面是工作上用的是这个系统,另一方面就是想去了解实时操作系统与普通操作系统的区别,学到任务就绪表及任务调度这里,对实时的概念有所了解,所以写此文帮助自己梳理,也希望与读者交流。
实时含有立即、及时之意。如果操作系统能使计算机系统及时响应外部事件的请求,并能及时控制所有实时设备与实时任务协调运行,且能在一个规定的事件内完成对事件的处理,那么这种操作系统就是一个实时操作系统。
对实时系统有两个基本要求,第一,实时操作系统的计算必须产生正确的结果,称为逻辑或功能正确。第二,实时系统的计算必须在预定的事件内完成,称为时间正确。为了达到这些要求,实时操作系统应满足以下三个条件:
1)实时操作系统必须是多任务系统
2)任务的切换时间应与系统中的任务数无关
3)中断延迟的时间可预知并尽可能短
本文主要关于第二点,任务的切换时间是如何与任务数无关。
===========================分割线============================
为系统中处于就绪状态的任务分配CPU是多任务操作系统的核心工作。内核在进行任务调度时,必须知道哪个任务在运行、哪个任务是就绪的最高优先级的任务。实时任务调度的要求无论系统的运行情况如何,调度的时间是确定的,不能把时间都用在调度上。为满足这样的需要,uC/OS-II系统里定义了任务就绪表结构
这个任务就绪表登记了系统中所有处于就绪状态的任务,它是一个位图,系统中的每个任务都在这个位图中占据一个二进制位,该位值的状态就表示任务是否处于就绪状态。实际上,它是一个类型为INT8U的数组OSRdyTbl[],它以任务优先级别的高低为顺序,为每个任务安排了一个二进制位。
在上图中,该数组有8个元素,即可以表达64个任务的就绪状态。每个元素描述了8个任务的就绪状态,于是8个任务可看成一个任务组,为了便于对就绪表进行查找,uC/OS-II又定义了一个数据类型为INT8U的变量OSRdyGrp,并使该变量的每一个位都对应OSRdyTbl[]的一个任务组(即数组的一个元素),如果某个任务组又任务就绪,则在变量OSRdyGrp里就把该任务组所对应的位设置为1,否则为0。如下图所示:
在上图中,创建了优先级最小的两个任务—空闲任务和统计任务。空闲任务的优先级是63,空闲任务就绪,那么OSRdyTbl[7]的最高位为1.统计任务的优先级是62,统计任务也就绪,那么OSRdyTbl[7]的次高位为1。
这里有个简单的问题,如何根据任务的优先级别来找到任务在就绪表的位置呢?每个任务的优先级别是唯一的,并且其最大值不会超过63,所以可以把优先级别看成是一个6位的二进制数。这样可以用高3位(D5D4D3)来指明变量OSRdyGrp的具体数据位,并用来确定就绪表数组元素的下标,用低3位(D2D1D0)来指明该数组元素具体数据位。
综上,可以看出uc/OS-II用类似下面的代码把优先级别为prio的任务置为就绪状态:
OSRdyGrp |= OSMapTbl[prio >> 3];
OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07];
//其中,OSMapTbl[]是uC/OS-II为加快运算速度定义的一个数组,其各元素的值为:
OSMapTbl[0] = 00000001B
OSMapTbl[1] = 00000010B
OSMapTbl[2] = 00000100B
OSMapTbl[3] = 00001000B
OSMapTbl[4] = 00010000B
OSMapTbl[5] = 00100000B
OSMapTbl[6] = 01000000B
OSMapTbl[7] = 10000000B
反之,若要注销某个任务,即系统在就绪表中将该任务的对应位设置为0,则使用如下代码:
if ((OSRbtTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0)
OSRbyGrp &= ~OSMapTbl[prio >> 3];
以上代码将任务就绪表OSRbtTbl[]中对应任务的相应位清零,对于OSRbyGrp,只有当被删除的任务所在的任务组全都没有一个处于就绪状态时,才将其相应位清零,即OSRbtTbl[prio >> 3]所有位都为零时,OSRbyGrp相应的位才清零。
最后就是关于最高优先级就绪任务的查找,在说明这个之前先要知道uc/OS-II的几个很重要的概念:
1)uc/OS-II是可抢占实时多任务系统,它总是运行就绪任务中优先级最高的那一个
2)uc/OS-II中不支持时间片轮转法,每个任务的优先级不一样且是唯一的,所以任务调度的工作就是查找准备就绪的优先级最高的任务并进行上下文切换
3)uc/OS-II任务调度所花的时间为常数,与应用程序中建立的任务数无关
根据第3点,uc/OS-II为了找到任务就绪表中优先级最高的任务,不会从OSRbtTbl[0]开始扫描整个任务就绪表,因为如果是这样的话时间就不能确定。所以对此uc/OS-II准备一张优先级判定表,即OSUnMapTbl[],该表定义如下:
NT8U const OSUnMapTbl[] = {
0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x00 to 0x0F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x10 to 0x1F */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x20 to 0x2F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x30 to 0x3F */
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x40 to 0x4F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x50 to 0x5F */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x60 to 0x6F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x70 to 0x7F */
7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x80 to 0x8F */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0x90 to 0x9F */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xA0 to 0xAF */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xB0 to 0xBF */
6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xC0 to 0xCF */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xD0 to 0xDF */
5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, /* 0xE0 to 0xEF */
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 /* 0xF0 to 0xFF */
};
首先根据OSRdyGrp查找优先级判定表,原理是根据优先级立即查找到OSRdyTbl[]中对应元素,即OSRdyGrp中从低位到高位第一个为1的位的位置(从0到7)。例如,如果OSRdyGrp为11001000B,那么最低的为1的位是3号位,于是查表得到3,就是说最高优先级OSRdyTbl[3]中。如果是01001000B,同样查找到3,这就是程序中为什么有那么多列是相同的原因。优先级判定表OSUnMapTbl就是根据一个8位的无符号数的数值来确定最低的为1的位的位置的,OSUnMapTbl[n]就是n的最低的为1的位的位号。
了解了OSUnMapTbl[]优先级判定表的作用后,就可以明白uc/OS-II调度器用于获取优先级最高的就绪任务代码如下:
y = OSUnMapTbl[OSRdyGrp]; //获取优先级别的D5、D4、D3位
x = OSUnMapTbl[OSRdyTbl[y]]; //获取优先级别的D2、D1、D0位
prio = (y << 3) + x
y=OSUnMapTbl[OSRdyGrp]是将任务就绪表任务组中最低位为1的位号取出来放在y中,即找到优先级别最高的一组任务组,x = OSUnMapTbl[OSRdyTbl[y]]是将该任务组中优先级别最高的任务找出来。所以最高优先级的任务就可以根据这两点来组成,即prio = (y << 3) + 3.
可以知道OSRbyGrp = 10000010B = 130,根据该值去OSUnMapTbl[]表中找,可得出OSUnMapTbl[130] = 1,即优先级最高的任务组在OSRdyTbl[1],由图可知OSRdyTbl[1] = 00001000B = 8,根据该值去OSUnMapTbl[]表中找,可得出OSUnMapTbl[8] = 3,即在该任务组中,优先级最高的任务是bit3,所以该任务的优先级 (1 << 3) + 3 = 11。
任务调度器代码的设计,即任务就绪表结构以及优先级判定表结构,利用索引查表的方式而不是轮训遍历的方式,使得它的运行时间与系统的任务数无关,从而使其满足了实时系统的要求。这就是uc/OS-II实时操作系统的设计方式之一。