µC/OS-II内核任务调度模块的扩展

来源:互联网 发布:149j最新x站免费域名 编辑:程序博客网 时间:2024/05/16 08:09
 

摘 要:µC/OS-II是一个实时操作系统内核,支持64种不同优先级的任务。以简单实用为原则,借用内核中的两个优先级任务,充当时钟源和轮询引擎,让同级任务在最低优先级任务下轮流运行。这在不失实时性的前提下,让内核支持多达192个同级任务,极大地扩展了µC/OS-II的灵活性和应用范围。

关键字: µC/OS-II;同级任务;时间片轮询调度;实时性;扩展

中图分类号: TP316    文献标识码: A

Expansion on task scheduling module of µC/OS-II kernel

 

Abstract : µC/OS-II is a real-time operating system kernel, supporting tasks in 64 different priorities. With the principle of simple and practical, it borrows two priority tasks in the kernel as the clock source and the polling engine, so that tasks with the same priority work under the lowest priority in turns. The method allows the kernel to support up to 192 tasks in the same priority  without losing the real-time, which greatly expands the flexibility and range of applications of µC/OS-II .

Key words : µC/OS-II ; Same priority task ; Round-Robin algorithm scheduling ;Real-time ; Expansion

0    引言

物联网时代来临,嵌入式系统用途愈加广泛。嵌入式系统一个极为核心的技术就是嵌入式操作系统。目前常用的嵌入式操作系统有µClinux、µC/OS-II、WinCE、VxWorks等。其中µC/OS-II以其实时性强、源码开放、高效而小巧等优点得到了比较广泛的应用。

µC/OS-II是一个具有基本功能的操作系统内核,包括基于优先级的可剥夺式任务调度和简单的内存管理等。µC/OS-II内核支持64种不同优先级的任务,不支持同级任务,不支持时间片轮询任务调度[1]。这样的系统完全为硬实时环境而设计,在某些实际应用中局限了系统的灵活性和应用范围,例如多点的温度或气压数据采集,这些任务往往需要同优先级进行调度。

给µC/OS-II添加同级任务调度,内核研究者提出了各种不同的方法。文献[2]中提出一种将8个优先级任务转换为相同优先级任务,并基于时间片轮询调度的方法;文献[3]中提出一种将任务调度在优先级抢占与时间片轮询之间轮流选用的方法。文献[4]中提出一种将单一优先级任务扩展为优先级任务链表,从而达到添加同级任务的方法。上述方法从不同的技术角度为µC/OS-II添加了同级任务调度,尽管有的方法从理论上讲符合逻辑,但是实际操作较为复杂,方案实用性较差、实时性难以保证。在不失实时性,同时尽可能少改动原有内核架构的前提下,提出一种占用2个优先级任务,为系统扩展出最多192个同级任务基于时间片轮询调度的方案。

1    思路与模型

在添加基于时间片轮询的同级任务调度时,模仿了µC/OS-II内核原有数据结构。

1.1 总体思路

设计的总体思路是:模仿优先级任务链表,创建一个同级任务链表。创建两个优先级任务A和B,且B=A+1。A以时间片为单位周期性的进入等待或运行,从而周期性的中断B。 B每次运行总是从同级任务链表取一个处于就绪态的同级任务,然后跳入同级任务执行。当没有比A更高优先级的任务时,同级任务以时间片为单位轮流在B下执行。B每次中断,总是将断点保存到当前运行的同级任务堆栈。B下次被内核调度运行,不会返回同级任务,而是重新进入B的任务,开始新一轮的轮询。

1.2  时钟源

时间片轮询调度算法,首先面临的问题是时钟来源。可以利用原有系统的功能,模拟时钟源。创建一个优先级任务A(下面用A标识此任务),利用系统延时函数OSTimeDly(),周期性的让A进入等待状态。创建一个优先级任务B(下面用B标识此任务,B=A+1),B的作用是轮询调度同级任务。每当A进入等待状态,如果没有比A更高优先级的任务,内核会调度B运行。一旦A等待结束进入就绪态,内核会中断B,由此B周期性地运行或中断,则自发为B提供了时钟源。由于A和B是内核中的两个优先级任务,它们服从内核调度,所以不会破坏内核的实时性。

1.3  构造同级任务

µC/OS-II以一个字节存贮任务优先级,使用64种不同的优先级状态。一个字节可以表达256种状态,剩余的192种状态可用来标识同级任务。同级任务的状态标识从64开始,256为最大。192个同级任务仍然用优先级数字标识,但是这里的优先级数字不再代表同级任务的优先级别,仅仅作为区分同级任务的编号。

