文章目录
一、ucos任务
1.1 ucos任务简介
- 一个任务通常是一个无限的循环。
一个任务就像是一个C函数,它可以带有形参,但是其绝不会有返回值。故返回类型为void。如下
void YourTask (void *pdata)
{
for (;;) {
/* 用户代码 */
调用uC/OS-II的某种系统服务:
OSMboxPend();
OSQPend();
OSSemPend();
OSTaskDel(OS_PRIO_SELF);
OSTaskSuspend(OS_PRIO_SELF);
OSTimeDly();
OSTimeDlyHMSM();
/* 用户代码 */
}
}
- 不同的是,当任务完成以后,任务可以自我删除。
注意任务代码并非真的删除了,μC/OS-Ⅱ只是简单地不再理会这个任务了,这个任务的代码也不会再运行,
如果任务调用了 OSTaskDel(),这个任务绝不会返回什么。
void YourTask (void *pdata)
{
/* 用户代码 */
OSTaskDel(OS_PRIO_SELF);
}
- μC/OS-Ⅱ可以管理多达 64 个任务,用户可以操作的最多只有 56 个应用任务
μC/OS-Ⅱ可以管理多达 64 个任务,但作者保留了优先级为 0、 1、 2、 3、 OS_LOWEST_PRIO-3、 OS_LOWEST_PRI0-2,OS_LOWEST_PRI0-1 以及 OS_LOWEST_PRI0 这 8 个任务以被将来使用。所有我们用户还可以自定义剩下的56个任务。 - μC/OS-Ⅱ任务优先级,任务的优先级号就是任务编号(ID)。
必须给每个任务赋以不同的优先级,优先级可以从 0 到OS_LOWEST_PR10-2。
优先级号越低,任务的优先级越高。μC/OS-Ⅱ总是运行进入就绪态的优先级最高的任务。
1.2 任务状态
从用户的观点来看,任务可以是有 5 种状态,休眠状态,就绪状态,运行状态,挂起状态,中断状态 。
在任一给定的时刻,任务的状态一定是在这五种状态之一。如下图:
- 睡眠态(DORMANT)
指任务驻留在程序空间之中,还没有交给μC/OS-Ⅱ管理。
一个任务可以通过调用 OSTaskDel()返回到睡眠态,或通过调用该函数让另一个任务进入睡眠态。 - 就绪态
当任务一旦建立,这个任务就进入就绪态准备运行。
任务的建立可以是在多任务运行开始之前,也可以是动态地被一个运行着的任务建立。
如果一个任务是被另一个任务建立的,而这个任务的优先级高于建立它的那个任务,则这个刚刚建立的任务将立即得到 CPU 的控制权。 - 运行态
调用 OSStart()可以启动多任务。OSStart()函数运行进入就绪态的优先级最高的任务。该任务进入了运行态。
就绪的任务只有当所有优先级高于这个任务的任务转为等待状态,或者是被删除了,才能进入运行态。 - 等待/挂起
■ 正在运行的任务需要自身延迟时,进入等待态
调用以下两个函数:OSTimeDly()、 OSTimeDlyHMSM() 之一将自身延迟一段时间。这个任务于是进入等待状态,等待这段时间过去,下一个优先级最高的、并进入了就绪态的任务立刻被赋予了 CPU 的控制权。等待的时间过去以后,系统服务函数 OSTimeTick()使延迟了的任务进入就绪态。
■ 正在运行的任务期待某一事件的发生时需要等待时,进入挂起态
调用以下 3 个函数之一:OSSemPend(),OSMboxPend(),或 OSQPend(),任务就进入挂起状态(WAITING)。当任务因等待事件被挂起(Pend),下一个优先级最高的任务立即得到了 CPU 的控制权。当事
件发生了,被挂起的任务进入就绪态。 - 中断服务态
正在运行的任务是可以被中断的,响应中断时,正在执行的任务被挂起,中断服务子程序控制了 CPU 的使用权。中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就绪态。在这种情况下,从中断服务子程序返回之前,μC/OS-Ⅱ要判定,被中断的任务是否还是就绪态任务中优先级最高的。如果中断服务子程序使一个优先级更高的任务进入了就绪态,则新进入就绪态的这个优先级更高的任务将得以运行,否则原来被中断了的任务才能继续运行。
1.3 任务控制块
任务控制块是被 uC/OS-III 用于维护任务的一个结构体。 每个任务都必须有自的己 TCB 。TCB 中的一些变量可以根据具体应用进行裁剪。
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; //OSTCBStkPtr 是指向当前任务栈顶的指针。
#if OS_TASK_CREATE_EXT_EN
void *OSTCBExtPtr; //OSTCBExtPtr 指向用户定义的任务控制块扩展。
OS_STK *OSTCBStkBottom; //OSTCBStkBottom 是指向任务栈底的指针
INT32U OSTCBStkSize; //OSTCBStkSize 存有栈中可容纳的指针元数目而不是用字节(Byte)表示的栈容量总数。
INT16U OSTCBOpt; //选择项
INT16U OSTCBId; //OSTCBId 用于存储任务的识别码。
#endif
//OSTCBNext,OSTCBPrev 用于任务控制块OS_TCBs的双重链接,双重连接的链表使得任一成员都能被快速插入或删除。
struct os_tcb *OSTCBNext;
struct os_tcb *OSTCBPrev;
#if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN || OS_SEM_EN
OS_EVENT *OSTCBEventPtr; //OSTCBEventPtr 是指向事件控制块的指针
#endif
#if (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN
void *OSTCBMsg; //OSTCBMsg 是指向传给任务的消息的指针。
#endif
INT16U OSTCBDly; //这个变量保存的是任务允许等待事件发生的最多时钟节拍数。如果这个变量为 0,表示任务不延时
INT8U OSTCBStat; //OSTCBStat 是任务的状态字。当.OSTCBStat 为 0,任务进入就绪态。
INT8U OSTCBPrio; //OSTCBPrio 是任务优先级。高优先级任务的.OSTCBPrio 值小。
//.OSTCBX, .OSTCBY, .OSTCBBitX 和 .OSTCBBitY 用于加速任务进入就绪态的过程或进入等待事件发生状态的过程
INT8U OSTCBX;
INT8U OSTCBY;
INT8U OSTCBBitX;
INT8U OSTCBBitY;
#if OS_TASK_DEL_EN
BOOLEAN OSTCBDelReq; //OSTCBDelReq 是一个布尔量,用于表示该任务是否需要删除,
#endif
} OS_TCB;
- 任务控制块OS_TCBs是一个数据结构
·OS_TCBs保存该任务的相关参数,包括任务堆栈指针,状态,优先级,任务表位置,任务链表指针等。 - 任一旦任务建立了,任务控制块OS_TCBs将被赋值
· 任务建立的时候,OS_TCBs被初始化了
· 当任务的CPU使用权被剥夺时,μC/0S-Il 用它来保存该任务的状态。
· 当任务重新得到CPU使用权时,任务控制块能确保任务从当时被中断的那上点丝毫不差地继续执行。
· OS_TCBs全部驻留在RAM中。 - 所有的任务控制块分为两条链表,空闲链表和使用链表。
疑问:任务ID呢?
ucos中以任务的优先级作为任务的ID
1.4 任务的调度
内核在进行任务调度的时候,必须知道哪个任务在运行,哪个任务是就绪的最高优先级的任务。
实时任务调度的关键在于速度,要求无论系绕的运行情况如何,调度的时间是确定的,不能把时间都用在调度上。
因此就需要设计高效的多任务调度方法,查找高优先级的任务,与正在运行任务的优先级进行比较以确定是否进行任务切换是内核在每个时钟中断都需要做的事情。
ucos任务调度的一些特点:
- μ C/OS是占先式实时多任务内核,采用的是基于优先级的调度算法
优先级最高的任务一旦维备就绪,则其拥有CPU的所有权开始投入运行。所有任务都分配了一个唯一的优先级。任务的优先级数字越小相应的优先级别就利高,数字0是最高优先级别。 - 调度任务的工作
μC/OS中不支持时间片轮转法,每个任务的优先级要求不一样且是唯一的,所以任务调度的工作就是:查找准备就绪的最高优先级的任务并进行上下文切换。 - μCOS任务调度所花的时间为常数
与应用程序中建立的任务数无关。
思考:我们会怎么设计,快速找到优先级序号,并且使任务调度所化的时间为常数的调度算法呢?
μCOS开发者的想法:
为满足以上的需要,μ C/OS-II的开发者采用了就绪表这样的数据结构,围绕就绪表定义了两个变量。
每个任务的就绪态标志都放入就绪表中的,就绪表中有两个变量 OSRedyGrp 和OSRdyTbl[ ] 。
核心是查表法,查表法具有确定的时间,增加了系统的可预测性,μC/0S中所有的系统调用时间都是确定的。
i. 就绪任务表
ucos中共64个任务,所以将任务就绪表设计成为一个8*8的表格。
就绪表中有64个bit位,1bit位代表着一个优先级。如下图:右上角的bit位就代表最高优先级0,左下角的bit位就代表最低优先级63,ucos中把空闲任务优先级设置为最低优先级63。
若某bit位被置1,则说明该bit位对应的优先级的任务就绪。如右上角的bit位置1,说明优先级位0的任务就绪了。
图解:
-
就绪表是 一个数据类型为unsign char,下标为0~7数组 OSRdyTbl[ ] 。
OSRdyTbl[ ]数组的数据值的位个数可以理解为X轴数据个数,OSRdyTbl[ ]数组的下标个数可以理解为Y轴数据个数。
X,Y轴各有8个数据,所以开发者定义 OSRdyTbl[ ]数组的数据类型为8位无符号数(unsign char) ,OSRdyTbl[ ] 数组的下标为 0~7。 -
优先级是用一个8位的变量表示
优先级有64个,所以用一个8位无符号(28=256 > 64)的变量Priorty就可以完全存储这个优先级。
以这个变量Priorty的低3位0-2就可以作为X轴下标,3-5位作为Y轴下标。 -
用OSRdyGrp这个变量来表示数组OSRdyTbl[ ]的下标,它是一个冗余的变量,它是用来加快找到最高的优先级,OSRedyGrp根据OSRdyTble[ ]设置对应的位为1,有以下关系:
当 OSRdyTbl[0]中的任何一位是 1 时,OSRdyGrp 的第 0 位置 1,
当 OSRdyTbl[1]中的任何一位是 1 时,OSRdyGrp 的第 1 位置 1,
当 OSRdyTbl[2]中的任何一位是 1 时,OSRdyGrp 的第 2 位置 1,
当 OSRdyTbl[3]中的任何一位是 1 时,OSRdyGrp 的第 3 位置 1,
当 OSRdyTbl[4]中的任何一位是 1 时,OSRdyGrp 的第 4 位置 1,
当 OSRdyTbl[5]中的任何一位是 1 时,OSRdyGrp 的第 5 位置 1,
当 OSRdyTbl[6]中的任何一位是 1 时,OSRdyGrp 的第 6 位置 1,
当 OSRdyTbl[7]中的任何一位是 1 时,OSRdyGrp 的第 7 位置 1,
例题:
上图例1:假设优先级为12(Priorty=12)的任务进入就绪状态,12 = 1 100b,则OSRdyTbl[1] 的第4位置1(从右边往左边数,最右边为第0位)。又因为OSRdyTbl[1]中的其中一位是 1 时,OSRdyGrp 的第 1 位置 1。
代码如下:
OSRdyGrp |= 0x02;//0x02=0b 0000-0010
OSRdyTbl[1] |= 0x10;//0x10=0b 0001-0000
上图例2:假设优先级为21(Priorty=21)的任务进入就绪状态,21= 10 101b,则OSRdyTbl[2]的第5位置1(从右边往左边数,最右边为第0位)。又因为OSRdyTbl[2]中的其中一位是 1 时,OSRdyGrp 的第 2 位置 1。
代码如下:
OSRdyGrp |= 0x04;//0x02=0b 0000-0100
OSRdyTbl[2] |= 0x20;//0x10=0b 0010-0000
ii. 使任务进入就绪态算法(核心:查表)
上面说过,将就绪表中某bit位被置1,就说明该bit位对应的优先级的任务就绪。
考虑到要使任务进入就绪态算法所花的时间为常数,我们不能使用上面例子的方式设置OSRdyGrp和OSRdyTbl[ ],于是我们使用查表的方法使每个任务进入就绪态的时间为固定值。
使任务进入就绪态算法如下:
- prio>>3
第一个prio>>3含义:优先级prio右移3位,使把0-2位的X移出去,保留3-5位的Y。
第一个prio>>3含义:prio除以8(23),Y轴有8个数据,这里优先级除以8得到Y轴的值。如优先级为12,,Y轴的值就为12/8=1. - OSMapTbl[ ] 数组
OSMapTbl[ ]数组中存放的是8个不同位置1的数据值,如下图。
- prio & 0x07
prio与上0b 0000 0111。保留0-2位的值,即保留X。
例1的代码如下:假设优先级为12(Priorty=12)的任务进入就绪状态,12 = 1 100b,则OSRdyTbl[1] 的第4位置1(从右边往左边数,最右边为第0位)。又因为OSRdyTbl[1]中的其中一位是 1 时,OSRdyGrp 的第 1 位置 1。
代码如下:
OSRdyGrp |= OSMapTbl[12>>3];//OSMapTbl[12>>3] = OSMapTbl[1] = 0b00000010 = 0x02;
OSRdyTbl[12>>3] |= OSMapTbl[12 & 0x07];//OSRdyTbl[12>>3]=OSRdyTbl[1];OSMapTbl[prio & 0x07] = OSMapTbl[4] = 0b00010000 = 0x10;
iii. 使任务脱离就绪态算法(可略)
将任务就绪表0SRdyTbl [prio>>3]相应元素的相应位清零,这时就能使任务脱离就绪态。
使任务进入就绪态算法如下:
OSRdyTbl[prio>>3]所有的位都是零时,OSRdyGrp 的相应位才清零
iv. 根据就绪表确定最高优先级算法(核心:查表)
思考:我们知道OSRdyGrp=0x24,OSRdyTbl[2]=0x12,根据以上条件如何确定最高优先级?
- 由OSRdyGrp定义的规则,根据OSRdyGrp = 0x24 = 0b0010 0100 可以知道OSRdyTbl[2]和OSRdyTbl[5]中都至少有一位是1。
- 根据就绪表,在表前的优先级更高,所以取在前面的OSRdyTbl[2],舍弃OSRdyTbl[5]。
OSRdyTbl[2]=0x12 = 0b00 010 010,对应的是OSRdyTbl[2]这一行的第2个任务和第5个任务,取在前面的第2个任务。
- 最后计算最高优先级,prio = 2*8 + 2 -1= 17 = 0x11。(2 * 8意义是表示跳过OSRdyTbl[0]、OSRdyTbl[1]这两列;2表示OSRdyTbl[2]的第二位;-1是因为优先级从0开始,不是从1开始,所以得减1)
根据以上的思想,设计一个算法,具体算法如下:(high3表示prio3-5,low3表示0-2位)
计算机如何看出是第几行是1呢?可以采用扫描的方法(一位一位的&上一个数就行),也可以是查表法。
为了使调度具有固定的时间,还是需要采用查表法,故这里需要查另外一张表,即优先级判定表 OSUnMapTbl[256]。
INT8U const OSUnMapTbl[256] = {
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=0x24,OSRdyTbl[2]=0x12,求最高优先级。
- High3 = OSUnMapTbl[OSRdyGrp] = OSUnMapTbl[0x24] = 2
- Low3 = OSUnMapTbl[ OSRdyTbl[High3] ] = OSUnMapTbl[ OSRdyTbl[2] ] = OSUnMapTbl[ 0x12 ] = 1
- Prio = (High3<<3) + Low3 = 2*8 + 1 = 17
OSUnMapTbl解析
OSUnMapTbl是根据所给的u8类型的数据,返回数据的最低置1位所在的位置。如传入下标0x02, 0x02最低置一位是第1位(从第0位算起),所以OSUnMapTbl对应于0x02的返回值是1。
以下是该表的一些特性:
- 后四位是奇数1、3、5、7、9、11、13、15,返回0,奇数最后一位都是1,最后一位是第0位,所以返回0。
-后四位是2、6、10、14,返回1。这类数最低的置1位是倒数第二位,倒数第二位是第1位,所以返回1。
- 后四位是2、4、8,分别返回1,2,3 。
- 后四位是12的返回2。
- 后4位为0,这时就根据前4位返回最低的置1位。如0x10第4位(从第0位算起)置1,所以返回4。