下载

2下载券

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

上传资料

关闭

关闭

关闭

封号提示

内容

首页 第4章

第4章.doc

第4章

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

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

第章任务管理建立任务OSTaskCreate()建立任务OSTaskCreateExt()任务堆栈堆栈检验OSTaskStkChk()删除任务OSTaskDel()请求删除任务OSTaskDelReq()改变任务的优先级OSTaskChangePrio()挂起任务OSTaskSuspend()恢复任务OSTaskResume()获得有关任务的信息OSTaskQuery()第章任务管理在前面的章节中笔者曾说过任务可以是一个无限的循环也可以是在一次执行完毕后被删除掉。这里要注意的是任务代码并不是被真正的删除了而只是µCOSⅡ不再理会该任务代码所以该任务代码不会再运行。任务看起来与任何C函数一样具有一个返回类型和一个参数只是它从不返回。任务的返回类型必须被定义成void型。在本章中所提到的函数可以在OSTASK文件中找到。如前所述任务必须是以下两种结构之一:voidYourTask(void*pdata){for(){*用户代码*调用µCOSⅡ的服务例程之一:OSMboxPend()OSQPend()OSSemPend()OSTaskDel(OSPRIOSELF)OSTaskSuspend(OSPRIOSELF)OSTimeDly()OSTimeDlyHMSM()*用户代码*}}或voidYourTask(void*pdata){*用户代码*OSTaskDel(OSPRIOSELF)}本章所讲的内容包括如何在用户的应用程序中建立任务、删除任务、改变任务的优先级、挂起和恢复任务以及获得有关任务的信息。µCOSⅡ可以管理多达个任务并从中保留了四个最高优先级和四个最低优先级的任务供自己使用所以用户可以使用的只有个任务。任务的优先级越高反映优先级的值则越低。在最新的µCOSⅡ版本中任务的优先级数也可作为任务的标识符使用。建立任务OSTaskCreate()想让µCOSⅡ管理用户的任务用户必须要先建立任务。用户可以通过传递任务地址和其它参数到以下两个函数之一来建立任务:OSTaskCreate()或OSTaskCreateExt()。OSTaskCreate()与µCOS是向下兼容的OSTaskCreateExt()是OSTaskCreate()的扩展版本提供了一些附加的功能。用两个函数中的任何一个都可以建立任务。任务可以在多任务调度开始前建立也可以在其它任务的执行过程中被建立。OSTCB的双向链表中时L()它就禁止中断L()。该双向链表开始于OSTCBList而一个新任务的OSTCB常常被插入到链表的表头。最后该任务处于就绪状态L()并且OSTCBInit()向它的调用者OSTaskCreate()返回一个代码表明OSTCB已经被分配和初始化了L()。现在我可以继续讨论OSTaskCreate()(程序清单L)函数了。从OSTCBInit()返回后OSTaskCreate()要检验返回代码L()如果成功就增加OSTaskCtrL()OSTaskCtr用于保存产生的任务数目。如果OSTCBInit()返回失败就置OSTCBPrioTblprio的入口为L()以放弃该任务的优先级。然后OSTaskCreate()调用OSTaskCreateHook()L()OSTaskCreateHook()是用户自己定义的函数用来扩展OSTaskCreate()的功能。例如用户可以通过OSTaskCreateHook()函数来初始化和存储浮点寄存器、MMU寄存器的内容或者其它与任务相关的内容。一般情况下用户可以在内存中存储一些针对用户的应用程序的附加信息。OSTaskCreateHook()既可以在OSCPUCC中定义(如果OSCPUHOOKSEN置)也可以在其它地方定义。注意OSTaskCreate()在调用OSTaskCreateHook()时中断是关掉的所以用户应该使OSTaskCreateHook()函数中的代码尽量简化因为这将直接影响中断的响应时间。OSTaskCreateHook()在被调用时会收到指向任务被建立时的OSTCB的指针。这意味着该函数可以访问OSTCB数据结构中的所有成员。如果OSTaskCreate()函数是在某个任务的执行过程中被调用(即OSRunning置为TrueL())则任务调度函数会被调用L()来判断是否新建立的任务比原来的任务有更高的优先级。如果新任务的优先级更高内核会进行一次从旧任务到新任务的任务切换。如果在多任务调度开始之前(即用户还没有调用OSStart())新任务就已经建立了,则任务调度函数不会被调用。建立任务OSTaskCreateExt()用OSTaskCreateExt()函数来建立任务会更加灵活但会增加一些额外的开销。OSTaskCreateExt()函数的代码如程序清单L所示。我们可以看到OSTaskCreateExt()需要九个参数!前四个参数(task,pdata,ptos和prio)与OSTaskCreate()的四个参数完全相同连先后顺序都一样。这样做的目的是为了使用户能够更容易地将用户的程序从OSTaskCreate()移植到OSTaskCreateExt()上去。id参数为要建立的任务创建一个特殊的标识符。该参数在µCOS以后的升级版本中可能会用到但在µCOSⅡ中还未使用。这个标识符可以扩展µCOSⅡ功能使它可以执行的任务数超过目前的个。但在这里用户只要简单地将任务的id设置成与任务的优先级一样的值就可以了。pbos是指向任务的堆栈栈底的指针用于堆栈的检验。stk​​size用于指定堆栈成员数目的容量。也就是说如果堆栈的入口宽度为字节宽那么stk​​size为是指堆栈有个字节。该参数与pbos一样也用于堆栈的检验。pext是指向用户附加的数据域的指针用来扩展任务的OSTCB。例如用户可以为每个任务增加一个名字(参看实例)或是在任务切换过程中将浮点寄存器的内容储存到这个附加数据域中等等。opt用于设定OSTaskCreateExt()的选项指定是否允许堆栈检验是否将堆栈清零任务是否要进行浮点操作等等。µCOSⅡH文件中有一个所有可能选项(OSTASKOPTSTKCHK,OSTASKOPTSTKCLR和OSTASKOPTSAVEFP)的常数表。每个选项占有opt的一位并通过该位的置位来选定(用户在使用时只需要将以上OSTASKOPT选项常数进行位或(OR)操作就可以了)。程序清单LOSTaskCreateExt()INTUOSTaskCreateExt(void(*task)(void*pd),void*pdata,OSSTK*ptos,INTUprio,INTUid,OSSTK*pbos,INTUstksize,void*pext,INTUopt){void*pspINTUerrINTUiOSSTK*pfillif(prio>OSLOWESTPRIO){()return(OSPRIOINVALID)}OSENTERCRITICAL()if(OSTCBPrioTblprio==(OSTCB*)){()OSTCBPrioTblprio=(OSTCB*)()OSEXITCRITICAL()()if(optOSTASKOPTSTKCHK){()if(optOSTASKOPTSTKCLR){Pfill=pbosfor(i=i<stksizei){#ifOSSTKGROWTH==*pfill=(OSSTK)#else*pfill=(OSSTK)#endif}}}psp=(void*)OSTaskStkInit(task,pdata,ptos,opt)()err=OSTCBInit(prio,psp,pbos,id,stksize,pext,opt)()if(err==OSNOERR){()OSENTERCRITICALOSTaskCtr()OSTaskCreateHook(OSTCBPrioTblprio)()OSEXITCRITICAL()if(OSRunning){()OSSched()()}}else{OSENTERCRITICAL()OSTCBPrioTblprio=(OSTCB*)()OSEXITCRITICAL()}return(err)}else{OSEXITCRITICAL()return(OSPRIOEXIST)}}OSTaskCreateExt()一开始先检测分配给任务的优先级是否有效L()。任务的优先级必须在到OSLOWESTPRIO之间。接着OSTaskCreateExt()要确保在规定的优先级上还没有建立任务L()。在使用µCOSⅡ时每个任务都有特定的优先级。如果某个优先级是空闲的µCOSⅡ通过放置一个非空指针在OSTCBPrioTbl中来保留该优先级L()。这就使得OSTaskCreateExt()在设置任务数据结构的其他部分时能重新允许中断L()。为了对任务的堆栈进行检验参看,堆栈检验OSTaskStkChk()用户必须在opt参数中设置OSTASKOPTSTKCHK标志。堆栈检验还要求在任务建立时堆栈的存储内容都是(即堆栈已被清零)。为了在任务建立的时候将堆栈清零需要在opt参数中设置OSTASKOPTSTKCLR。当以上两个标志都被设置好后OSTaskCreateExt()才能将堆栈清零L()。接着OSTaskCreateExt()调用OSTaskStkInit()L(),它负责建立任务的堆栈。该函数是与处理器的硬件体系相关的函数可以在OSCPUCC文件中找到。有关实现OSTaskStkInit()的细节可参看第八章移植µCOSⅡ。如果已经有人在你用的处理器上成功地移植了µCOSⅡ而你又得到了他的代码就不必考虑该函数的实现细节了。OSTaskStkInit()函数返回新的堆栈栈顶(psp),并被保存在任务的STCB中。µCOSⅡ支持的处理器的堆栈既可以从上(高地址)往下(低地址)递减也可以从下往上递增(参看,任务堆栈)。用户在调用OSTaskCreateExt()的时候必须知道堆栈是递增的还是递减的(参看用户所用处理器的OSCPUH中的OSSTACKGROWTH)因为用户必须得把堆栈的栈顶传递给OSTaskCreateExt()而栈顶可能是堆栈的最低地址(当OSSTKGROWTH为时)也可能是最高地址(当OSSTKGROWTH为时)。一旦OSTaskStkInit()函数完成了建立堆栈的任务OSTaskCreateExt()就调用OSTCBInit()L()从空闲的OSTCB缓冲池中获得并初始化一个OSTCB。OSTCBInit()的代码在OSTaskCreate()中曾描述过(参看节),从OSTCBInit()返回后OSTaskCreateExt()要检验返回代码L()如果成功就增加OSTaskCtrL()OSTaskCtr用于保存产生的任务数目。如果OSTCBInit()返回失败就置OSTCBPrioTblprio的入口为L()以放弃对该任务优先级的占用。然后OSTaskCreateExt()调用OSTaskCreateHook()L()OSTaskCreateHook()是用户自己定义的函数用来扩展OSTaskCreateExt()的功能。OSTaskCreateHook()可以在OSCPUCC中定义(如果OSCPUHOOKSEN置)也可以在其它地方定义(如果OSCPUHOOKSEN置)。注意OSTaskCreateExt()在调用OSTaskCreateHook()时中断是关掉的所以用户应该使OSTaskCreateHook()函数中的代码尽量简化因为这将直接影响中断的响应时间。OSTaskCreateHook()被调用时会收到指向任务被建立时的OSTCB的指针。这意味着该函数可以访问OSTCB数据结构中的所有成员。如果OSTaskCreateExt()函数是在某个任务的执行过程中被调用的(即OSRunning置为TrueL())以任务调度函数会被调用L()来判断是否新建立的任务比原来的任务有更高的优先级。如果新任务的优先级更高内核会进行一次从旧任务到新任务的任务切换。如果在多任务调度开始之前(即用户还没有调用OSStart())新任务就已经建立了,则任务调度函数不会被调用。任务堆栈每个任务都有自己的堆栈空间。堆栈必须声明为OSSTK类型并且由连续的内存空间组成。用户可以静态分配堆栈空间(在编译的时候分配)也可以动态地分配堆栈空间(在运行的时候分配)。静态堆栈声明如程序清单L和所示这两种声明应放置在函数的外面。程序清单L静态堆栈staticOSSTKMyTaskStackstacksize或程序清单L静态堆栈OSSTKMyTaskStackstacksize用户可以用C编译器提供的malloc()函数来动态地分配堆栈空间如程序清单L所示。在动态分配中用户要时刻注意内存碎片问题。特别是当用户反复地建立和删除任务时内存堆中可能会出现大量的内存碎片导致没有足够大的一块连续内存区域可用作任务堆栈这时malloc()便无法成功地为任务分配堆栈空间。程序清单LL用malloc()为任务分配堆栈空间OSSTK*pstkpstk=(OSSTK*)malloc(stacksize)if(pstk!=(OSSTK*)){*确认malloc()能得到足够地内存空间*Createthetask}图表示了一块能被malloc()动态分配的K字节的内存堆F()。为了讨论问题方便假定用户要建立三个任务(任务A,B和C)每个任务需要K字节的空间。设第一个K字节给任务A,第二个K字节给任务B,第三个K字节给任务CF()。然后用户的应用程序删除任务A和任务C用free()函数释放内存到内存堆中F()。现在用户的内存堆虽有K字节的自由内存空间但它是不连续的所以用户不能建立另一个需要K字节内存的任务(即任务D)。如果用户并不会去删除任务使用malloc()是非常可行的。图F内存碎片µCOSⅡ支持的处理器的堆栈既可以从上(高地址)往下(低地址)长也可以从下往上长(参看,任务堆栈)。用户在调用OSTaskCreate()或OSTaskCreateExt()的时候必须知道堆栈是怎样长的因为用户必须得把堆栈的栈顶传递给以上两个函数当OSCPUH文件中的OSSTKGROWTH置为时用户需要将堆栈的最低内存地址传递给任务创建函数如程序清单所示。程序清单L堆栈从下往上递增OSSTKTaskStackTASKSTACKSIZEOSTaskCreate(task,pdata,TaskStack,prio)当OSCPUH文件中的OSSTKGROWTH置为时用户需要将堆栈的最高内存地址传递给任务创建函数如程序清单所示。程序清单L堆栈从上往下递减OSSTKTaskStackTASKSTACKSIZEOSTaskCreate(task,pdata,TaskStackTASKSTACKSIZE,prio)这个问题会影响代码的可移植性。如果用户想将代码从支持往下递减堆栈的处理器中移植到支持往上递增堆栈的处理器中的话用户得使代码同时适应以上两种情况。在这种特殊情况下程序清单L和可重新写成如程序清单L所示的形式。程序清单L对两个方向增长的堆栈都提供支持OSSTKTaskStackTASKSTACKSIZE#ifOSSTKGROWTH==OSTaskCreate(task,pdata,TaskStack,prio)#elseOSTaskCreate(task,pdata,TaskStackTASKSTACKSIZE,prio)#endif任务所需的堆栈的容量是由应用程序指定的。用户在指定堆栈大小的时候必须考虑用户的任务所调用的所有函数的嵌套情况任务所调用的所有函数会分配的局部变量的数目以及所有可能的中断服务例程嵌套的堆栈需求。另外用户的堆栈必须能储存所有的CPU寄存器。堆栈检验OSTaskStkChk()有时候决定任务实际所需的堆栈空间大小是很有必要的。因为这样用户就可以避免为任务分配过多的堆栈空间从而减少自己的应用程序代码所需的RAM(内存)数量。µCOSⅡ提供的OSTaskStkChk()函数可以为用户提供这种有价值的信息。在图中笔者假定堆栈是从上往下递减的(即OSSTKGROWTH被置为)但以下的讨论也同样适用于从下往上长的堆栈F()。µCOSⅡ是通过查看堆栈本身的内容来决定堆栈的方向的。只有内核或是任务发出堆栈检验的命令时堆栈检验才会被执行它不会自动地去不断检验任务的堆栈使用情况。在堆栈检验时µCOSⅡ要求在任务建立的时候堆栈中存储的必须是值(即堆栈被清零)F()。另外µCOSⅡ还需要知道堆栈栈底(BOS)的位置和分配给任务的堆栈的大小F()。在任务建立的时候BOS的位置及堆栈的这两个值储存在任务的OSTCB中。为了使用µCOSⅡ的堆栈检验功能用户必须要做以下几件事情:·在OSCFGH文件中设OSTASKCREATEEXT为。·用OSTaskCreateExt()建立任务并给予任务比实际需要更多的内存空间。·在OSTaskCreateExt()中将参数opt设置为OSTASKOPTSTKCHKOSTASKOPTSTKCLR。注意如果用户的程序启动代码清除了所有的RAM并且从未删除过已建立了的任务那么用户就不必设置选项OSTASKOPTSTKCLR了。这样就会减少OSTaskCreateExt()的执行时间。·将用户想检验的任务的优先级作为OSTaskStkChk()的参数并调用之。图堆栈检验OSTaskStkChk()顺着堆栈的栈底开始计算空闲的堆栈空间大小具体实现方法是统计储存值为的连续堆栈入口的数目直到发现储存值不为的堆栈入口F()。注意堆栈入口的储存值在进行检验时使用的是堆栈的数据类型(参看OSCPUH中的OSSTK)。换句话说如果堆栈的入口有位宽对值的比较也是按位完成的。所用的堆栈的空间大小是指从用户在OSTaskCreateExt()中定义的堆栈大小中减去了储存值为的连续堆栈入口以后的大小。OSTaskStkChk()实际上把空闲堆栈的字节数和已用堆栈的字节数放置在SSTKDATA数据结构中(参看µCOSⅡH)。注意在某个给定的时间被检验的任务的堆栈指针可能会指向最初的堆栈栈顶(TOS)与堆栈最深处之间的任何位置F()。每次在调用OSTaskStkChk()的时候用户也可能会因为任务还没触及堆栈的最深处而得到不同的堆栈的空闲空间数。用户应该使自己的应用程序运行足够长的时间并且经历最坏的堆栈使用情况这样才能得到正确的数。一旦OSTaskStkChk()提供给用户最坏情况下堆栈的需求用户就可以重新设置堆栈的最后容量了。为了适应系统以后的升级和扩展用户应该多分配%-%的堆栈空间。在堆栈检验中用户所得到的只是一个大致的堆栈使用情况并不能说明堆栈使用的全部实际情况。OSTaskStkChk()函数的代码如程序清单L所示。SSTKDATA(参看µCOSⅡH)数据结构用来保存有关任务堆栈的信息。笔者打算用一个数据结构来达到两个目的。第一把OSTaskStkChk()当作是查询类型的函数并且使所有的查询函数用同样的方法返回即返回查询数据到某个数据结构中。第二在数据结构中传递数据使得笔者可以在不改变OSTaskStkChk()的API(应用程序编程接口)的条件下为该数据结构增加其它域从而扩展OSTaskStkChk()的功能。现在SSTKDATA只包含两个域:OSFree和OSUsed。从代码中用户可看到通过指定执行堆栈检验的任务的优先级可以调用OSTaskStkChk()。如果用户指定SPRIOSELFL()那么就表明用户想知道当前任务的堆栈信息。当然前提是任务已经存在L()。要执行堆栈检验用户必须已用OSTaskCreateExt()建立了任务并且已经传递了选项OSTASKOPTCHKL()。如果所有的条件都满足了OSTaskStkChk()就会象前面描述的那样从堆栈栈底开始统计堆栈的空闲空间L()。最后储存在SSTKDATA中的信息就被确定下来了L()。注意函数所确定的是堆栈的实际空闲字节数和已被占用的字节数而不是堆栈的总字节数。当然堆栈的实际大小(用字节表示)就是该两项之和。程序清单L堆栈检验函数INTUOSTaskStkChk(INTUprio,OSSTKDATA*pdata){OSTCB*ptcbOSSTK*pchkINTUfreeINTUsizepdata>OSFree=pdata>OSUsed=if(prio>OSLOWESTPRIOprio!=OSPRIOSELF){return(OSPRIOINVALID)}OSENTERCRITICAL()if(prio==OSPRIOSELF){()prio=OSTCBCur>OSTCBPrio}ptcb=OSTCBPrioTblprioif(ptcb==(OSTCB*)){()OSEXITCRITICAL()return(OSTASKNOTEXIST)}if((ptcb>OSTCBOptOSTASKOPTSTKCHK)==){()OSEXITCRITICAL()return(OSTASKOPTERR)}free=()size=ptcb>OSTCBStkSizepchk=ptcb>OSTCBStkBottomOSEXITCRITICAL()#ifOSSTKGROWTH==while(*pchk==){free}#elsewhile(*pchk==){free}#endifpdata>OSFree=free*sizeof(OSSTK)()pdata>OSUsed=(sizefree)*sizeof(OSSTK)return(OSNOERR)}删除任务OSTaskDel()有时候删除任务是很有必要的。删除任务,是说任务将返回并处于休眠状态(参看,任务状态)并不是说任务的代码被删除了只是任务的代码不再被µCOSⅡ调用。通过调用OSTaskDel()就可以完成删除任务的功能(如程序清单L所示)。OSTaskDel()一开始应确保用户所要删除的任务并非是空闲任务因为删除空闲任务是不允许的L()。不过用户可以删除statistic任务L()。接着OSTaskDel()还应确保用户不是在ISR例程中去试图删除一个任务因为这也是不被允许的L()。调用此函数的任务可以通过指定OSPRIOSELF参数来删除自己L()。接下来OSTaskDel()会保证被删除的任务是确实存在的L()。如果指定的参数是OSPRIOSELF的话这一判断过程(任务是否存在)自然是可以通过的但笔者不准备为这种情况单独写一段代码因为这样只会增加代码并延长程序的执行时间。程序清单L删除任务INTUOSTaskDel(INTUprio){OSTCB*ptcbOSEVENT*peventif(prio==OSIDLEPRIO){()return(OSTASKDELIDLE)}if(prio>=OSLOWESTPRIOprio!=OSPRIOSELF){()return(OSPRIOINVALID)}OSENTERCRITICAL()if(OSIntNesting>){()OSEXITCRITICAL()return(OSTASKDELISR)}if(prio==OSPRIOSELF){()Prio=OSTCBCur>OSTCBPrio}if((ptcb=OSTCBPrioTblprio)!=(OSTCB*)){()if((OSRdyTblptcb>OSTCBY=~ptcb>OSTCBBitX)==){()OSRdyGrp=~ptcb>OSTCBBitY}if((pevent=ptcb>OSTCBEventPtr)!=(OSEVENT*)){()if((pevent>OSEventTblptcb>OSTCBY=~ptcb>OSTCBBitX)==){pevent>OSEventGrp=~ptcb>OSTCBBitY}}Ptcb>OSTCBDly=()Ptcb>OSTCBStat=OSSTATRDY()OSLockNesting()OSEXITCRITICAL()()OSDummy()()OSENTERCRITICAL()OSLockNesting()OSTaskDelHook(ptcb)()OSTaskCtrOSTCBPrioTblprio=(OSTCB*)()if(ptcb>OSTCBPrev==(OSTCB*)){()ptcb>OSTCBNext>OSTCBPrev=(OSTCB*)OSTCBList=ptcb>OSTCBNext}else{ptcb>OSTCBPrev>OSTCBNext=ptcb>OSTCBNextptcb>OSTCBNext>OSTCBPrev=ptcb>OSTCBPrev}ptcb>OSTCBNext=OSTCBFreeList()OSTCBFreeList=ptcbOSEXITCRITICAL()OSSched()()return(OSNOERR)}else{OSEXITCRITICAL()return(OSTASKDELERR)}}一旦所有条件都满足了OSTCB就会从所有可能的µCOSⅡ的数据结构中移除。OSTaskDel()分两步完成该移除任务以减少中断响应时间。首先如果任务处于就绪表中它会直接被移除L()。如果任务处于邮箱、消息队列或信号量的等待表中它就从自己所处的表中被移除L()。接着OSTaskDel()将任务的时钟延迟数清零以确保自己重新允许中断的时候ISR例程不会使该任务就绪L()。最后OSTaskDel()置任务的OSTCBStat标志为OSSTATRDY。注意OSTaskDel()并不是试图使任务处于就绪状态而是阻止其它任务或ISR例程让该任务重新开始执行(即避免其它任务或ISR调用OSTaskResume()L())。这种情况是有可能发生的因为OSTaskDel()会重新打开中断而ISR可以让更高优先级的任务处于就绪状态这就可能会使用户想删除的任务重新开始执行。如果不想置任务的OSTCBStat标志为OSSTATRDY就只能清除OSSTATSUSPEND位了(这样代码可能显得更清楚更容易理解一些)但这样会使得处理时间稍长一些。要被删除的任务不会被其它的任务或ISR置于就绪状态因为该任务已从就绪任务表中删除了它不是在等待事件的发生也不是在等待延时期满不能重新被执行。为了达到删除任务的目的任务被置于休眠状态。正因为这样OSTaskDel()必须得阻止任务调度程序L()在删除过程中切换到其它的任务中去因为如果当前的任务正在被删除它不可能被再次调度!接下来OSTaskDel()重新允许中断以减少中断的响应时间L()。这样OSTaskDel()就能处理中断服务了但由于它增加了OSLockNestingISR执行完后会返回到被中断任务从而继续任务的删除工作。注意OSTaskDel()此时还没有完全完成删除任务的工作因为它还需要从TCB链中解开OSTCB并将OSTCB返回到空闲OSTCB表中。另外需要注意的是笔者在调用OSEXITCRITICAL()函数后马上调用了OSDummy()L()该函数并不会进行任何实质性的工作。这样做只是因为想确保处理器在中断允许的情况下至少执行一个指令。对于许多处理器来说执行中断允许指令会强制CPU禁止中断直到下个指令结束!Intelx和ZilogZ处理器就是如此工作的。开中断后马上关中断就等于从来没开过中断当然这会增加中断的响应时间。因此调用OSDummy()确保在再次禁止中断之前至少执行了一个调用指令和一个返回指令。当然用户可以用宏定义将OSDummy()定义为一个空操作指令(译者注:例如MCHC指令中的NOP指令)这样调用OSDummy()就等于执行了一个空操作指令会使OSTaskDel()的执行时间稍微缩短一点。但笔者认为这种宏定义是没价值的因为它会增加移植µCOSⅡ的工作量。现在OSTaskDel()可以继续执行删除任务的操作了。在OSTaskDel()重新关中断后它通过锁定嵌套计数器(OSLockNesting)减一以重新允许任务调度L()。接着OSTaskDel()调用用户自定义的OSTaskDelHook()函数L()用户可以在这里删除或释放自定义的TCB附加数据域。然后OSTaskDel()减少µCOSⅡ的任务计数器。OSTaskDel()简单地将指向被删除的任务的OSTCB的指针指向L()从而达到将OSTCB从优先级表中移除的目的。再接着OSTaskDel()将被删除的任务的OSTCB从OSTCB双向链表中移除L()。注意,没有必要检验ptcb>OSTCBNext==的情况因为OSTaskDel()不能删除空闲任务而空闲任务就处于链表的末端(ptcb>OSTCBNext==)。接下来OSTCB返回到空闲OSTCB表中并允许其它任务的建立L()。最后调用任务调度程序来查看在OSTaskDel()重新允许中断的时候L()中断服务子程序是否曾使更高优先级的任务处于就绪状态L()。请求删除任务OSTaskDelReq()有时候如果任务A拥有内存缓冲区或信号量之类的资源而任务B想删除该任务这些资源就可能由于没被释放而丢失。在这种情况下用户可以想法子让拥有这些资源的任务在使用完资源后先释放资源再删除自己。用户可以通过OSTaskDelReq()函数来完成该功能。发出删除任务请求的任务(任务B)和要删除的任务(任务A)都需要调用OSTaskDelReq()函数。任务B的代码如程序清单L所示。任务B需要决定在怎样的情况下请求删除任务L()。换句话说用户的应用程序需要决定在什么样的情况下删除任务。如果任务需要被删除可以通过传递被删除任务的优先级来调用OSTaskDelReq()L()。如果要被删除的任务不存在(即任务已被删除或是还没被建立)OSTaskDelReq()返回OSTASKNOTEXIST。如果OSTaskDelReq()的返回值为OSNOERR则表明请求已被接受但任务还没被删除。用户可能希望任务B等到任务A删除了自己以后才继续进行下面的工作这时用户可以象笔者一样通过让任务B延时一定时间来达到这个目的L()。笔者延时了一个时钟节拍。如果需要用户可以延时得更长一些。当任务A完全删除自己后L()中的返回值成为STASKNOTEXIST此时循环结束L()。程序清单L请求删除其它任务的任务(任务B)voidRequestorTask(void*pdata){INTUerrpdata=pdatafor(){*应用程序代码*if('TaskToBeDeleted()'需要被删除){()while(OSTaskDelReq(TASKTODELPRIO)!=OSTASKNOTEXIST){()OSTimeDly()()}}*应用程序代码*()}}程序清单L需要删除自己的任务(任务A)voidTaskToBeDeleted(void*pdata){INTUerrpdata=pdatafor(){*应用程序代码*If(OSTaskDelReq(OSPRIOSELF)==OSTASKDELREQ){()释放所有占用的资源()释放所有动态内存OSTaskDel(OSPRIOSELF)()}else{*应用程序代码*}}}需要删除自己的任务(任务A)的代码如程序清单L所示。在OSTAB中存有一个标志任务通过查询这个标志的值来确认自己是否需要被删除。这个标志的值是通过调用OSTaskDelReq(OSPRIOSELF)而得到的。当OSTaskDelReq()返回给调用者OSTASKDELREQL()时则表明已经有另外的任务请求该任务被删除了。在这种情况下被删除的任务会释放它所拥有的所用资源L()并且调用OSTaskDel(OSPRIOSELF)来删除自己L()。前面曾提到过任务的代码没有被真正的删除而只是µCOSⅡ不再理会该任务代码换句话说就是任务的代码不会再运行了。但是用户可以通过调用OSTaskCreate()或OSTaskCreateExt()函数重新建立该任务。OSTaskDelReq()的代码如程序清单L所示。通常OSTaskDelReq()需要检查临界条件。首先如果正在删除的任务是空闲任务OSTaskDelReq()会报错并返回L()。接着它要保证调用者请求删除的任务的优先级是有效的L()。如果调用者就是被删除任务本身存储在OSTCB中的标志将会作为返回值L()。如果用户用优先级而不是OSPRIOSELF指定任务并且任务是存在的L()OSTaskDelReq()就会设置任务的内部标志L()。如果任务不存在OSTaskDelReq()则会返回OSTASKNOTEXIST表明任务可能已经删除自己了L()。程序清单LOSTaskDelReq()INTUOSTaskDelReq(INTUprio){BOOLEANstatINTUerrOSTCB*ptcbif(prio==OSIDLEPRIO){()return(OSTASKDELIDLE)}if(prio>=OSLOWESTPRIOprio!=OSPRIOSELF){()return(OSPRIOINVALID)}if(prio==OSPRIOSELF){()OSENTERCRITICAL()stat=OSTCBCur>OSTCBDelReqOSEXITCRITICAL()return(stat)}else{OSENTERCRITICAL()if((ptcb=OSTCBPrioTblprio)!=(OSTCB*)){()ptcb>OSTCBDelReq=OSTASKDELREQ()err=OSNOERR}else{err=OSTASKNOTEXIST()}OSEXITCRITICAL()return(err)}}改变任务的优先级OSTaskChangePrio()在用户建立任务的时候会分配给任务一个优先级。在程序运行期间用户可以通过调用OSTaskChangePrio()来改变任务的优先级。换句话说就是µCOSⅡ允许用户动态的改变任务的优先级。OSTaskChangePrio()的代码如程序清单L所示。用户不能改变空闲任务的优先级L()但用户可以改变调用本函数的任务或者其它任务的优先级。为了改变调用本函数的任务的优先级用户可以指定该任务当前的优先级或OSPRIOSELFOSTaskChangePrio()会决定该任务的优先级。用户还必须指定任务的新(即想要的)优先级。因为µCOSⅡ不允许多个任务具有相同的优先级所以OSTaskChangePrio()需要检验新优先级是否是合法的(即不存在具有新优先级的任务)L()。如果新优先级是合法的µCOSⅡ通过将某些东西储存到OSTCBPrioTblnewprio中保留这个优先级L()。如此就使得OSTaskChangePrio()可以重新允许中断因为此时其它任务已经不可能建立拥有该优先级的任务也不能通过指定相同的新优先级来调用OSTaskChangePrio()。接下来OSTaskChangePrio()可以预先计算新优先级任务的OSTCB中的某些值L()。而这些值用来将任务放入就绪表或从该表中移除(参看,就绪表)。接着OSTaskChangePrio()检验目前的任务是否想改变它的优先级L()。然后OSTaskChangePrio()检查想要改变优先级的任务是否存在L()。很明显如果要改变优先级的任务就是当前任务这个测试就会成功。但是如果OSTaskChangePrio()想要改变优先级的任务不存在它必须将保留的新优先级放回到优先级表OSTCBPrioTbl中L()并返回给调用者一个错误码。现在OSTaskChangePrio()可以通过插入指针将指向当前任务OSTCB的指针从优先级表中移除了L()。这就使得当前任务的旧的优先级可以重新使用了。接着我们检验一下OSTaskChangePrio()想要改变优先级的任务是否就绪L()。如果该任务处于就绪状态它必须在当前的优先级下从就绪表中移除L()然后在新的优先级下插入到就绪表中L()。这儿需要注意的是OSTaskChangePrio()所用的是重新计算的值L()将任务插入就绪表中的。如果任务已经就绪它可能会正在等待一个信号量、一封邮件或是一个消息队列。如果OSTCBEventPtr非空(不等于)L()OSTaskChangePrio()就会知道任务正在等待以上的某件事。如果任务在等待某一事件的发生OSTaskChangePrio()必须将任务从事件控制块(参看,事件控制块)的等待队列(在旧的优先级下)中移除。并在新的优先级下将事件插入到等待队列中L()。任务也有可能正在等待延时的期满(参看第五章-任务管理)或是被挂起(参看,挂起任务OSTaskSuspend())。在这些情况下从L()到L()这几行可以略过。接着OSTaskChangePrio()将指向任务OSTCB的指针存到OSTCBPrioTbl中L()。新的优先级被保存在OSTCB中L()重新计算的值也被保存在OSTCB中L()。OSTaskChangePrio()完成了关键性的步骤后在新的优先级高于旧的优先级或新的优先级高于调用本函数的任务的优先级情况下任务调度程序就会被调用L()。程序清单LOSTaskChangePrio()INTUOSTaskChangePrio(INTUoldprio,INTUnewprio){OSTCB*ptcbOSEVENT*peventINTUxINTUyINTUbitxINTUbityif((oldprio>=OSLOWESTPRIOoldprio!=OSPRIOSELF)||()newprio>=OSLOWESTPRIO){return(OSPRIOINVALID)}OSENTERCRITICAL()if(OSTCBPrioTblnewprio!=(OSTCB*)){()OSEXITCRITICAL()return(OSPRIOEXIST)}else{OSTCBPrioTblnewprio=(OSTCB*)()OSEXITCRITICAL()y=newprio>>()bity=OSMapTblyx=newprioxbitx=OSMapTblxOSENTERCRITICAL()if(oldprio==OSPRIOSELF){()oldprio=OSTCBCur>OSTCBPrio}if((ptcb=OSTCBPrioTbloldprio)!=(OSTCB*)){()OSTCBPrioTbloldprio=(OSTCB*)()if(OSRdyTblptcb>OSTCBYptcb>OSTCBBitX){()if((OSRdyTblptcb>OSTCBY=~ptcb>OSTCBBitX)==){()OSRdyGrp=~ptcb>OSTCBBitY}OSRdyGrp|=bity()OSRdyTbly|=bitx}else{if((pevent=ptcb>OSTCBEventPtr)!=(OSEVENT*)){()if((pevent>OSEventTblptcb>OSTCBY=~ptcb>OSTCBBitX)==){pevent>OSEventGrp=~ptcb>OSTCBBitY}pevent>OSEventGrp|=bity()pevent>OSEventTbly|=bitx}}OSTCBPrioTblnewprio=ptcb()ptcb>OSTCBPrio=newprio()ptcb>OSTCBY=y()ptcb>OSTCBX=xptcb>OSTCBBitY=bityptcb>OSTCBBitX=bitxOSEXITCRITICAL()OSSched()()return(OSNOERR)}else{OSTCBPrioTblnewprio=(OSTCB*)()OSEXITCRITICAL()return(OSPRIOERR)}}挂起任务OSTaskSuspend()有时候将任务挂起是很有用的。挂起任务可通过调用OSTaskSuspend()函数来完成。被挂起的任务只能通过调用OSTaskResume()函数来恢复。任务挂起是一个附加功能。也就是说如果任务在被挂起的同时也在等待延时的期满那么挂起操作需要被取消而任务继续等待延时期满并转入就绪状态。任务可以挂起自己或者其它任务。OSTaskSuspend()函数的代码如程序清单L所示。通常OSTaskSuspend()需要检验临界条件。首先OSTaskSuspend()要确保用户的应用程序不是在挂起空闲任务L()接着确认用户指定优先级是有效的L()。记住最大的有效的优先级数(即最低的优先级)是OSLOWESTPRIO。注意用户可以挂起统计任务(statistic)。可能用户已经注意到了第一个测试L()在L()中被重复了。笔者这样做是为了能与µCOS兼容。第一个测试能够被移除并可以节省一点程序处理的时间但是这样做的意义不大所以笔者决定留下它。接着OSTaskSuspend()检验用户是否通过指定OSPRIOSELF来挂起调用本函数的任务本身L()。用户也可以通过指定优先级来挂起调用本函数的任务L()。在这两种情况下任务调度程序都需要被调用。这就是笔者为什么要定义局部变量self的原因该变量在适当的情况下会被测试。如果用户没有挂起调用本函数的任务OSTaskSuspend()就没有必要运行任务调度程序因为正在挂起的是较低优先级的任务。然后OSTaskSuspend()检验要挂起的任务是否存在L()。如果该任务存在的话它就会从就绪表中被移除L()。注意要被挂起的任务有可能没有在就绪表中因为它有可能在等待事件的发生或延时的期满。在这种情况下要被挂起的任务在OSRdyTbl中对应的位已被清除了(即为)。再次清除该位要比先检验该位是否被清除了再在它没被清除时清除它快得多所以笔者没有检验该位而直接清除它。现在OSTaskSuspend()就可以在任务的OSTCB中设置OSSTATSUSPEND标志了以表明任务正在被挂起L()。最后OSTaskSuspend()只有在被挂起的任务是调用本函数的任务本身的情况下才调用任务调度程序L()。程序清单LOSTaskSuspend()INTUOSTaskSuspend(INTUprio){BOOLEANselfOSTCB*ptcbif(prio==OSIDLEPRIO){()return(OSTASKSUSPENDIDLE)}if(prio>=OSLOWESTPRIOprio!=OSPRIOSELF){()return(OSPRIOINVALID)}OSENTERCRITICAL()if(prio==OSPRIOSELF){()prio=OSTCBCur>OSTCBPrioself=TRUE}elseif(prio==OSTCBCur>OSTCBPrio){()self=TRUE}else{self=FALSE}if((ptcb=OSTCBPrioTblprio)==(OSTCB*)){()OSEXITCRITICAL()return(OSTASKSUSPENDPRIO)}else{if((OSRdyTblptcb>OSTCBY=~ptcb>OSTCBBitX)==){()OSRdyGrp=~ptcb>OSTCBBitY}ptcb>OSTCBStat|=OSSTATSUSPEND()OSEXITCRITICAL()if(self==TRUE){()OSSched()}return(OSNOERR)}}恢复任务OSTaskResume()在上一节中曾提到过被挂起的任务只有通过调用OSTaskResume()才能恢复。OSTaskResume()函数的代码如程序清单L所示。因为OSTaskSuspend()不能挂起空闲任务所以必须得确认用户的应用程序不是在恢复空闲任务L()。注意这个测试也可以确保用户不是在恢复优先级为OSPRIOSELF的任务(OSPRIOSELF被定义为xFF它总是比OSLOWESTPRIO大)。要恢复的任务必须是存在的因为用户要需要操作它的任务控制块OSTCBL(),并且该任务必须是被挂起的L()。OSTaskResume()是通过清除OSTCBStat域中的OSSTATSUSPEND位来取消挂起的L()。要使任务处于就绪状态OSTCBDly域必须为L()这是因为在OSTCBStat中没有任何标志表明任务正在等待延时的期满。只有当以上两个条件都满足的时候任务才处于就绪状态L()。最后任务调度程序会检查被恢复的任务拥有的优先级是否比调用本函数的任务的优先级高L()。程序清单LOSTaskResume()INTUOSTaskResume(INTUprio){OSTCB*ptcbIf(prio>=OSLOWESTPRIO){()return(OSPRIOINVALID)}OSENTERCRITICAL()If((ptcb=OSTCBPrioTblprio)==(OSTCB*)){()OSEXITCRITICAL()return(OSTASKRESUMEPRIO)}else{if(ptcb>OSTCBStatOSSTATSUSPEND){()if(((ptcb>OSTCBStat=~OSSTATSUSPEND)==OSSTATRDY)()(ptcb>OSTCBDly==)){()OSRdyGrp|=ptcb>OSTCBBitY()OSRdyTblptcb>OSTCBY|=ptcb>OSTCBBitXOSEXITCRITICAL()OSSched()()}else{OSEXITCRITICAL()}return(OSNOERR)}else{OSEXITCRITICAL()return(OSTASKNOTSUSPENDED)}}}获得有关任务的信息OSTaskQuery()用户的应用程序可以通过调用OSTaskQuery()来获得自身或其它应用任务的信息。实际上OSTaskQuery()获得的是对应任务的OSTCB中内容的拷贝。用户能访问的OSTCB的数据域的多少决定于用户的应用程序的配置(参看OSCFGH)。由于µCOSⅡ是可裁剪的它只包括那些用户的应用程序所要求的属性和功能。要调用OSTaskQuery()如程序清单L中所示的那样用户的应用程序必须要为OSTCB分配存储空间。这个OSTCB与µCOSⅡ分配的OSTCB是完全不同的数据空间。在调用了OSTaskQuery()后这个OSTCB包含了对应任务的OSTCB的副本。用户必须十分小心地处理OSTCB中指向其它OSTCB的指针(即OSTCBNext与OSTCBPrev)用户不要试图去改变这些指针!一般来说本函数只用来了解任务正在干什么本函数是有用的调试工具。程序清单L得到任务的信息OSTCBMyTaskDatavoidMyTask(void*pdata){pdata=pdatafor(){*用户代码*err=OSTaskQuery(,MyTaskData)*Examineerrorcode**用户代码*}}OSTaskQuery()的代码如程序清单L所示。注意笔者允许用户查询所有的任务包括空闲任务L()。用户尤其需要注意的是不要改变OSTCBNext与OSTCBPrev的指向。通常OSTaskQuery()需要检验用户是否想知道当前任务的有关信息L()以及该任务是否已经建立了L()。所有的域是通过赋值语句一次性复制的而不是一个域一个域地复制的L()。这样复制会比较快一点因为编译器大多都能够产生内存拷贝指令。程序清单LOSTaskQuery()INTUOSTaskQuery(INTUprio,OSTCB*pdata){OSTCB*ptcbif(prio>OSLOWESTPRIOprio!=OSPRIOSELF){()return(OSPRIOINVALID)}OSENTERCRITICAL()if(prio==OSPRIOSELF){()prio=OSTCBCur>OSTCBPrio}if((ptcb=OSTCBPrioTblprio)==(OSTCB*)){()OSEXITCRITICAL()return(OSPRIOERR)}*pdata=*ptcb()OSEXITCRITICAL()return(OSNOERR)}

用户评价(0)

关闭

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

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

提示

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

评分:

/28

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利