同级任务的数据结构模仿优先级任务。仿照优先级任务链表创建同级任务链表,同级任务的优先级编号从64开始。在调用OSTaskCreate()等函数创建任务时,如果任务优先级是介于64到256之间,说明任务是同级任务,需要添加到同级任务链表中。UserTbl[]数组表示同级任务控制块数组。每当创建新的同级任务时,总是将空的任务链表头UserFreeList指向的任务控制块分配给该任务。在给任务控制块中的各成员赋值后,就按任务控制块链表的头指针UserList将其加入到任务控制块链表中[5]。UserCur指向链表中即将调度的同级任务。具体数据结构如下图所示。

图1:同级任务控制块数据结构

1.4  轮询引擎

1.2中已经说明,“同级任务轮询引擎”是系统的一个优先级任务,用B标识。当B第一次进入任务代码时,会将当前运行位置保存到一个全局变量(在C语言中可通过setjmp()来完成),便于在其它地方可以跳回此处。此后,B取得同级任务链表中UserCur指向的任务控制块,检查UserCur指向的任务是否处于就绪状态,是则跳入此同级任务去运行,否则调整UserCur指向下一个控制块。

UserCur指向的同级任务运行一个时间片后,A从等待态进入就绪态,进而中断B。B立即将当前的运行断点保存到UserCur指向的同级任务堆栈中。当下一次B调度运行时,B会跳回第一次运行时设置的全局变量断点处(在C语言中可通过longjmp()来完成),开始新一轮的轮询。

A不仅提供了B的时钟源,还负责更新UserCur。每次A从等待态进入运行态时,将UserCur重新指向下一个节点,由此使得B每次轮询时都是从新的同级任务开始。这里有必要设置一个全局变量UserCount,表示同级任务链表中处于就绪态任务的个数,如果其值为0,A不需要更新UserCur,B也会进入等待状态,从而不浪费系统资源。轮询引擎运行过程如下图。

图2: 同级任务调度流程模型

2    同级任务调度的实现

所用源代码是µC/OS-II 2.52,以下所有函数及变量均基于此。

2.1  时钟节拍

内核中每发生一次时钟中断,均会调用OSTimeTick()。OSTimeTick()负责更新任务的延时节拍,将延时结束的任务更新到就绪表,添加对同级任务的处理伪代码如下。

ptcb = OSTCBList;

