首先要了解任务的概念。任务其实就是一个线程,或者更通俗点说就是一个函数和与之相关联的一些数据结构构成的一个实体。
ucos-II中人物包含三个部分:
1、 任务控制块:保存了任务的属性;
2、任务堆栈:保存了任务的工作环境;
任务堆栈用于保存任务的私有数据,以及在任务切换和中断的时候保存断点数据(CPU的PC、PSW(程序状态字)、通用寄存器中的数据)。所以每个任务都需要一个任务堆栈。ucos-II中定义了一个无符号的16位的整形作为堆栈的基本单元。
即:typedef unsigned int OS_STK; //为16位长
示例:OS_STK MyTask[512]; //定义堆栈的长为1024字节
3、任务程序代码:任务的执行部分。
任务的执行代码通常都是一个无线循环结构,但是在其循环中能够响应中断,这种结构也叫做超循环结构。
在讲任务调度之前,先来了解下任务控制块链表。
ucos-II中任务控制块分为两个部分
1、 空任务链表
因为此链表中这些控制块还没有与具体任务相关联,所以叫做空任务链表。
大小为ucos-II.H中定义的OS_MAX_TASKS + OS_N_SYS_TASKS(即用户任务最大数+系统任务总数)个。其初始化由系统初始化函数OSInit()完成。
其头指针为OSTCBFreeList,其为一个OS_TCB类型的数组。
即OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];
2、 任务链表
每当应用程序调用系统函数OSTaskCreate()或OSTaskCreateExt()创建一个任务的时候,系统就会将空任务链表头指针OSTCBFreeList 指向的任务控制块分配给该任务。在给任务控制块中的成员赋值后,就将其加入OSTCBList所指向的任务控制链表中,于是,就出现了任务链表。在任务链表中,系统任务(如空闲任务、统计任务)都存放在其优先级最低的地方,其中空闲任务的优先级最低,统计任务(如果有的话)的优先级次低。
任务控制块访问优化:
为了加快对任务控制块的访问速度,任务链表为双向链表。同时,设置了一个OS_TCB*类型的指针数组OS_TCBPrioTbl[]。其中以任务的优先级为顺序在各数组元素中存放指向该任务控制块的指针。
同样,为了能随时访问正在运行任务的任务控制块,还定义了一个OS_TCB*类型的OSTCBCur,专门用于存放当前正在运行的任务的任务控制块的指针。
最后,在讲任务调度之前还需要了解一个概念,就是任务的优先级。
任务的优先级是在任务创建(下文将具体说明)时便定义了的。ucos-II中prior为0的任务的优先级最高。
那么当定义了一大堆任务的时候,系统怎么样去分辨哪个就绪任务的优先级别最高呢?什么是就绪任务呢?这时候就要提到任务就绪表的概念了。任务就绪表就是系统中的每个人物都在这个表里面占据了一个位置,并用这个位置的状态(1或者0)来表示任务是否处于就绪状态。有了任务就绪表,我们就能很快的知道该时刻哪个就绪任务的优先级别最高。下面具体来了解一下任务就绪表的结构。
ucos-II定义了一个类型为INT8U类型的数组OSRdyTbl[]来充当这个任务就绪表。该数组中每一个元素分别用于描述8个任务(即一个任务组)的就绪状态。系统又定义了另一个INT8U类型的变量OSRdyGrp,它中间的每一位分别对应于OSRdyTbl[]中的一个元素,用于表示该任务组中是否有任务处于就绪状态。例如OSRdyGrp=1000 0101,则表示OSRdyTbl[7]、OSRdyTbl[2]、OSRdyTbl[0]中又任务处于就绪状态。
那么我们怎么样确定这些处于就绪状态的任务的优先级呢?这时候,我们便要借助一个系统定义好的数组OSUnMapTbl[]来达到这个目的。OSUnMapTbl[]是系统定义的一个INT8U类型的数组,它表示的是一个8bit数据最低位为1的位置是哪一位。
如OSUnMapTbl[1001 0100] = 2;
下面我们来看怎样从任务就绪表中获取最高的就绪任务
y = OSUnMapTbl[OSRdyGrp]; //获取OSRdyGrp中为1的最低位是第几位
x = OSUnMapTbl[OSRdyTbl[y]]; //获取该任务组中为1的最低位是第几位
prio = (y << 3) + x; //prio即为最高优先级
有了上面的基础后,我们来看看ucos-II中和任务相关的一些操作
1、 任务的创建
创建任务实际上是创建一个任务控制块,并通过任务控制块把任务代码和任务堆栈关联起来形成一个完整的任务,并使刚刚创建的任务进入就绪状态,并接着引发一次任务调度。(注:区分任务其实就是其优先级,所以任务可以同名,但是优先级必须是唯一的)。
OSTaskCreate()创建任务的过程:首先先判断其优先级是否合法,如果合法的话,那么接着调用OSTaskStkInit()和OSTCBInit()对其任务堆栈和任务控制块进行初始化,初始化成功后,将任务计数器OSTaskCtr+1,若ucos-II的核处于运行状态(即OSRunning ==1),则接着引发一次任务调度。
2、 任务挂起
OSTaskSuspend()挂起任务的过程:首先判断待挂起的任务是否调用了这个函数的任务本身,如果是任务本身,则必须删除任务在任务就绪表中的就绪标志,并在任务控制块成员OSTCBStat中做挂起记录,然后引发一次任务调度。如果待挂起的任务不是调用函数的任务本身,而是其他任务,那么只要删除任务就绪表中北挂起任务的就绪标志,并在其任务控制块成员OSTCBStat中作挂起记录就行了。
3、 任务删除
OSTaskDel()删除任务的过程:把被删除的任务控制块从任务控制块链表中删除,并归还给空任务控制块链表,然后在任务就绪表中将该任务的就绪状态置0。(注:可以通过该函数来删除任务自身或者除了空闲任务之外的其他任务)。
4、 任务恢复
OSTaskResume()恢复任务的过程:首先判断其是否是已挂起的任务,并且同时又不是一个等待任务,如果符合条件,则清楚其OSTCBStat中的挂起记录并使该任务就绪,再接着进行一次任务调度。
任务创建的一般步骤:
在调用启动函数OSStart()之前,必须先创建一个任务,并赋予它罪该优先级别,使其成为起始任务,然后在这个起始任务中,再创建其他任务。(下面是一个程序清单的演示)。
5、任务调度
接着,来看看最关键的任务调度。
任务调度是靠任务调度器完成的,ucos-II中有两种任务调度器,一种是任务级的调度器(OSSched()),另一种是中断级的任务调度器(OSIntExt()),这里只将任务级的任务调度器。
任务调度器(OSSched())有两个作用:一是直接查找出最高优先级的就绪任务;二是实现任务的切换,即让最高优先级的任务运行。OSSched()中调用了OS_Task_sw()来完成任务的切换。
OSSched()的工作过程:首先判断任务调度器是否被上锁且不是中断服务程序(ISR)中调用任务调度器(ISR程序应该调用OSIntExt()),如果满足条件,则查找出任务就绪表中的最高优先级的任务(查找方法上文已经说过),然后判断该任务是否是当前任务,如果不是,则将该任务的的任务控制块指针赋值给OSTCBHighRdy,并给统计任务切换次数的计数器(OSCtxSwCtr)加1.最后调用OS_TASK_SW()进行任务切换。
OS_TASK_SW()的工作过程:因为任务切换的本质就是断点数据的切换,所以OS_TASK_SW()主要的工作就是保存和恢复断点数据(CPU的pc、PWS和通用寄存器中的数据)。当任务中止时,OS_TASK_SW()先将该任务的断点数据保存在任务的堆栈中,然后将该任务堆栈的指针保存在该任务控制块成员OSTCBStkPtr中。任务恢复运行时,首先从其任务控制块成员OSTCBStkPtr中获得该任务堆栈的指针,并将其放入CPU的堆栈指针SP中,接着进行断点数据的恢复,最后CPU获得运行任务的断点指针(PC)。