3.1任务的基本概念
一、任务及内核结构
在设计一个较为复杂的应用程序时,通常把任务分成若干个小问题,这样使系统并发运行多个任务,提高处理器的利用率,加快程序的执行速度,所以现代操作系统都是多任务操作系统。UCOSII就是一个多任务的操作系统。
UCOSII的任务由任务程序代码(函数)、任务堆栈、任务控制块这三个部分组成。
PC的值总是待要执行指令的地址,根据任务是否具有自己的私有运行空间,分为进程和线程。UCOSII中没有给任何定义私有空间,所以UCOSII中的所有任务就叫做线程。
一个任务可以在睡眠状态,就绪状态,运行状态,等待状态,中断服务状态5个状态中发生转换。
二、用户任务代码的一般结构
根据嵌入式系统任务的工作特点,任务的执行代码通常是一个无限循环结构,在这个循环中可以响应中断,这种结构也叫做超循环结构。
从程序设计的角度来说,一个UCOSII的任务代码就是一个C语言函数,为了传递各种不同的类型的数据甚至是函数,所以UCOSII的任务的参数定义成了一个VOID类型的指针。
代码中的OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()是UCOSII定义的两个宏,OS_ENTER_CRITICAL()封装了关中断的代码,而OS_EXIT_CRITICAL()封装了开中断的代码,即处于这两个宏之间的代码是不会被中断的,把这种受保护的代码段叫做临界段,所以OS_ENTER_CRITICAL()叫做临界段宏,而OS_EXIT_CRITICAL()叫做推出临界段宏。
从程序代码的形式上来看,用户任务就是一个C语言函数,但这个函数不是主函数调用 的函数,在系统中它与main函数处于平等地位,但是何时被调用以及何时被终止由操作系统决定的。
应用程序结构:
三、系统任务
操作系统除了管理用户任务之外还要管理系统任务,即系统的内部事务要处理。
UCOSII预定义了两个系统任务:空闲任务和统计任务,空闲任务的不是必须的,但是统计任务是应用程序根据实际需要来使用。
前面所说任务分为5个状态,即系统极有可能在某个时间内处于空闲状态。空闲任务是在系统空闲时,有事可做。
统计任务就是统计cpu在单位时间内被使用的时间,以百分比的形式保存在变量OSCPUsage中。用户根据实际需要来选择是否使用统计任务,若用则把定义在头文件中的OS_CFG.H中的系统配置常数OS_TASK_STAT_EN设置为1,并在程序中调用函数OSStatInit()对统计任务进行初始化。
四、任务优先级权和优先级
UCOSII采用了优先级抢占式规则,即系统中的每个任务都按照其任务的重要性分配有一个惟一的优先级别。任务级别高的先运行,在UCOSII中最多创建64个任务,每个级别都用一个整数数字来表示,即0,1,2,3……,63。数字越小,优先级别越高
在系统配置文件OS_CFG.H中定义了一个用来表示最先优先级的常数OS_LOWEST_PRIO,用户通过对其赋值来设置所需任务的实际数目。一般最低优先级OS_LOWEST_PRIO设置为空闲任务,若有统计任务,一般设置为OS_LOWEST_PRIO-1;所以还有OS_LOWEST_PRIO-2个优先级可用。
3.2任务堆栈的创建
为了定义堆栈方便,在文件OS_CPU.H中专门定义了一个数据类型OS_STK。在定义任务堆栈的栈区时,只要定义一个OS_STK类型相同的数组就可以了。
当调用函数OSTaskCreat()来创建一个任务时,把数组和指针传递给函数OSTaskCreat()中的堆栈栈顶的参数ptos,就可以把该数组与任务关联起来而成为任务的任务堆栈。
堆栈增长的方向是随系统所使用的处理器不同而不同的,有的处理器要求堆栈增长方向是向上的,而有些则是向下的。使用OSTaskCreat()来创建任务时,一定要注意所使用的处理器所支持堆栈的增长方向。
在应用程序创建一个新任务时,必须把系统启动这个任务时所需要的CPU各寄存器的初始数据(任务指针,任务堆栈指针,程序状态字等)事先放在任务堆栈。这样当获得CPU的使用权时,就把堆栈的内容复制到CPU的各个寄存器中。函数的初始化就是完成这项工作。
3.3任务控制块及其链表
任务的控制块是用来记录任务的堆栈指针、任务的当前状态、任务的优先级等与任务相关的属性。系统通过任务控制块来感知和管理任务,没有任务控制块的任务不能被系统承认和管理。UCOSII把系统所有的任务控制块链接为两条链表,并通过他们管理各个任务。
任务控制块上的所需的两条链表:一条控任务块链表(任务控制块未分配任务)和一条任务链表(任务控制块已分配任务)。
当应用程序调用函数OSTaskCreat()创建一个任务时,这个函数会调用系统函数OSTCBInit()来为任务控制块进行初始化。
该函数的主要任务如下:
l 为被创建任务从空任务控制块
3.4任务就绪表及任务就绪
多任务操作系统的核心任务就是给处于就绪状态的任务分配CPU。这项工作涉及两项技术:一是判断哪些任务处于就绪状态;二是进行任务调度,即通过一个算法在就绪任务中确定马上要执行的任务。
系统用一张就绪任务登记表,登记了系统中所有处于就绪状态的任务。用一个二进制位表示一个任务,1为对应任务处于就绪状态,0表示对应任务处于非就绪状态。所以一个元素可以表示8个任务状态,常把这8个任务看成一个任务组,如果任务组中有任务就绪,就把任务组置1,否则置为0。
对就绪表的三个操作:登记、注销、最高优先级就绪任务的查找(prio)
登记:当任务处于就绪状态时,系统将该任务登记在就绪表中,即就绪表中对应位置置1。
注销:当某个任务脱离就绪状态时,系统在就绪表中将该任务对应位置置0。
最高优先级的就绪任务的查找:
y = OSUnMapTal[OSRdyGrp]; //获得优先级别的D5,D4,D3位
x = OSUnMapTal[OSRdyTbl[y]]; //获得D2,D1,D0位
prio = (y << 3) + x; //获得就绪任务的优先级别
或者
y = OSUnMapTal[OSRdyGrp];
prio = (y << 3) + OSUnMapTal[OSRdyTbl[y]];
任务调度:
令CPU中止当前正在执行的任务转而去执行另一个任务叫做任务的调度
每时每刻让优先级最高就绪任务处于运行状态。
具体做法:它在系统或用户任务调用函数及执行中断任务服务程序结束时来调用调度器,以确定应该运行的任务并运行它。
ucosII有两种调度器。一种是任务级的,另一种是中断级的调度器。两者分别与OSSched()和OSIntExt()函数来实现。
任务调度器的步骤:第一步是获得运行任务的指针;第二步是进行断点数据的切换。
任务切换宏OS_TASK_SW()
任务切换就是中止正在运行的任务,转而去运行另一个任务的操作。
任务的切换就是断点数据的切换,即cpu堆栈指针的切换。被中止运行任务的任务堆栈指针要保护到该任务的任务控制块中,待运行任务的任务堆栈指针要由该任务控制块转存到CPU的SP中。
为了完成操作,OSCtxSw()完成下列7项工作:
(1)把被中止任务的断电指针保存到任务堆栈中
(2)把cpu通过寄存器的内容保存到任务堆栈中
(3)把被中止任务的任务堆栈指针当前值保存到该任务的任务控制块的OSTCBStkPtr中
(4)获得待运行任务的任务控制块
(5)使cpu通过任务控制块获得待运行的任务堆栈指针
(6)把待运行任务对战中通用寄存器的内容恢复大奥cpu的通用寄存器中
(7)使cpu获得运行任务的断点指针
ucosII是把当前正在运行任务的任务控制块的指针放到一个指针变量OSTCBCur 中,并且在调度器的前面代码中已经得到了待运行的任务控制块指针OSTCBHighRdy,所以第(2)~(6)项工作非常容易,其示意性代码如下:
OSTCBCur -> OSTCBStkPtr = sp; //把sp保存在中止任务控制块中
OSTCBCur = OSTCBHighRdy; //使系统获得运行任务控制块SKSp = OSTCBHighRdy -> OSTCBStkPtr; //把待运行任务堆栈指针赋给sp
第(1)和(7)项工作通过中断将断电指针推入cpu的pc寄存器的功能,恢复带运行任务的断点。一般情况下,中断服务函数要用汇编语言编写。用宏OS_TASK_SW()来引发中断,若微处理器有软中断,那么宏里封装一个软中断,若微处理器没有软中断,在宏中封装其他可以使PC等相关的寄存器压栈的指令。
3.5 任务创建
UCOSII用OSTaskCreat()和OSTaskCreatExt()来创建任务,其中OSTaskCreatExt()是OSTaskCreat()扩展,并提供了一些附加功能。
用OSTaskCreat()创建任务时,函数对待创建任务的优先级进行判断,确认该优先级合法且未被使用之后,就调用函数对任务堆栈和任务控制块初始化。初始化成功之后,把任务计数器加1和判断OSRunning的值是否为1,若为1就进行任务调度。
用OSTaskCreatExt()创建任务时,更加灵活,但是会增加额外开销。原型如下:
UCOSII有一个规定,必须在调用启动任务函数OSStart()之前,必须已经创建了至少一个任务。所以,一般习惯上在调用函数OSStart()之前先创建一个任务。
3.6任务的挂起和恢复
任务的挂起就是停止任务的运行。用户任务可以通过调用系统提供OSTaskSuspend()函数来挂起自身或者除空闲任务之外的其他任务,只能在其他任务中通过调用恢复函数OSTaskResume()恢复就绪状态。
任务在运行状态、就绪状态和等待状态之间转移关系。
挂起:
函数原型OSTaskSuspend():
INT8U OSTaskSuspend(INT8U prio);
参数prio为优先级别,函数要挂起自身,参数为常数OS_PRIO_SEI,F,在uCOS_H.H中定义为0xFF。
流程:
先判断是否调用该函数的任务本身,如果是,则删除任务在就绪表中的就绪标志,做起挂起记录,引发任务调度;如果不是,则删除被挂起任务 的就绪标致,并做挂起记录
恢复任务:
原型:INT8U OSTaskResume(INT8U prio);
流程:判断任务是否挂起且不是一个等待任务,就清除挂起记录
3.7 其他任务管理函数
修改任务优先级别:
任务可根据需要调用函数OSTaskChangePrio()来改变任务的优先级别。
删除任务:
把要删除的任务的任务控制块从任务控制链表中删除,并归还给空任务控制块链表,在任务就绪表中把该任务的就绪状态位置置为0。
可以调用函数OSTaskDel()来删除任务自身或除了空白任务之外的其他任务。如果调用这个函数是为了删除自己,在调用函数时令函数的参数prio为OS_PRIO_SELF。
若是为了删除一个占用资源的任务,需要调用OSTaskDelReq()。
查询任务信息:
用OSTaskQuery()来获取选定任务的信息,若查询成功,则返回OS_NO_ERR,并将查询得到的任务信息存放在结构OS_TCB类型变量中。
3.8 UCOSII的初始化和任务的启动
初始化:
在使用ucosii所有服务之前,必须调用ucosii的初始化函数OSInit()。OSInit()对UCOSII的所有全局变量和数据结构进行初始化,同时创建空闲任务OSTaskIdle,并赋之以最低的优先级别和永远就绪状态。
初始化函数OSInit()对数据结构进行初始化时,主要是创建包括空任务控制块链表在内的5个空数据缓冲区。
启动:
任务管理从调用OSStart()开始,前提是至少创建了一个用户任务。