while (ptcb->OSTCBPrio != OS_IDLE_PRIO) {

//原系统代码不做任何修改 }

//添加对同级任务的处理

ptcb = UserList;

while(ptcb != 0){

OS_ENTER_CRITICAL();

if (ptcb->OSTCBDly != 0) {

if (--ptcb->OSTCBDly == 0) {

if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {

更新B到就绪表; //有同级任务就绪,将B 添加到就绪表                   

UserCount ++ ;//同级任务就绪个数更新

}else{ptcb->OSTCBDly= 1; }

}}

ptcb=ptcb->OSTCBNext;

OS_EXIT_CRITICAL();

}

内核中并没有同级任务的就绪表,当有同级任务就绪时,则使轮询引擎B进入就绪态。B一旦获得执行时间,同级任务则按时间片轮询规则得到运行。

2.2  任务延时

内核中的任务空闲时,需调用延时函数进入等待状态,以释放系统资源。常用的延时函数是OSTimeDly(),其它延时函数也是借用此函数完成[7]。同级任务是借用轮询引擎B得以执行,当前执行的同级任务进入等待态后,不能简单的让B也进入等待,而是进行一次调度,让其它处于就绪态的同级任务有机会继续在B下运行。OSTimeDly()添加对同级任务的处理伪代码如下。

if (ticks>0) {

OS_ENTER_CRITICAL();

if(OSTCBCur->OSTCBPrio == B ){ //如果是轮询引擎B

UserCur->OSTCBDly = ticks; // UserCur指向的同级任务延时

if(--UserCount >0){ //还有其他同级任务

OS_EXIT_CRITICAL();

OS_Sched();//主动调度一次

return;

}}

//后面是原系统代码,不做任何修改

}

当没有同级任务处于就绪态时,B 会调用原来代码,进入等待状态,不会浪费系统资源。

2.3  中断保存和调度规则

为了完成同级任务基于时间片轮询调度,需对系统中有关中断保存和调度规则进行修改。µC/OS-II 2.52源码中,OS_Sched() 与OSCtxSw()与任务调度相关, OSTickISR() 、OSIntCtxSw()、OSIntExit()与中断相关。完成任务切换或中断跳转的函数是OSCtxSw()和OSIntCtxSw()。

OS_Sched()与OSIntExit()作用类似,不同的是后者调用之前断点已经自动保存过。轮询引擎B每次中断需要将断点保存到UserCur指向的同级任务堆栈,下次调度运行不是返回上一个同级任务,而是返回第一次设置的全局变量断点处,开始新一轮的轮询。在每次任务切换与B有关时,必须主动保存同级任务断点,修改OS_Sched()伪代码如下。

if (OSPrioHighRdy != OSPrioCur) {

//原系统代码不加修改}

if(OSPrioHighRdy==B){ //最高就绪任务是B

OS_TASK_SW();  //主动切换任务, OS_TASK_SW() 内部会保存同级任务断点       

}//原系统代码不加修改

对于OSIntExit()则不需修改,因为此函数调用之前断点已经自动保存过。

 

OSTickISR()、OSCtxSw()、OSIntCtxSw()三个函数位于Os_cpu_a.asm中,是用汇编语言编写,需要添加相应的判断和跳转代码。对于判断两个任务的优先级是否一样,需要比较任务控制块中的OSTCBPrio变量。由于此变量在OS_TCB结构的尾部,不便于寻址,可将它改到OS_TCB结构中的第二个位置,即位于OSTCBStkPtr变量之后。(OSTCBStkPtr也要方便寻址,故其在第一[1])。这三个函数,在原有中断保存和任务切换代码上,需要服从以下逻辑规则:

 

当被中断的任务是轮询引擎B时,需将断点保存到UserCur指向的同级任务堆栈中,而不是任务B自身的堆栈,添加汇编伪代码如下:

LES  BX ,DWORD PTR DS:_OSTCBCur ; 取当前运行的优先级任务

CMP  BYTE PTR ES:[BX+4], #B ; 判断当前运行的优先级任务是否为B

JNE    _OLD ; 不是B ,跳到原有代码

MOV   AX, SEG(_UserCur) ; 取UserCur指向的同级任务

MOV   DS,AX ;

LES    BX,DWORD PTR DS:_ UserCur ;

MOV   ES:[BX+2], SS ; 保存断点到UserCur指向的同级任务

MOV   ES:[BX+0], SP ;

JMP    _NEW ; 保存断点后,跳过原有保存断点语句

 

当即将调度运行的优先级任务是B时,需要返回到第一次B运行时设置的全局变量断点处,而不是上一个同级任务,添加汇编伪代码如下:

LES  BX ,DWORD PTR DS:_OSTCBHighRdy ; 取就绪的最高优先级任务

CMP  BYTE PTR ES:[BX+4],#B; 判断就绪的最高优先级任务是否为B

JNE    _OLD; 不是B ,跳到原有代码

CALL FAR PTR  _OSLONGJMP; 是B ,返回第一次设置的全局变量断点处

3  实验

为了检验改造后的µC/OS-II,需要把µC/OS-II移植到合适的处理器上运行。这里选择Intel的x86系列CPU。硬件平台包括 Intel Pentium Dual-Core CPU 2.30GHz ,2GB 内存。操作系统平台采用Windows Xp ,编译器采用 Borland C++ V4.5 ,源码为µC/OS-II 2.52。移植代码针对x86的实模式,使用BC4.5在大模式下编译和链接。

让A的优先级为58,B 的优先级为59 ,如此一来,除去系统保留优先级外,同级任务轮流运行在最低优先级下,其他优先级任务都可以中断B的运行,从而不干扰任何优先级任务的实时性。实验中除A、B外,另创建了5个优先级任务,同时创建了优先级编号从64到73共十个同级任务,任务堆栈TASK_STK_SIZE均设为512,时间片单位为100ms。任务的工作是调用PC_DispChar()在屏幕不同行上打印出不同的字符,优先级任务分别打印0 ~ 4 ,同级任务分别打印A ~ G。经测试,系统工作状态良好,结果如下所示。

图3:实验运行结果(略)

4  结论

本文的出发点在于:从简单实用的角度为µC/OS-II扩展出基于时间片轮询调度的同级任务。创新点在于:在尽量少改动内核架构及不失实时性的前提下,借用系统的两个优先级任务,充当时钟源和同级任务轮询引擎,将µC/OS-II扩展出192个同级任务,且同级任务在最低优先级任务下轮流运行。实验表明,文中方法是可行的。文中的工作仅对内核中任务调度模块进行了扩展,要做一个完善可靠、支持同级任务调度的µC/OS-II系统,还需要对内核其它模块进行修改(如信号量、邮箱、调度算法优化等),这将是下一步要进行的工作。

参考文献

[1] LABROSSE J.嵌入式实时操作系统µC/OS-II[M].(第2版)邵贝贝,译.北京:北京航空航天大学出版社,2003:72-142 , 283-366.

[2]成后发,杨春金.µC/OS-II 操作系统内核的改进[J].通讯和计算机, 2006, 19 (6):52-55.

[3]邹航,李小文.µC/OS-II 内核任务调度算法的改进[J].重庆邮电大学学报:自然科学版,2010, 22 (3):360 – 363.

[4]何海涛.µC/OS-II中优先级抢占的时间片调度算法的实现[J].计算机系统应用.2009,(11):73 -75

[5]任哲.嵌入式实时操作系统µC/OS-II原理及应用[M].北京:北京航空航天大学出版社,2005:15-97.

[6]姚燕南,薛钧义,姚向华等.微型计算机原理与接口技术[M].北京:高等教育出版社,2004:115-122.

[7] µC/OS-II 2.52 [CP].http://www.micrium.com.