系统任务调度时总是从就绪任务表中选择一个任务运行的,因此需要一个表来记录所有就绪任务,在uCOS中使用的是位图这种数据结构。其基本思路是定义一个类型为INT8U的数组OSTdytbl[],使用这个数组来表示所有是否处于就绪状态,OSRdyTbl[]数组的每一个元素都有8位,每一位由两种状态,因此可以使用1代表处于就绪状态,0代表不处于就绪状态。前面提到过uCOS一共最多只有64个任务,因此OSRdyTbl[]的元素个数只要为8就能表示所有的任务是否处于就绪状态了。那么每个任务在OSRdyTbl[]里面是怎么表示的呢?OSRdyTbl[]数组元素的每一位对应一个任务,表示是否处于就绪状态,那么究竟是怎么对应的呢?
答案是根据中断优先级来对应的,因为uCOS-II里面每个任务都有唯一的一个优先级,因此,优先级可以作为任务的标识。具体的顺序是这样的,OSRdyTbl[0]对应优先级为07的任务,OSRdyTbl[1]对应优先级为815的任务,依此类推,最后一个元素OSRdyTbl[7]对应优先级为56~63的任务。每八个任务又称为一个任务组。这样OSRdyTbl[]数组里面每一位都与一个任务对应,这样就可以知道每一个任务是不是处于就绪状态了。
为了方便对任务就绪表的查找,uCOS-II又定义了一个INT8U类型的变量OSRdyGrp,OSRdyGrp变量的每一位都对应OSRdyTbl[]的一个元素。也就是说OSRdyGrp变量的每一位都对应了一组(八个)任务,如果这组任务中存在就绪任务,那么就将对应于OSRdyGrp变量的该位置为一。
这样通过OSRdyGrp就能确定哪个组有任务就绪了。然后再去OSRdyTbl[]中找具体的任务,并且能知道最高优先级的处于就绪状态的任务。
因为uCOS-II最多只有64个任务,优先级最大为63,63用二进制表示只需要6位(00111111),因此其实OSRdyGrp变量只需要六位就可以完成对任务的管理了。这样可以将优先级别看成一个6位的二进制数。这样高三位可以指定OSRdyGrp的具体数据位,也就是OSRdyTbl[]数组的下标;低三位可以指定OSRdyTbl[]具体的数据位,也就是具体的任务。
比如:一个任务优先级为prio=30,用二进制表示:011110。高三位值为:3,低三位值为:6;因此这个任务在OSRdyTbl[]数组中的位置为OSRdyTbl[3]的第6位.
主要有三个操作:登记、注销和最高优先级任务查找
登记的含义是:某个任务处于就绪状态时,系统将该任务登记在任务就绪表中,即将OSRdyTbl[]、OSRdyGrp对应的位置一。具体代码如下:
OSRdyGrp |= OSMapTbl[prio >> 3];
OSRdyTbl[prio >> 3] |= OSMapTbl[prio&0x07];
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 (OSRdyTbl[prio >> 3] &= -oSMapTbl[prio&0x07] == 0)
OSRdyGrp & = -OSMapTbl[prio >> 3];
在uCOS-II中CPU总是将控制权交给优先级最高的任务,因此调度器需要能知道当前任务中最高优先级任务在哪里。
y = OSUnMapTbl[OSRdyGrp]; // 优先级的D5、D4、D3位
x = OSUnMapTbl[OSRdyTbl[y]]; // 优先级的D2、D1、D0位
prio = (y << 3) + x;
OSUnMapTal[]是uCOS-II为了提高查找最高优先级任务定义的一个数组。
uCOS-II中,任务调度由任务调度器完成。任务调度器工作有两个:第一,在任务就绪表中查找优先级最高的任务;第二,实现任务的切换。uCOS-II中,调度器分两种:任务级(OSSched())和中断级的(OSIntExt())。这里介绍的是OSSched()。
任务是由任务控制块管理的,所以调度器实现任务切换之前需要获得待运行任务的任务控制块指针和当前任务的任务控制块指针。
当前任务控制块指针由OSTCBCur指向。因此调度器主要是要获得当运行任务的任务控制块指针。
源码如下:
void OSSched(void)
{
# if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
# endif
INT8U y;
OS_ENTER_CRITICAL();
if ((OSLockNesting | OSIntNesting) == 0)
{
y = OSUnMapTbl[OSRdyGrp];
OSPrioHightRdy = (INT8U)((y << 3) + UnMapTbl[OSRdyTbl[y]]); // 得到最高优先级优先任务
if (OSPrioHightRdy != OSPrioCur)
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHightRdy]; // 得到任务控制块指针
OSCtxSwCtr++; // 任务切换次数计数器加1
OS_TASK_SW(); // 任务切换宏
}
}
}
OS_TASK_SW()的切换主要是OSCtxSw()完成的,OSCtxSw()依次做如下七件事:
对于uCOS-II来说,2~6项任务容易完成,但是第一项和最后一项不容易弄,因为uCOS-II里面没有置零可以直接将PC压栈和出栈。
主要是两个函数OSTaskCreate()和OSTaskCreateExt()。OSTaskCreateExt()是OSTaskCreate()的扩展。
OSTaskCreate()的源码如下:
INT8U OSTaskCreate (void (*task)(void *p_arg), // 指向任务的指针
void *p_arg, // 传递给任务的参数
OS_STK *ptos, // 指向任务堆栈栈顶指针
INT8U prio) // 任务的优先级
{
OS_STK *psp;
INT8U err;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
可以看到在OSTaskCreate()源代码可以看出,函数中调用了OSTaskStkInit()和OSTCBInit()对任务堆栈和任务控制块进行初始化。
源码如下:
INT8U OSTaskCreateExt (void (*task)(void *p_arg), // 指向任务的指针
void *p_arg, // 传递给任务的参数
OS_STK *ptos, // 指向任务堆栈栈顶的指针
INT8U prio, // 任务优先级
INT16U id, // 任务的标识
OS_STK *pbos, // 任务堆栈栈底的指针
INT32U stk_size, // 任务堆栈的容量
void *pext, // 指向附加数据域的指针
INT16U opt); // 用于设定操作选项
uCOS-II有一个规定:在调用启动函数OSStart()函数之前,必须已经创建了至少一个任务。因此习惯上在调用函数OSStart()之前先创建一个任务,并赋予它最高优先级别,让它成为起始任务;然后在这个起始任务中,再创建其他各个任务。
挂起就是停止这个任务的运行。uCOS-II中,用户可以通过OSTaskSupend()函数来挂起自身或者除空闲任务之外的任务。用OSTaskSupend()挂起的任务只能在其它任务里面通过调用恢复函数OSTaskResume()使其恢复为就绪任务。
INT8U OSTaskSuspend(INT8U prio);
OSTaskSuspend函数参数为优先级,如果要挂起任务自身,那么参数就是:OS_PRIO_SELF(在uCOS_II.H)中被定义。调用成功返回OS_NO_ERR;
INT8U OSTaskResume(INT8U prio);
参数同样是任务优先级。调用成功返回:OS_NO_ERR;
OSTaskChangePrio()
INT8U OSTaskChangePrio(
INT8U oldprio, // 要修改的优先级别
INT8U newprio // 要修改的优先级别
)
若OSTaskChangePrio()成功,则函数返回OS_NO_ERR。
所谓删除一个任务,就是把该任务置于睡眠状态。实际就是将该任务的任务控制块从任务控制块中删除,并归还给任务控制块,然后在任务就绪表中把该任务的就绪状态设置为0,于是该任务就不能再被调度器所调用了。简单地说,就是将它的身份证吊销了。
在任务中通过OSTaskDel()来删除任务自身或者除了空闲任务之外其他任务。
INT8U OSTaskDel(INT8U prio) //要删除任务的优先级
如果一个任务要删除自己,那么OSTaskDel()的参数为OS_PRIO_SELF。
一般任务会占用一些动态分配的内存或者信号量之外的资源,如果直接删除的话,会使得这些任务没有释放导致数据出问题。因此在删除一个任务的时候必须要谨慎,在uCOS设置了一种方法来避免这种问题,具体做法是:如果A任务要删除B任务,那么A任务只负责提出申请删除B任务,具体的删除操作由B任务自己实现,这就给了任务B足够的缓冲时间。但是这样就意味着A、B任务必须要有某种通信方法。uCOS-II提供了一个OSTCBDelReq作为双方的联络信号,同时提供了一个双方都能调用的函数–OSTaskDelReq()。这样,双方就都能通过这个函数来访问OSTCBDelReq这个信号,从而可以根据这个信号的状态来决定各自的行为。
INT8U OSTaskDelReq(INT8U prio) // 待删除任务的优先级别
任务请求删除方调用这个函数的目的就是要查看被删除的任务控制块是否存在,如果存在,就将被删除任务控制块成员OSTCBDelReq的值设置为OS_TASK_DEL_REQ,意思就是让被删除任务在适当时候删除自己。如果不存在,就认为被删除任务已经删除了。
被删除任务方一定要用OS_PRIO_SELF作为参数来调用OSTaskDelReq(),在OSTaskDelReq()判断出调用参数是OS_PRIO_SELF时,会返回任务TCB的OSTCBDelReq的值,从OSTCBDelReq的值就可以知道是不是有其他任务提出要删除自己了,如果是,那么就在合适的地方调用函数OSTaskDel(OS_PRIO_SELF)来删除自己。
举个栗子:
申请删除任务方:
while(OSTaskDelReq(44) != OS_TASK_NOT_EXIT)
{
OSTimeDly()1; // 延迟一个时钟节拍
}
被删除任务方:
if (OSTaskDelReq(OS_PRIO_SELF) == OS_TASK_DEL_REQ)
{
// 释放内存空间、信号量等等
OSTaskDel(OS_PRIO_SELF);
}
else
{
// 其他代码
}
通过函数OSTaskQuery()来获取选定任务的信息。
INT8U OSTaskQuery(
INT8U prio, // 待查询任务的优先级别
OS_TCB * pdata // 存储任务信息的结构
)
调用成功返回:OS_NO_ERR,并将信息放在Pdata中。
在使用uCOS-II所有服务之前,必须调用uCOS-II的初始化函数OSInit(),对uCOS-II自身的运行环境及进行初始化。
函数OSInit()将对uCOS-II所有全局变量和数据结构进行初始化,同时创建空闲任务OSTaskIdle,并赋给最低优先级和永远的就绪状态。如果还要使用统计任务,那么统计任务的优先级为OS_LOWEST_PRIO-1。
uCOS-II进行任务的管理是从调用启动函数OSStart()开始的。调用OSStart()之前必须创建了至少一个用户任务。
void OSStart (void)
{
if (OSRunning == OS_FALSE) {
OS_SchedNew(); /* Find highest priority's task priority number */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
OSStartHighRdy()函数的功能是:设置系统运行标志位OSRunning=TRUE,将就绪表中最高优先级任务的栈指针Load到SP中,并强制中断返回。这样就绪的最高任务就如同从中断返回到运行状态一样,使得整个系统得以运转
。
嵌入式实时操作系统uC/OS-II原理及应用