下载

2下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 第3章

第3章.doc

第3章

linwhwylb
2018-09-05 0人阅读 举报 0 0 0 暂无简介

简介:本文档为《第3章doc》,可适用于IT/计算机领域

第章内核结构临界段(CriticalSections)任务任务状态任务控制块(TaskControlBlocks,OSTCBs)就绪表(ReadyList)任务调度(TaskScheduling)给调度器上锁和开锁(LockingandUnLockingtheScheduler)空闲任务(IdleTask)统计任务μCOS中的中断处理时钟节拍μCOSⅡ初始化μCOSⅡ的启动获取当前μCOSⅡ的版本号OSEvent()函数第章内核结构本章给出μCOSⅡ的主要结构概貌。读者将学习以下一些内容·μCOSⅡ是怎样处理临界段代码的·什么是任务怎样把用户的任务交给μCOSⅡ·任务是怎样调度的·应用程序CPU的利用率是多少μCOSⅡ是怎样知道的·怎样写中断服务子程序·什么是时钟节拍μCOSⅡ是怎样处理时钟节拍的·μCOSⅡ是怎样初始化的以及·怎样启动多任务本章还描述以下函数,这些服务于应用程序:·OSENTERCRITICAL()和OSEXITCRITICAL(),·OSInit(),·OSStart(),·OSIntEnter()和OSIntExit(),·OSSchedLock()和OSSchedUnlock(),以及·OSVersion()临界段(CriticalSections)和其它内核一样μCOSⅡ为了处理临界段代码需要关中断处理完毕后再开中断。这使得μCOSⅡ能够避免同时有其它任务或中断服务进入临界段代码。关中断的时间是实时内核开发商应提供的最重要的指标之一因为这个指标影响用户系统对实时事件的响应性。μCOSⅡ努力使关中断时间降至最短但就使用μCOSⅡ而言关中断的时间很大程度上取决于微处理器的架构以及编译器所生成的代码质量。微处理器一般都有关中断开中断指令用户使用的C语言编译器必须有某种机制能够在C中直接实现关中断开中断地操作。某些C编译器允许在用户的C源代码中插入汇编语言的语句。这使得插入微处理器指令来关中断开中断很容易实现。而有的编译器把从C语言中关中断开中断放在语言的扩展部分。μCOSⅡ定义两个宏(macros)来关中断和开中断以便避开不同C编译器厂商选择不同的方法来处理关中断和开中断。μCOSⅡ中的这两个宏调用分别是:OSENTERCRITICAL()和OSEXITCRITICAL()。因为这两个宏的定义取决于所用的微处理器故在文件OSCPUH中可以找到相的使用权。中断服务子程序可能会报告一个或多个事件的发生而使一个或多个任务进入就绪态。在这种情况下从中断服务子程序返回之前μCOSⅡ要判定被中断的任务是否还是就绪态任务中优先级最高的。如果中断服务子程序使一个优先级更高的任务进入了就绪态则新进入就绪态的这个优先级更高的任务将得以运行否则原来被中断了的任务才能继续运行。当所有的任务都在等待事件发生或等待延迟时间结束μCOSⅡ执行空闲任务(idletask)执行OSTaskIdle()函数。任务控制块(TaskControlBlocks,OS​​TCBs)一旦任务建立了任务控制块OS​​TCBs将被赋值(程序清单)。任务控制块是一个数据结构当任务的CPU使用权被剥夺时μCOSⅡ用它来保存该任务的状态。当任务重新得到CPU使用权时任务控制块能确保任务从当时被中断的那一点丝毫不差地继续执行。OS​​TCBs全部驻留在RAM中。读者将会注意到笔者在组织这个数据结构时考虑到了各成员的逻辑分组。任务建立的时候OS​​TCBs就被初始化了(见第四章任务管理)。程序清单LµCOSII任务控制块typedefstructostcb{OSSTK*OSTCBStkPtr#ifOSTASKCREATEEXTENvoid*OSTCBExtPtrOSSTK*OSTCBStkBottomINTUOSTCBStkSizeINTUOSTCBOptINTUOSTCBId#endifstructostcb*OSTCBNextstructostcb*OSTCBPrev#if(OSQEN(OSMAXQS>=))||OSMBOXEN||OSSEMENOSEVENT*OSTCBEventPtr#endif#if(OSQEN(OSMAXQS>=))||OSMBOXENvoid*OSTCBMsg#endifINTUOSTCBDlyINTUOSTCBStatINTUOSTCBPrioINTUOSTCBXINTUOSTCBYINTUOSTCBBitXINTUOSTCBBitY#ifOSTASKDELENBOOLEANOSTCBDelReq#endif}OSTCBOSTCBStkPtr是指向当前任务栈顶的指针。μCOSⅡ允许每个任务有自己的栈尤为重要的是每个任务的栈的容量可以是任意的。有些商业内核要求所有任务栈的容量都一样除非用户写一个复杂的接口函数来改变之。这种限制浪费了RAM当各任务需要的栈空间不同时也得按任务中预期栈容量需求最多的来分配栈空间。OSTCBStkPtr是OSTCB数据结构中唯一的一个能用汇编语言来处置的变量(在任务切换段的代码Contextswitchingcode之中)把OSTCBStkPtr放在数据结构的最前面使得从汇编语言中处理这个变量时较为容易。OSTCBExtPtr指向用户定义的任务控制块扩展。用户可以扩展任务控制块而不必修改μCOSⅡ的源代码。OSTCBExtPtr只在函数OstaskCreateExt()中使用故使用时要将OSTASKCREATEN设为以允许建立任务函数的扩展。例如用户可以建立一个数据结构这个数据结构包含每个任务的名字或跟踪某个任务的执行时间或者跟踪切换到某个任务的次数(见例)。注意笔者将这个扩展指针变量放在紧跟着堆栈指针的位置为的是当用户需要在汇编语言中处理这个变量时从数据结构的头上算偏移量比较方便。OSTCBStkBottom是指向任务栈底的指针。如果微处理器的栈指针是递减的即栈存储器从高地址向低地址方向分配则OSTCBStkBottom指向任务使用的栈空间的最低地址。类似地如果微处理器的栈是从低地址向高地址递增型的则OSTCBStkBottom指向任务可以使用的栈空间的最高地址。函数OSTaskStkChk()要用到变量OSTCBStkBottom在运行中检验栈空间的使用情况。用户可以用它来确定任务实际需要的栈空间。这个功能只有当用户在任务建立时允许使用OSTaskCreateExt()函数时才能实现。这就要求用户将OSTASKCREATEEXTEN设为以便允许该功能。OSTCBStkSize存有栈中可容纳的指针元数目而不是用字节(Byte)表示的栈容量总数。也就是说如果栈中可以保存,个入口地址每个地址宽度是位的则实际栈容量是,字节。同样是,个入口地址如果每个地址宽度是位的则总栈容量只有,字节。在函数OSStakChk()中要调用OSTCBStkSize。同理若使用该函数的话要将OSTASKCREATEXTEN设为。OSTCBOpt把“选择项”传给OSTaskCreateExt()只有在用户将OSTASKCREATEEXTEN设为时这个变量才有效。μCOSⅡ目前只支持个选择项(见uCOSIIH):OSTASKOTPSTKCHK,OSTASKOPTSTKCLR和OSTASKOPTSAVEFP。OSTASKOTPSTKCHK用于告知TaskCreateExt()在任务建立的时候任务栈检验功能得到了允许。OSTASKOPTSTKCLR表示任务建立的时候任务栈要清零。只有在用户需要有栈检验功能时才需要将栈清零。如果不定义OSTASKOPTSTKCLR而后又建立、删除了任务栈检验功能报告的栈使用情况将是错误的。如果任务一旦建立就决不会被删除而用户初始化时已将RAM清过零则OSTASKOPTSTKCLR不需要再定义这可以节约程序执行时间。传递了OSTASKOPTSTKCLR将增加TaskCreateExt()函数的执行时间因为要将栈空间清零。栈容量越大清零花的时间越长。最后一个选择项OSTASKOPTSAVEFP通知TaskCreateExt()任务要做浮点运算。如果微处理器有硬件的浮点协处理器则所建立的任务在做任务调度切换时浮点寄存器的内容要保存。OSTCBId用于存储任务的识别码。这个变量现在没有使用留给将来扩展用。OSTCBNext和OSTCBPrev用于任务控制块OSTCBs的双重链接该链表在时钟节拍函数OSTimeTick()中使用用于刷新各个任务的任务延迟变量OSTCBDly每个任务的任务控制块OSTCB在任务建立的时候被链接到链表中在任务删除的时候从链表中被删除。双重连接的链表使得任一成员都能被快速插入或删除。OSTCBEventPtr是指向事件控制块的指针后面的章节中会有所描述(见第章任务间通讯与同步)。OSTCBMsg是指向传给任务的消息的指针。用法将在后面的章节中提到(见第章任务间通讯与同步)。OSTCBDly当需要把任务延时若干时钟节拍时要用到这个变量或者需要把任务挂起一段时间以等待某事件的发生这种等待是有超时限制的。在这种情况下这个变量保存的是任务允许等待事件发生的最多时钟节拍数。如果这个变量为表示任务不延时或者表示等待事件发生的时间没有限制。OSTCBStat是任务的状态字。当OSTCBStat为任务进入就绪态。可以给OSTCBStat赋其它的值在文件uCOSIIH中有关于这个值的描述。OSTCBPrio是任务优先级。高优先级任务的OSTCBPrio值小。也就是说这个值越小任务的优先级越高。OSTCBX,OSTCBY,OSTCBBitX和OSTCBBitY用于加速任务进入就绪态的过程或进入等待事件发生状态的过程(避免在运行中去计算这些值)。这些值是在任务建立时算好的或者是在改变任务优先级时算出的。这些值的算法见程序清单L。程序清单L任务控制块OSTCB中几个成员的算法OSTCBY=priority>>OSTCBBitY=OSMapTblpriority>>OSTCBX=priorityxOSTCBBitX=OSMapTblpriorityxOSTCBDelReq是一个布尔量用于表示该任务是否需要删除用法将在后面的章节中描述(见第章任务管理)应用程序中可以有的最多任务数(OSMAXTASKS)是在文件OSCFGH中定义的。这个最多任务数也是μCOSⅡ分配给用户程序的最多任务控制块OSTCBs的数目。将OSMAXTASKS的数目设置为用户应用程序实际需要的任务数可以减小RAM的需求量。所有的任务控制块OSTCBs都是放在任务控制块列表数组OSTCBTbl中的。请注意μCOSⅡ分配给系统任务OSNSYSTASKS若干个任务控制块见文件μCOSⅡH供其内部使用。目前一个用于空闲任务另一个用于任务统计(如果OSTASKSTATEN是设为的)。在μCOSⅡ初始化的时候如图所示所有任务控制块OSTCBs被链接成单向空任务链表。当任务一旦建立空任务控制块指针OSTCBFreeList指向的任务控制块便赋给了该任务然后OSTCBFreeList的值调整为指向下链表中下一个空的任务控制块。一旦任务被删除任务控制块就还给空任务链表。图空任务列表就绪表(ReadyList)每个任务被赋予不同的优先级等级从级到最低优先级OSLOWESTPRO包括和OSLOWESTPRO在内(见文件OSCFGH)。当μCOSⅡ初始化的时候最低优先级OSLOWESTPRO总是被赋给空闲任务idletask。注意最多任务数目OSMAXTASKS和最低优先级数是没有关系的。用户应用程序可以只有个任务而仍然可以有个优先级的级别(如果用户将最低优先级数设为的话)。每个任务的就绪态标志都放入就绪表中的就绪表中有两个变量OSRedyGrp和OSRdyTbl。在OSRdyGrp中任务按优先级分组个任务为一组。OSRdyGrp中的每一位表示组任务中每一组中是否有进入就绪态的任务。任务进入就绪态时就绪表OSRdyTbl中的相应元素的相应位也置位。就绪表OSRdyTbl数组的大小取决于OSLOWESTPRO(见文件OSCFGH)。当用户的应用程序中任务数目比较少时减少OSLOWESTPRO的值可以降低μCOSⅡ对RAM(数据空间)的需求量。为确定下次该哪个优先级的任务运行了内核调度器总是将OSLOWESTPRO在就绪表中相应字节的相应位置。OSRdyGrp和OSRdyTbl之间的关系见图是按以下规则给出的:当OSRdyTbl中的任何一位是时OSRdyGrp的第位置当OSRdyTbl中的任何一位是时OSRdyGrp的第位置当OSRdyTbl中的任何一位是时OSRdyGrp的第位置当OSRdyTbl中的任何一位是时OSRdyGrp的第位置当OSRdyTbl中的任何一位是时OSRdyGrp的第位置当OSRdyTbl中的任何一位是时OSRdyGrp的第位置当OSRdyTbl中的任何一位是时OSRdyGrp的第位置当OSRdyTbl中的任何一位是时OSRdyGrp的第位置程序清单中的代码用于将任务放入就绪表。Prio是任务的优先级。程序清单L使任务进入就绪态OSRdyGrp|=OSMapTblprio>>OSRdyTblprio>>|=OSMapTblpriox表TOSMapTbl的值IndexBitMask(Binary)读者可以看出任务优先级的低三位用于确定任务在总就绪表OSRdyTbl中的所在位。接下去的三位用于确定是在OSRdyTbl数组的第几个元素。OSMapTbl是在ROM中的(见文件OSCOREC)屏蔽字用于限制OSRdyTbl数组的元素下标在到之间见表图μCOSⅡ就绪表如果一个任务被删除了则用程序清单中的代码做求反处理。程序清单L从就绪表中删除一个任务if((OSRdyTblprio>>=~OSMapTblpriox)==)OSRdyGrp=~OSMapTblprio>>以上代码将就绪任务表数组OSRdyTbl中相应元素的相应位清零而对于OSRdyGrp只有当被删除任务所在任务组中全组任务一个都没有进入就绪态时才将相应位清零。也就是说OSRdyTblprio>>所有的位都是零时OSRdyGrp的相应位才清零。为了找到那个进入就绪态的优先级最高的任务并不需要从OSRdyTbl开始扫描整个就绪任务表只需要查另外一张表即优先级判定表OSUnMapTbl()(见文件OSCOREC)。OSRdyTbl中每个字节的位代表这一组的个任务哪些进入就绪态了低位的优先级高于高位。利用这个字节为下标来查OSUnMapTbl这张表返回的字节就是该组任务中就绪态任务中优先级最高的那个任务所在的位置。这个返回值在到之间。确定进入就绪态的优先级最高的任务是用以下代码完成的如程序清单L所示。程序清单L找出进入就绪态的优先级最高的任务y=OSUnMapTblOSRdyGrpx=OSUnMapTblOSRdyTblyprio=(y<<)x例如如果OSRdyGrp的值为二进制查OSUnMapTblOSRdyGrp得到的值是它相应于OSRdyGrp中的第位bit这里假设最右边的一位是第位bit。类似地如果OSRdyTbl的值是二进制,则OSUnMapTblOSRdyTbc的值是即第位。于是任务的优先级Prio就等于(*)。利用这个优先级的值。查任务控制块优先级表OSTCBPrioTbl得到指向相应任务的任务控制块OSTCB的工作就完成了。任务调度(TaskScheduling)μCOSⅡ总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。任务级的调度是由函数OSSched()完成的。中断级的调度是由另一个函数OSIntExt()完成的这个函数将在以后描述。OSSched()的代码如程序清单L所示。程序清单L任务调度器(theTaskScheduler)voidOSSched(void){INTUyOSENTERCRITICAL()if((OSLockNesting|OSIntNesting)==){()y=OSUnMapTblOSRdyGrp()OSPrioHighRdy=(INTU)((y<<)OSUnMapTblOSRdyTbly)()if(OSPrioHighRdy!=OSPrioCur){()OSTCBHighRdy=OSTCBPrioTblOSPrioHighRdy()OSCtxSwCtr()OSTASKSW()()}}OSEXITCRITICAL()}μCOSⅡ任务调度所花的时间是常数与应用程序中建立的任务数无关。如程序清单中L()条件语句的条件不满足任务调度函数OSSched()将退出不做任务调度。这个条件是:如果在中断服务子程序中调用OSSched()此时中断嵌套层数OSIntNesting>或者由于用户至少调用了一次给任务调度上锁函数OSSchedLock()使OSLockNesting>。如果不是在中断服务子程序调用OSSched()并且任务调度是允许的即没有上锁则任务调度函数将找出那个进入就绪态且优先级最高的任务L()进入就绪态的任务在就绪任务表中有相应的位置位。一旦找到那个优先级最高的任务OSSched()检验这个优先级最高的任务是不是当前正在运行的任务以此来避免不必要的任务调度L()。注意在μCOS中曾经是先得到OSTCBHighRdy然后和OSTCBCur做比较。因为这个比较是两个指针型变量的比较在位和一些位微处理器中这种比较相对较慢。而在μCOSⅡ中是两个整数的比较。并且除非用户实际需要做任务切换在查任务控制块优先级表OSTCBPrioTbl时不需要用指针变量来查OSTCBHighRdy。综合这两项改进即用整数比较代替指针的比较和当需要任务切换时再查表使得μCOSⅡ比μCOS在位和一些位微处理器上要更快一些。为实现任务切换OSTCBHighRdy必须指向优先级最高的那个任务控制块OSTCB这是通过将以OSPrioHighRdy为下标的OSTCBPrioTbl数组中的那个元素赋给OSTCBHighRdy来实现的L()。接着统计计数器OSCtxSwCtr加以跟踪任务切换次数L()。最后宏调用OSTASKSW()来完成实际上的任务切换L()。任务切换很简单由以下两步完成将被挂起任务的微处理器寄存器推入堆栈然后将较高优先级的任务的寄存器值从栈中恢复到寄存器中。在μCOSⅡ中就绪任务的栈结构总是看起来跟刚刚发生过中断一样所有微处理器的寄存器都保存在栈中。换句话说μCOSⅡ运行就绪态的任务所要做的一切只是恢复所有的CPU寄存器并运行中断返回指令。为了做任务切换运行OSTASKSW(),人为模仿了一次中断。多数微处理器有软中断指令或者陷阱指令TRAP来实现上述操作。中断服务子程序或陷阱处理(Traphardler)也称作事故处理(exceptionhandler)必须提供中断向量给汇编语言函数OSCtxSw()。OSCtxSw()除了需要OSTCBHighRdy指向即将被挂起的任务还需要让当前任务控制块OSTCBCur指向即将被挂起的任务参见第章移植μCOSⅡ有关于OSCtxSw()的更详尽的解释。OSSched()的所有代码都属临界段代码。在寻找进入就绪态的优先级最高的任务过程中为防止中断服务子程序把一个或几个任务的就绪位置位中断是被关掉的。为缩短切换时间OSSched()全部代码都可以用汇编语言写。为增加可读性可移植性和将汇编语言代码最少化OSSched()是用C写的。给调度器上锁和开锁(LockingandUnLockingtheScheduler)给调度器上锁函数OSSchedlock()(程序清单L)用于禁止任务调度直到任务完成后调用给调度器开锁函数OSSchedUnlock()为止(程序清单L)。调用OSSchedlock()的任务保持对CPU的控制权尽管有个优先级更高的任务进入了就绪态。然而此时中断是可以被识别的中断服务也能得到(假设中断是开着的)。OSSchedlock()和OSSchedUnlock()必须成对使用。变量OSLockNesting跟踪OSSchedLock()函数被调用的次数以允许嵌套的函数包含临界段代码这段代码其它任务不得干预。μCOSⅡ允许嵌套深度达层。当OSLockNesting等于零时调度重新得到允许。函数OSSchedLock()和OSSchedUnlock()的使用要非常谨慎因为它们影响μCOSⅡ对任务的正常管理。当OSLockNesting减到零的时候OSSchedUnlock()调用OSSchedL()。OSSchedUnlock()是被某任务调用的在调度器上锁的期间可能有什么事件发生了并使一个更高优先级的任务进入就绪态。调用OSSchedLock()以后用户的应用程序不得使用任何能将现行任务挂起的系统调用。也就是说用户程序不得调用OSMboxPend()、OSQPend()、OSSemPend()、OSTaskSuspend(OSPROSELF)、OSTimeDly()或OSTimeDlyHMSM(),直到OSLockNesting回零为止。因为调度器上了锁用户就锁住了系统任何其它任务都不能运行。当低优先级的任务要发消息给多任务的邮箱、消息队列、信号量时(见第章任务间通讯和同步)用户不希望高优先级的任务在邮箱、队列和信号量没有得到消息之前就取得了CPU的控制权此时用户可以使用禁止调度器函数。程序清单L给调度器上锁voidOSSchedLock(void){if(OSRunning==TRUE){OSENTERCRITICAL()OSLockNestingOSEXITCRITICAL()}}程序清单L给调度器开锁voidOSSchedUnlock(void){if(OSRunning==TRUE){OSENTERCRITICAL()if(OSLockNesting>){OSLockNestingif((OSLockNesting|OSIntNesting)==){()OSEXITCRITICAL()OSSched()()}else{OSEXITCRITICAL()}}else{OSEXITCRITICAL()}}}空闲任务(IdleTask)μCOSⅡ总是建立一个空闲任务这个任务在没有其它任务进入就绪态时投入运行。这个空闲任务OSTaskIdle()永远设为最低优先级即OSLOWESTPRI。空闲任务OSTaskIdle()什么也不做只是在不停地给一个位的名叫OSIdleCtr的计数器加统计任务(见节统计任务)使用这个计数器以确定现行应用软件实际消耗的CPU时间。程序清单L是空闲任务的代码。在计数器加前后中断是先关掉再开启的因为位以及大多数位微处理器的位加需要多条指令要防止高优先级的任务或中断服务子程序从中打入。空闲任务不可能被应用软件删除。程序清单LμCOSⅡ的空闲任务voidOSTaskIdle(void*pdata){pdata=pdatafor(){OSENTERCRITICAL()OSIdleCtrOSEXITCRITICAL()}}统计任务μCOSⅡ有一个提供运行时间统计的任务。这个任务叫做OSTaskStat(),如果用户将系统定义常数OSTASKSTATEN(见文件OSCFGH)设为这个任务就会建立。一旦得到了允许OSTaskStat()每秒钟运行一次(见文件OSCOREC)计算当前的CPU利用率。换句话说OSTaskStat()告诉用户应用程序使用了多少CPU时间用百分比表示这个值放在一个有符号位整数OSCPUsage中精读度是个百分点。如果用户应用程序打算使用统计任务用户必须在初始化时建立一个唯一的任务在这个任务中调用OSStatInit()(见文件OSCOREC)。换句话说在调用系统启动函数OSStart()之前用户初始代码必须先建立一个任务在这个任务中调用系统统计初始化函数OSStatInit()然后再建立应用程序中的其它任务。程序清单L是统计任务的示意性代码。程序清单L初始化统计任务voidmain(void){OSInit()*初始化uCOSII()**安装uCOSII的任务切换向量**创建用户起始任务(为了方便讨论这里以TaskStart()作为起始任务)()*OSStart()*开始多任务调度()*}voidTaskStart(void*pdata){*安装并启动uCOSII的时钟节拍()*OSStatInit()*初始化统计任务()**创建用户应用程序任务*for(){*这里是TaskStart()的代码!*}}因为用户的应用程序必须先建立一个起始任务TaskStart()当主程序main()调用系统启动函数OSStcnt()的时候μCOSⅡ只有个要管理的任务:TaskStart()、OSTaskIdle()和OSTaskStat()。请注意任务TaskStart()的名称是无所谓的叫什么名字都可以。因为μCOSⅡ已经将空闲任务的优先级设为最低即OSLOWESTPR统计任务的优先级设为次低OSLOWESTPR。启动任务TaskStart()总是优先级最高的任务。图F解释初始化统计任务时的流程。用户必须首先调用的是μCOSⅡ中的系统初始化函数OSInit()该函数初始化μCOSⅡ图F()。有的处理器(例如Motorola的MCHC),不需要“设置”中断向量中断向量已经在ROM中有了。用户必须调用OSTaskCreat()或者OSTaskCreatExt()以建立TaskStart()图F()。进入多任务的条件准备好了以后调用系统启动函数OSStart()。这个函数将使TaskStart()开始执行因为TaskStart()是优先级最高的任务图F()。图F统计任务的初始化TaskStart()负责初始化和启动时钟节拍图F()。在这里启动时钟节拍是必要的因为用户不会希望在多任务还没有开始时就接收到时钟节拍中断。接下去TaskStart()调用统计初始化函数OSStatInit()图F()。统计初始化函数OSStatInit()决定在没有其它应用任务运行时空闲计数器(OSIdleCtr)的计数有多快。奔腾II微处理器以MHz运行时加操作可以使该计数器的值达到每秒,,次。OSIdleCtr的值离位计数器的溢出极限值,,,还差得远。微处理器越来越快用户要注意这里可能会是将来的一个潜在问题。系统统计初始化任务函数OSStatInit()调用延迟函数OSTimeDly()将自身延时个时钟节拍以停止自身的运行图F()。这是为了使OSStatInit()与时钟节拍同步。μCOSⅡ然后选下一个优先级最高的进入就绪态的任务运行这恰好是统计任务OSTaskStat()。读者会在后面读到OSTaskStat()的代码但粗看一下OSTaskStat()所要做的第一件事就是查看统计任务就绪标志是否为“假”如果是的话也要延时两个时钟节拍图F()。一定会是这样因为标志OSStatRdy已被OSInit()函数初始化为“假”所以实际上DSTaskStat也将自己推入休眠态(Sleep)两个时钟节拍图F()。于是任务切换到空闲任务OSTaskIdle()开始运行这是唯一一个就绪态任务了。CPU处在空闲任务OSTaskIdle中直到TaskStart()的延迟两个时钟节拍完成图()。两个时钟节拍之后TaskStart()恢复运行图F()。在执行OSStartInit()时空闲计数器OSIdleCtr被清零图F()。然后OSStatInit()将自身延时整整一秒图F()。因为没有其它进入就绪态的任务OSTaskIdle()又获得了CPU的控制权图F()。一秒钟以后TaskStart()继续运行还是在OSStatInit()中空闲计数器将秒钟内计数的值存入空闲计数器最大值OSIdleCtrMax中图F()。OSStarInit()将统计任务就绪标志OSStatRdy设为“真”图F()以此来允许两个时钟节拍以后OSTaskStat()开始计算CPU的利用率。统计任务的初始化函数OSStatInit()的代码如程序清单L所示。程序清单L统计任务的初始化voidOSStatInit(void){OSTimeDly()OSENTERCRITICAL()OSIdleCtr=LOSEXITCRITICAL()OSTimeDly(OSTICKSPERSEC)OSENTERCRITICAL()OSIdleCtrMax=OSIdleCtrOSStatRdy=TRUEOSEXITCRITICAL()}统计任务OSStat()的代码程序清单L所示。在前面一段中已经讨论了为什么要等待统计任务就绪标志OSStatRdyL()。这个任务每秒执行一次以确定所有应用程序中的任务消耗了多少CPU时间。当用户的应用程序代码加入以后运行空闲任务的CPU时间就少了OSIdleCtr就不会像原来什么任务都不运行时有那么多计数。要知道OSIdleCtr的最大计数值是OSStatInit()在初始化时保存在计数器最大值OSIdleCtrMax中的。CPU利用率(表达式)是保存在变量OSCPUsageL()中的:表达式Needtotypesettheequation一旦上述计算完成OSTaskStat()调用任务统计外界接入函数OSTaskStatHook()L()这是一个用户可定义的函数这个函数能使统计任务得到扩展。这样用户可以计算并显示所有任务总的执行时间每个任务执行时间的百分比以及其它信息(参见节例)。程序清单L统计任务voidOSTaskStat(void*pdata){INTUrunINTSusagepdata=pdatawhile(OSStatRdy==FALSE){()OSTimeDly(*OSTICKSPERSEC)}for(){OSENTERCRITICAL()OSIdleCtrRun=OSIdleCtrrun=OSIdleCtrOSIdleCtr=LOSEXITCRITICAL()if(OSIdleCtrMax>L){usage=(INTS)(LL*runOSIdleCtrMax)()if(usage>){OSCPUUsage=}elseif(usage<){OSCPUUsage=}else{OSCPUUsage=usage}}else{OSCPUUsage=}OSTaskStatHook()()OSTimeDly(OSTICKSPERSEC)}}μCOS中的中断处理μCOS中中断服务子程序要用汇编语言来写。然而如果用户使用的C语言编译器支持在线汇编语言的话用户可以直接将中断服务子程序代码放在C语言的程序文件中。中断服务子程序的示意码如程序清单L所示。程序清单LμCOSII中的中断服务子程序用户中断服务子程序:保存全部CPU寄存器()调用OSIntEnter或OSIntNesting直接加()执行用户代码做中断服务()调用OSIntExit()()恢复所有CPU寄存器()执行中断返回指令()用户代码应该将全部CPU寄存器推入当前任务栈L()。注意有些微处理器例如Motorola(及以上的微处理器)做中断服务时使用另外的堆栈。μCOSⅡ可以用在这类微处理器中当任务切换时寄存器是保存在被中断了的那个任务的栈中的。μCOSⅡ需要知道用户在做中断服务故用户应该调用OSIntEnter()或者将全程变量OSIntNestingL()直接加如果用户使用的微处理器有存储器直接加的单条指令的话。如果用户使用的微处理器没有这样的指令必须先将OSIntNesting读入寄存器再将寄存器加然后再写回到变量OSIatNesting中去就不如调用OSIatEnter()。OSIntNesting是共享资源。OSIntEnter()把上述三条指令用开中断、关中断保护起来以保证处理OSIntNesting时的排它性。直接给OSIntNesting加比调用OSIntEnter()快得多可能时直接加更好。要当心的是在有些情况下从OSIntEnter()返回时会把中断开了。遇到这种情况在调用OSIntEnter()之前要先清中断源否则中断将连续反复打入用户应用程序就会崩溃!上述两步完成以后用户可以开始服务于叫中断的设备了L()。这一段完全取决于应用。μCOSⅡ允许中断嵌套因为μCOSⅡ跟踪嵌套层数OSIntNesting。然而为允许中断嵌套在多数情况下用户应在开中断之前先清中断源。调用脱离中断函数OSIntExit()L()标志着中断服务子程序的终结OSIntExit()将中断嵌套层数计数器减。当嵌套计数器减到零时所有中断包括嵌套的中断就都完成了此时μCOSⅡ要判定有没有优先级较高的任务被中断服务子程序(或任一嵌套的中断)唤醒了。如果有优先级高的任务进入了就绪态μCOSⅡ就返回到那个高优先级的任务OSIntExit()返回到调用点L()。保存的寄存器的值是在这时恢复的然后是执行中断返回指令L()。注意如果调度被禁止了(OSIntNesting>)μCOSⅡ将被返回到被中断了的任务。以上描述的详细解释如图F所示。中断来到了F()但还不能被被CPU识别也许是因为中断被μCOSⅡ或用户应用程序关了或者是因为CPU还没执行完当前指令。一旦CPU响应了这个中断F()CPU的中断向量(至少大多数微处理器是如此)跳转到中断服务子程序F()。如上所述中断服务子程序保存CPU寄存器(也叫做CPUcontext)F()一旦做完用户中断服务子程序通知μCOSⅡ进入中断服务子程序了办法是调用OSIntEnter()或者给OSIntNesting直接加F()。然后用户中断服务代码开始执行F()。用户中断服务中做的事要尽可能地少要把大部分工作留给任务去做。中断服务子程序通知某任务去做事的手段是调用以下函数之一:OSMboxPost()OSQPost()OSQPostFront()OSSemPost()。中断发生并由上述函数发出消息时接收消息的任务可能是也可能不是挂起在邮箱、队列或信号量上的任务。用户中断服务完成以后要调用OSIntExit()F()。从时序图上可以看出对被中断了的任务说来如果没有高优先级的任务被中断服务子程序激活而进入就绪态OSIntExit()只占用很短的运行时间。进而在这种情况下CPU寄存器只是简单地恢复F()并执行中断返回指令F()。如果中断服务子程序使一个高优先级的任务进入了就绪态则OSIntExit()将占用较长的运行时间因为这时要做任务切换F()。新任务的寄存器内容要恢复并执行中断返回指令F()。图中断服务进入中断函数OSIntEnter()的代码如程序清单L所示从中断服务中退出函数OSIntExit()的代码如程序清单L所示。如前所述OSIntEnter()所做的事是非常少的。程序清单L通知μCOSⅡ中断服务子程序开始了voidOSIntEnter(void){OSENTERCRITICAL()OSIntNestingOSEXITCRITICAL()}程序清单L通知μCOSⅡ脱离了中断服务voidOSIntExit(void){OSENTERCRITICAL()()if((OSIntNesting|OSLockNesting)==){()OSIntExitY=OSUnMapTblOSRdyGrp()OSPrioHighRdy=(INTU)((OSIntExitY<<)OSUnMapTblOSRdyTblOSIntExitY)if(OSPrioHighRdy!=OSPrioCur){OSTCBHighRdy=OSTCBPrioTblOSPrioHighRdyOSCtxSwCtrOSIntCtxSw()()}}OSEXITCRITICAL()}OSIntExit()看起来非常像OSSched()。但有三点不同。第一点OSIntExit()使中断嵌套层数减L()而调度函数OSSched()的调度条件是:中断嵌套层数计数器和锁定嵌套计数器(OSLockNesting)二者都必须是零。第二个不同点是OSRdyTbl所需的检索值Y是保存在全程变量OSIntExitY中的L()。这是为了避免在任务栈中安排局部变量。这个变量在哪儿和中断任务切换函数OSIntCtxSw()有关(见节中断任务切换函数)。最后一点如果需要做任务切换OSIntExit()将调用OSIntCtxSw()L()而不是调用OSTASKSW(),正像在OSSched()函数中那样。调用中断切换函数OSIntCtxSw()而不调用任务切换函数OSTASKSW()有两个原因首先是如程序清单中L()和图F()所示一半的工作即CPU寄存器入栈的工作已经做完了。第二个原因是在中断服务子程序中调用OSIntExit()时将返回地址推入了堆栈L()和F()。OSIntExit()中的进入临界段函数OSENTERCRITICAL()或许将CPU的状态字也推入了堆栈L()和F()。这取决于中断是怎么被关掉的(见第章移植μCOSⅡ)。最后调用OSIntCtxSw()时的返回地址又被推入了堆栈L()和F()除了栈中不相关的部分当任务挂起时栈结构应该与μCOSⅡ所规定的完全一致。OSIntCtxSw()只需要对栈指针做简单的调整如图F()所示。换句话说调整栈结构要保证所有挂起任务的栈结构看起来是一样的。图中断中的任务切换函数OSIntCtxSw()调整栈结构有的微处理器像MotorolaHC中断发生时CPU寄存器是自动入栈的且要想允许中断嵌套的话在中断服务子程序中要重新开中断这可以视作一个优点。确实如果用户中断服务子程序执行得非常快用户不需要通知任务自身进入了中断服务只要不在中断服务期间开中断也不需要调用OSIntEnter()或OSIntNesting加。程序清单L。中的示意代码表示这种情况。一个任务和这个中断服务子程序通讯的唯一方法是通过全程变量。程序清单LMotorolaHC中的中断服务子程序MHCISR:*快中断服务程序必须禁止中断*所有寄存器被CPU自动保存执行用户代码以响应中断执行中断返回指令时钟节拍μCOS需要用户提供周期性信号源用于实现时间延时和确认超时。节拍率应在每秒次到次之间或者说到Hz。时钟节拍率越高系统的额外负荷就越重。时钟节拍的实际频率取决于用户应用程序的精度。时钟节拍源可以是专门的硬件定时器也可以是来自Hz交流电源的信号。用户必须在多任务系统启动以后再开启时钟节拍器也就是在调用OSStart()之后。换句话说在调用OSStart()之后做的第一件事是初始化定时器中断。通常容易犯的错误是将允许时钟节拍器中断放在系统初始化函数OSInit()之后在调启动多任务系统启动函数OSStart()之前如程序清单L所示。程序清单L启动时钟就节拍器的不正确做法voidmain(void){OSInit()*初始化uCOSII**应用程序初始化代码**通过调用OSTaskCreate()创建至少一个任务*允许时钟节拍(TICKER)中断*千万不要在这里允许时钟节拍中断!!!*OSStart()*开始多任务调度*}这里潜在地危险是时钟节拍中断有可能在μCOSⅡ启动第一个任务之前发生此时μCOSⅡ是处在一种不确定的状态之中用户应用程序有可能会崩溃。μCOSⅡ中的时钟节拍服务是通过在中断服务子程序中调用OSTimeTick()实现的。时钟节拍中断服从所有前面章节中描述的规则。时钟节拍中断服务子程序的示意代码如程序清单L所示。这段代码必须用汇编语言编写因为在C语言里不能直接处理CPU的寄存器。程序清单L时钟节拍中断服务子程序的示意代码voidOSTickISR(void){保存处理器寄存器的值调用OSIntEnter()或是将OSIntNesting加调用OSTimeTick()调用OSIntExit()恢复处理器寄存器的值执行中断返回指令}时钟节拍函数OSTimeTick()的代码如程序清单所示。OSTimtick()以调用可由用户定义的时钟节拍外连函数OSTimTickHook()开始这个外连函数可以将时钟节拍函数OSTimtick()予以扩展L()。笔者决定首先调用OSTimTickHook()是打算在时钟节拍中断服务一开始就给用户一个可以做点儿什么的机会因为用户可能会有一些时间要求苛刻的工作要做。OSTimtick()中量大的工作是给每个用户任务控制块OSTCB中的时间延时项OSTCBDly减(如果该项不为零的话)。OSTimTick()从OSTCBList开始沿着OSTCB链表做一直做到空闲任务L()。当某任务的任务控制块中的时间延时项OSTCBDly减到了零这个任务就进入了就绪态L()。而确切被任务挂起的函数OSTaskSuspend()挂起的任务则不会进入就绪态L()。OSTimTick()的执行时间直接与应用程序中建立了多少个任务成正比。程序清单L时钟节拍函数OSTimtick()的一个节拍服务voidOSTimeTick(void){OSTCB*ptcbOSTimeTickHook()()ptcb=OSTCBList()while(ptcb>OSTCBPrio!=OSIDLEPRIO){()OSENTERCRITICAL()if(ptcb>OSTCBDly!=){if(ptcb>OSTCBDly==){if(!(ptcb>OSTCBStatOSSTATSUSPEND)){()OSRdyGrp|=ptcb>OSTCBBitY()OSRdyTblptcb>OSTCBY|=ptcb>OSTCBBitX}else{ptcb>OSTCBDly=}}}ptcb=ptcb>OSTCBNextOSEXITCRITICAL()}OSENTERCRITICAL()()OSTime()OSEXITCRITICAL()}OSTimeTick()还通过调用OSTime()L()累加从开机以来的时间用的是一个无符号位变量。注意在给OSTime加之前使用了关中断因为多数微处理器给位数加的操作都得使用多条指令。中断服务子程序似乎就得写这么长如果用户不喜欢将中断服务程序写这么长可以从任务级调用OSTimeTick()如程序清单L所示。要想这么做得建立一个高于应用程序中所有其它任务优先级的任务。时钟节拍中断服务子程序利用信号量或邮箱发信号给这个高优先级的任务。程序清单L时钟节拍任务TickTask()作时钟节拍服务voidTickTask(void*pdata){pdata=pdatafor(){OSMboxPend()*等待从时钟节拍中断服务程序发来的信号*OSTimeTick()}}用户当然需要先建立一个邮箱(初始化成)用于发信号给上述任何告知时钟节拍中断已经发生了(程序清单L)。程序清单L时钟节拍中断服务函数OSTickISR()做节拍服务。voidOSTickISR(void){保存处理器寄存器的值调用OSIntEnter()或是将OSIntNesting加发送一个‘空’消息(例如(void*))到时钟节拍的邮箱调用OSIntExit()恢复处理器寄存器的值执行中断返回指令}μCOSⅡ初始化在调用μCOSⅡ的任何其它服务之前μCOSⅡ要求用户首先调用系统初始化函数OSIint()。OSIint()初始化μCOSⅡ所有的变量和数据结构(见OSCOREC)。OSInit()建立空闲任务idletask这个任务总是处于就绪态的。空闲任务OSTaskIdle()的优先级总是设成最低即OSLOWESTPRIO。如果统计任务允许OSTASKSTATEN和任务建立扩展允许都设为则OSInit()还得建立统计任务OSTaskStat()并且让其进入就绪态。OSTaskStat的优先级总是设为OSLOWESTPRIO。图F表示调用OSInit()之后一些μCOSⅡ变量和数据结构之间的关系。其解释是基于以下假设的:·在文件OSCFGH中OSTASKSTATEN是设为的。·在文件OSCFGH中OSLOWESTPRIO是设为的。·在文件OSCFGH中,最多任务数OSMAXTASKS是设成大于的。以上两个任务的任务控制块(OSTCBs)是用双向链表链接在一起的。OSTCBList指向这个链表的起始处。当建立一个任务时这个任务总是被放在这个链表的起始处。换句话说OSTCBList总是指向最后建立的那个任务。链的终点指向空字符(也就是零)。因为这两个任务都处在就绪态在就绪任务表OSRdyTbl中的相应位是设为的。还有因为这两个任务的相应位是在OSRdyTbl的同一行上即属同一组故OSRdyGrp中只有位是设为的。μCOSⅡ还初始化了个空数据结构缓冲区如图F所示。每个缓冲区都是单向链表允许μCOSⅡ从缓冲区中迅速得到或释放一个缓冲区中的元素。注意空任务控制块在空缓冲区中的数目取决于最多任务数OSMAXTASKS这个最多任务数是在OSCFGH文件中定义的。μCOSⅡ自动安排总的系统任务数OSNSYSTASKS(见文件μCOSⅡH)。控制块OSTCB的数目也就自动确定了。当然包括足够的任务控制块分配给统计任务和空闲任务。指向空事件表OSEventFreeList和空队列表OSFreeList的指针将在第章任务间通讯与同步中讨论。指向空存储区的指针表OSMemFreeList将在第章存储管理中讨论。μCOSⅡ的启动多任务的启动是用户通过调用OSStart()实现的。然而启动μCOSⅡ之前用户至少要建立一个应用任务如程序清单L所示。程序清单L初始化和启动μCOSⅡvoidmain(void){OSInit()*初始化uCOSII*通过调用OSTaskCreate()或OSTaskCreateExt()创建至少一个任务OSStart()*开始多任务调度!OSStart()永远不会返回*}图调用OSInit()之后的数据结构图空缓冲区OSStart()的代码如程序清单L所示。当调用OSStart()时OSStart()从任务就绪表中找出那个用户建立的优先级最高任务的任务控制块L()。然后OSStart()调用高优先级就绪任务启动函数OSStartHighRdy()L,()(见汇编语言文件OSCPUAASM)这个文件与选择的微处理器有关。实质上函数OSStartHighRdy()是将任务栈中保存的值弹回到CPU寄存器中然后执行一条中断返回指令中断返回指令强制执行该任务代码。见节高优先级就绪任务启动函数OSStartHighRdy()。那一节详细介绍对于x微处理器是怎么做的。注意OSStartHighRdy()将永远不返回到OSStart()。程序清单L启动多任务voidOSStart(void){INTUyINTUxif(OSRunning==FALSE){y=OSUnMapTblOSRdyGrpx=OSUnMapTblOSRdyTblyOSPrioHighRdy=(INTU)((y<<)x)OSPrioCur=OSPrioHighRdyOSTCBHighRdy=OSTCBPrioTblOSPrioHighRdy()OSTCBCur=OSTCBHighRdyOSStartHighRdy()()}}多任务启动以后变量与数据结构中的内容如图F所示。这里笔者假设用户建立的任务优先级为注意OSTaskCtr指出已经建立了个任务。OSRunning已设为“真”指出多任务已经开始OSPrioCur和OSPrioHighRdy存放的是用户应用任务的优先级OSTCBCur和OSTCBHighRdy二者都指向用户任务的任务控制块。获取当前μCOSⅡ的版本号应用程序调用OSVersion()程序清单L可以得到当前μCOSⅡ的版本号。OSVersion()函数返回版本号值乘以。换言之表示版本号。程序清单L得到μCOSⅡ当前版本号INTUOSVersion(void){return(OSVERSION)}为找到μCOSⅡ的最新版本以及如何做版本升级用户可以与出版商联系或者查看μCOSⅡ得正式网站WWWuCOSIICOM图调用OSStart()以后的变量与数据结构OSEvent()函数读者或许注意到有个OSCOREC中的函数没有在本章中提到。这个函数是OSEventWaitListInit()OSEventTaskRdy()OSEventTaskWait()OSEventTO()。这几个函数是放在文件OSCOREC中的而对如何使用这个函数的解释见第章任务间的通讯与同步。PAGE

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

评分:

/29

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利