下载

2下载券

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

上传资料

关闭

关闭

关闭

封号提示

内容

首页 第6章

第6章.doc

第6章

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

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

1第章任务之间的通讯与同步2事件控制块ECB6初始化一个ECB块OSEventWaitListInit()7使一个任务进入就绪状态OSEventTaskRdy()9使一个任务进入等待状态,OSEventTaskWait()9由于等待超时将一个任务置为就绪状态,OSEventTO()10信号量11建立一个信号量,OSSemCreate()12等待一个信号量,OSSemPend()14发送一个信号量,OSSemPost()16无等待地请求一个信号量,OSSemAccept()17查询一个信号量的当前状态,OSSemQuery()18邮箱19建立一个邮箱OSMboxCreate()20等待一个邮箱中的消息OSMboxPend()22发送一个消息到邮箱中OSMboxPost()24无等待地从邮箱中得到一个消息,OSMboxAccept()25查询一个邮箱的状态,OSMboxQuery()26使用邮箱作为二值信号量27使用邮箱实现延时而不使用OSTimeDly()28消息队列31建立一个消息队列OSQCreate()33等待一个消息队列中的消息OSQPend()35向消息队列发送一个消息(FIFO)OSQPost()37向消息队列发送一个消息(LIFO)OSQPostFront()39无等待地从一个消息队列中取得消息,OSQAccept()40清空一个消息队列,OSQFlush()41查询一个消息队列的状态OSQQuery()42使用消息队列读取模拟量的值43使用一个消息队列作为计数信号量第章任务之间的通讯与同步在µCOSII中有多种方法可以保护任务之间的共享数据和提供任务之间的通讯。在前面的章节中已经讲到了其中的两种:一是利用宏OSENTERCRITICAL()和OSEXITCRITICAL()来关闭中断和打开中断。当两个任务或者一个任务和一个中断服务子程序共享某些数据时可以采用这种方法详见节临界段、节OSENTERCRITICAL()和OSEXITCRITICAL()及节临界段OSCPUH二是利用函数OSSchedLock()和OSSchekUnlock()对µCOSII中的任务调度函数上锁和开锁。用这种方法也可以实现数据的共享详见节给调度器上锁和开锁。本章将介绍另外三种用于数据共享和任务通讯的方法:信号量、邮箱和消息队列。图F介绍了任务和中断服务子程序之间是如何进行通tr=(OSEVENT*)()OSEXITCRITICAL()*err=OSNOERR}}}发送一个信号量,OSSemPost()程序清单L是OSSemPost()函数的源代码。它首先检查参数指针pevent指向的任务控制块是否是OSSemCreate()函数建立的L()接着检查是否有任务在等待该信号量L()。如果该任务控制块中的OSEventGrp域不是说明有任务正在等待该信号量。这时就要调用函数OSEventTaskRdy()见节使一个任务进入就绪状态OSEventTaskRdy()把其中的最高优先级任务从等待任务列表中删除L()并使它进入就绪状态。然后调用OSSched()任务调度函数检查该任务是否是系统中的最高优先级的就绪任务L()。如果是这时就要进行任务切换当OSSemPost()函数是在任务中调用的准备执行该就绪任务。如果不是OSSched()直接返回调用OSSemPost()的任务得以继续执行。如果这时没有任务在等待该信号量该信号量的计数值就简单地加L()。上面是由任务调用OSSemPost()时的情况。当中断服务子程序调用该函数时不会发生上面的任务切换。如果需要任务切换要等到中断嵌套的最外层中断服务子程序调用OSIntExit()函数后才能进行(见节µCOSII中的中断)。程序清单L发出一个信号量INTUOSSemPost(OSEVENT*pevent){OSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPESEM){()OSEXITCRITICAL()return(OSERREVENTTYPE)}if(pevent>OSEventGrp){()OSEventTaskRdy(pevent,(void*),OSSTATSEM)()OSEXITCRITICAL()OSSched()()return(OSNOERR)}else{if(pevent>OSEventCnt<){pevent>OSEventCnt()OSEXITCRITICAL()return(OSNOERR)}else{OSEXITCRITICAL()return(OSSEMOVF)}}}无等待地请求一个信号量,OSSemAccept()当一个任务请求一个信号量时如果该信号量暂时无效也可以让该任务简单地返回而不是进入睡眠等待状态。这种情况下的操作是由OSSemAccept()函数完成的其源代码见程序清单L。该函数在最开始也是检查参数指针pevent指向的事件控制块是否是由OSSemCreate()函数建立的L()接着从该信号量的事件控制块中取出当前计数值L()并检查该信号量是否有效(计数值是否为非值)L()。如果有效则将信号量的计数值减L()然后将信号量的原有计数值返回给调用函数L()。调用函数需要对该返回值进行检查。如果该值是说明该信号量无效。如果该值大于说明该信号量有效同时该值也暗示着该信号量当前可用的资源数。应该注意的是这些可用资源中已经被该调用函数自身占用了一个(该计数值已经被减)。中断服务子程序要请求信号量时只能用OSSemAccept()而不能用OSSemPend()因为中断服务子程序是不允许等待的。程序清单L无等待地请求一个信号量INTUOSSemAccept(OSEVENT*pevent){INTUcntOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPESEM){()OSEXITCRITICAL()return()}cnt=pevent>OSEventCnt()if(cnt>){()pevent>OSEventCnt()}OSEXITCRITICAL()return(cnt)()}查询一个信号量的当前状态,OSSemQuery()在应用程序中用户随时可以调用函数OSSemQuery()程序清单L来查询一个信号量的当前状态。该函数有两个参数:一个是指向信号量对应事件控制块的指针pevent。该指针是在生产信号量时由OSSemCreate()函数返回的另一个是指向用于记录信号量信息的数据结构OSSEMDATA(见uCOSIIH)的指针pdata。因此调用该函数前用户必须先定义该结构变量用于存储信号量的有关信息。在这里之所以使用一个新的数据结构的原因在于调用函数应该只关心那些和特定信号量有关的信息而不是象OSEVENT数据结构包含的很全面的信息。该数据结构只包含信号量计数值OSCnt和等待任务列表OSEventTbl、OSEventGrp而OSEVENT中还包含了另外的两个域OSEventType和OSEventPtr。和其它与信号量有关的函数一样OSSemQuery()也是先检查pevent指向的事件控制块是否是OSSemCreate()产生的L()然后将等待任务列表L()和计数值L()从OSEVENT结构拷贝到OSSEMDATA结构变量中去。程序清单L查询一个信号量的状态INTUOSSemQuery(OSEVENT*pevent,OSSEMDATA*pdata){INTUiINTU*psrcINTU*pdestOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPESEM){()OSEXITCRITICAL()return(OSERREVENTTYPE)}pdata>OSEventGrp=pevent>OSEventGrp()psrc=pevent>OSEventTblpdest=pdata>OSEventTblfor(i=i<OSEVENTTBLSIZEi){*pdest=*psrc}pdata>OSCnt=pevent>OSEventCnt()OSEXITCRITICAL()return(OSNOERR)}邮箱邮箱是µCOSII中另一种通讯机制它可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构。为了在µCOSII中使用邮箱必须将OSCFGH中的OSMBOXEN常数置为。使用邮箱之前必须先建立该邮箱。该操作可以通过调用OSMboxCreate()函数来完成(见下节)并且要指定指针的初始值。一般情况下这个初始值是但也可以初始化一个邮箱使其在最开始就包含一条消息。如果使用邮箱的目的是用来通知一个事件的发生(发送一条消息)那么就要初始化该邮箱为因为在开始时事件还没有发生。如果用户用邮箱来共享某些资源那么就要初始化该邮箱为一个非的指针。在这种情况下邮箱被当成一个二值信号量使用。µCOSII提供了种对邮箱的操作:OSMboxCreate()OSMboxPend()OSMboxPost()OSMboxAccept()和OSMboxQuery()函数。图F描述了任务、中断服务子程序和邮箱之间的关系这里用符号“I”表示邮箱。邮箱包含的内容是一个指向一条消息的指针。一个邮箱只能包含一个这样的指针(邮箱为满时)或者一个指向的指针(邮箱为空时)。从图F可以看出任务或者中断服务子程序可以调用函数OSMboxPost()但是只有任务可以调用函数OSMboxPend()和OSMboxQuery()。图F任务、中断服务子程序和邮箱之间的关系建立一个邮箱OSMboxCreate()程序清单L是OSMboxCreate()函数的源代码基本上和函数OSSemCreate()相似。不同之处在于事件控制块的类型被设置成OSEVENTTYPEMBOXL()以及使用OSEventPtr域来容纳消息指针而不是使用OSEventCnt域L()。OSMboxCreate()函数的返回值是一个指向事件控制块的指针L()。这个指针在调用函数OSMboxPend()OSMboxPost()OSMboxAccept()和OSMboxQuery()时使用。因此该指针可以看作是对应邮箱的句柄。值得注意的是如果系统中已经没有事件控制块可用函数OSMboxCreate()将返回一个指针。邮箱一旦建立是不能被删除的。比如如果有任务正在等待一个邮箱的信息这时删除该邮箱将有可能产生灾难性的后果。程序清单L建立一个邮箱OSEVENT*OSMboxCreate(void*msg){OSEVENT*peventOSENTERCRITICAL()pevent=OSEventFreeListif(OSEventFreeList!=(OSEVENT*)){OSEventFreeList=(OSEVENT*)OSEventFreeList>OSEventPtr}OSEXITCRITICAL()if(pevent!=(OSEVENT*)){pevent>OSEventType=OSEVENTTYPEMBOX()pevent>OSEventPtr=msg()OSEventWaitListInit(pevent)}return(pevent)()}等待一个邮箱中的消息OSMboxPend()程序清单L是OSMboxPend()函数的源代码。同样它和OSSemPend()也很相似因此在这里只讲述其中的不同之处。OSMboxPend()首先检查该事件控制块是由OSMboxCreate()函数建立的L()。当OSEventPtr域是一个非的指针时说明该邮箱中有可用的消息L()。这种情况下OSMboxPend()函数将该域的值复制到局部变量msg中然后将OSEventPtr置为L()。这正是我们所期望的也是执行OSMboxPend()函数最快的路径。如果此时邮箱中没有消息是可用的(OSEventPtr域是指针)OSMboxPend()函数检查它的调用者是否是中断服务子程序L()。象OSSemPend()函数一样不能在中断服务子程序中调用OSMboxPend()因为中断服务子程序是不能等待的。这里的代码同样是为了以防万一。但是如果邮箱中有可用的消息即使从中断服务子程序中调用OSMboxPend()函数也一样是成功的。如果邮箱中没有可用的消息OSMboxPend()的调用任务就被挂起直到邮箱中有了消息或者等待超时L()。当有其它的任务向该邮箱发送了消息后(或者等待时间超时)这时该任务再一次成为最高优先级任务OSSched()返回。这时OSMboxPend()函数要检查是否有消息被放到该任务的任务控制块中L()。如果有那么该次函数调用成功对应的消息被返回到调用函数。程序清单L等待一个邮箱中的消息void*OSMboxPend(OSEVENT*pevent,INTUtimeout,INTU*err){void*msgOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEMBOX){()OSEXITCRITICAL()*err=OSERREVENTTYPEreturn((void*))}msg=pevent>OSEventPtrif(msg!=(void*)){()pevent>OSEventPtr=(void*)()OSEXITCRITICAL()*err=OSNOERR}elseif(OSIntNesting>){()OSEXITCRITICAL()*err=OSERRPENDISR}else{OSTCBCur>OSTCBStat|=OSSTATMBOX()OSTCBCur>OSTCBDly=timeoutOSEventTaskWait(pevent)OSEXITCRITICAL()OSSched()OSENTERCRITICAL()if((msg=OSTCBCur>OSTCBMsg)!=(void*)){()OSTCBCur>OSTCBMsg=(void*)OSTCBCur>OSTCBStat=OSSTATRDYOSTCBCur>OSTCBEventPtr=(OSEVENT*)OSEXITCRITICAL()*err=OSNOERR}elseif(OSTCBCur>OSTCBStatOSSTATMBOX){()OSEventTO(pevent)()OSEXITCRITICAL()msg=(void*)()*err=OSTIMEOUT}else{msg=pevent>OSEventPtr()pevent>OSEventPtr=(void*)()OSTCBCur>OSTCBEventPtr=(OSEVENT*)()OSEXITCRITICAL()*err=OSNOERR}}return(msg)}在OSMboxPend()函数中通过检查任务控制块中的OSTCBStat域中的OSSTATMBOX位可以知道是否等待超时。如果该域被置说明任务等待已经超时L()。这时通过调用函数OSEventTo()可以将任务从邮箱的等待列表中删除L()。因为此时邮箱中没有消息所以返回的指针是L()。如果OSSTATMBOX位没有被置说明所等待的消息已经被发出。OSMboxPend()的调用函数得到指向消息的指针L()。此后OSMboxPend()函数通过将邮箱事件控制块的OSEventPtr域置为清空该邮箱并且要将任务任务控制块中指向邮箱事件控制块的指针删除L()。发送一个消息到邮箱中OSMboxPost()程序清单L是OSMboxPost()函数的源代码。检查了事件控制块是否是一个邮箱后L()OSMboxPost()函数还要检查是否有任务在等待该邮箱中的消息L()。如果事件控制块中的OSEventGrp域包含非零值就暗示着有任务在等待该消息。这时调用OSEventTaskRdy()将其中的最高优先级任务从等待列表中删除见节使一个任务进入就绪状态OSEventTaskRdy()L()加入系统的就绪任务列表中准备运行。然后调用OSSched()函数L()检查该任务是否是系统中最高优先级的就绪任务。如果是执行任务切换仅当OSMboxPost()函数是由任务调用时该任务得以执行。如果该任务不是最高优先级的任务OSSched()返回OSMboxPost()的调用函数继续执行。如果没有任何任务等待该消息指向消息的指针就被保存到邮箱中L()(假设此时邮箱中的指针不是非的L())。这样下一个调用OSMboxPend()函数的任务就可以立刻得到该消息了。注意如果OSMboxPost()函数是从中断服务子程序中调用的那么这时并不发生上下文的切换。如果需要中断服务子程序引起的上下文切换只发生在中断嵌套的最外层中断服务子程序对OSIntExit()函数的调用时(见节µCOSII中的中断)。程序清单L向邮箱中发送一条消息INTUOSMboxPost(OSEVENT*pevent,void*msg){OSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEMBOX){()OSEXITCRITICAL()return(OSERREVENTTYPE)}if(pevent>OSEventGrp){()OSEventTaskRdy(pevent,msg,OSSTATMBOX)()OSEXITCRITICAL()OSSched()()return(OSNOERR)}else{if(pevent>OSEventPtr!=(void*)){()OSEXITCRITICAL()return(OSMBOXFULL)}else{pevent>OSEventPtr=msg()OSEXITCRITICAL()return(OSNOERR)}}}无等待地从邮箱中得到一个消息,OSMboxAccept()应用程序也可以以无等待的方式从邮箱中得到消息。这可以通过程序清单L中的OSMboxAccept()函数来实现。OSMboxAccept()函数开始也是检查事件控制块是否是由OSMboxCreate()函数建立的L()。接着它得到邮箱中的当前内容L()并判断是否有消息是可用的L()。如果邮箱中有消息就把邮箱清空L()而邮箱中原来指向消息的指针被返回给OSMboxAccept()的调用函数L()。OSMboxAccept()函数的调用函数必须检查该返回值是否为。如果该值是说明邮箱是空的没有可用的消息。如果该值是非值说明邮箱中有消息可用而且该调用函数已经得到了该消息。中断服务子程序在试图得到一个消息时应该使用OSMboxAccept()函数而不能使用OSMboxPend()函数。OSMboxAccept()函数的另一个用途是用户可以用它来清空一个邮箱中现有的内容。程序清单L无等待地从邮箱中得到消息void*OSMboxAccept(OSEVENT*pevent){void*msgOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEMBOX){()OSEXITCRITICAL()return((void*))}msg=pevent>OSEventPtr()if(msg!=(void*)){()pevent>OSEventPtr=(void*)()}OSEXITCRITICAL()return(msg)()}查询一个邮箱的状态,OSMboxQuery()OSMboxQuery()函数使应用程序可以随时查询一个邮箱的当前状态。程序清单L是该函数的源代码。它需要两个参数:一个是指向邮箱的指针pevent。该指针是在建立该邮箱时由OSMboxCreate()函数返回的另一个是指向用来保存有关邮箱的信息的OSMBOXDATA(见uCOSIIH)数据结构的指针pdata。在调用OSMboxCreate()函数之前必须先定义该结构变量用来保存有关邮箱的信息。之所以定义一个新的数据结构是因为这里关心的只是和特定邮箱有关的内容而非整个OSEVENT数据结构的内容。后者还包含了另外两个域(OSEventCnt和OSEventType)而OSMBOXDATA只包含邮箱中的消息指针(OSMsg)和该邮箱现有的等待任务列表(OSEventTbl和OSEventGrp)。和前面的所以函数一样该函数也是先检查事件控制是否是邮箱L()。然后将邮箱中的等待任务列表L()和邮箱中的消息L()从OSEVENT数据结构复制到OSMBOXDATA数据结构。程序清单L查询邮箱的状态INTUOSMboxQuery(OSEVENT*pevent,OSMBOXDATA*pdata){INTUiINTU*psrcINTU*pdestOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEMBOX){()OSEXITCRITICAL()return(OSERREVENTTYPE)}pdata>OSEventGrp=pevent>OSEventGrp()psrc=pevent>OSEventTblpdest=pdata>OSEventTblfor(i=i<OSEVENTTBLSIZEi){*pdest=*psrc}pdata>OSMsg=pevent>OSEventPtr()OSEXITCRITICAL()return(OSNOERR)}用邮箱作二值信号量一个邮箱可以被用作二值的信号量。首先在初始化时将邮箱设置为一个非零的指针(如void*)。这样一个任务可以调用OSMboxPend()函数来请求一个信号量然后通过调用OSMboxPost()函数来释放一个信号量。程序清单L说明了这个过程是如何工作的。如果用户只需要二值信号量和邮箱这样做可以节省代码空间。这时可以将OSSEMEN设置为只使用邮箱就可以了。程序清单L使用邮箱作为二值信号量OSEVENT*MboxSemvoidTask(void*pdata){INTUerrfor(){OSMboxPend(MboxSem,,err)*获得对资源的访问权**任务获得信号量,对资源进行访问*OSMboxPost(MboxSem,(void*))*释放对资源的访问权*}}用邮箱实现延时而不使用OSTimeDly()邮箱的等待超时功能可以被用来模仿OSTimeDly()函数的延时如程序清单L所示。如果在指定的时间段TIMEOUT内没有消息到来Task()函数将继续执行。这和OSTimeDly(TIMEOUT)功能很相似。但是如果Task()在指定的时间结束之前向该邮箱发送了一个“哑”消息Task()就会提前开始继续执行。这和调用OSTimeDlyResume()函数的功能是一样的。注意这里忽略了对返回的消息的检查因为此时关心的不是得到了什么样的消息。程序清单L使用邮箱实现延时OSEVENT*MboxTimeDlyvoidTask(void*pdata){INTUerrfor(){OSMboxPend(MboxTimeDly,TIMEOUT,err)*延时该任务**延时结束后执行的代码*}}voidTask(void*pdata){INTUerrfor(){OSMboxPost(MboxTimeDly,(void*))*取消任务的延时*}}消息队列消息队列是µCOSII中另一种通讯机制它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。因具体的应用有所不同每个指针指向的数据结构变量也有所不同。为了使用µCOSII的消息队列功能需要在OSCFGH文件中将OSQEN常数设置为并且通过常数OSMAXQS来决定µCOSII支持的最多消息队列数。在使用一个消息队列之前必须先建立该消息队列。这可以通过调用OSQCreate()函数(见节)并定义消息队列中的单元数(消息数)来完成。µCOSII提供了个对消息队列进行操作的函数:OSQCreate()OSQPend()OSQPost()OSQPostFront()OSQAccept()OSQFlush()和OSQQuery()函数。图F是任务、中断服务子程序和消息队列之间的关系。其中消息队列的符号很像多个邮箱。实际上我们可以将消息队列看作时多个邮箱组成的数组只是它们共用一个等待任务列表。每个指针所指向的数据结构是由具体的应用程序决定的。N代表了消息队列中的总单元数。当调用OSQPend()或者OSQAccept()之前调用N次OSQPost()或者OSQPostFront()就会把消息队列填满。从图F中可以看出一个任务或者中断服务子程序可以调用OSQPost()OSQPostFront()OSQFlush()或者OSQAccept()函数。但是只有任务可以调用OSQPend()和OSQQuery()函数。图F任务、中断服务子程序和消息队列之间的关系Figure图F是实现消息队列所需要的各种数据结构。这里也需要事件控制块来记录等待任务列表F()而且事件控制块可以使多个消息队列的操作和信号量操作、邮箱操作相同的代码。当建立了一个消息队列时一个队列控制块(OSQ结构见OSQC文件)也同时被建立并通过OSEVENT中的OSEventPtr域链接到对应的事件控制块F()。在建立一个消息队列之前必须先定义一个含有与消息队列最大消息数相同个数的指针数组F()。数组的起始地址以及数组中的元素数作为参数传递给OSQCreate()函数。事实上如果内存占用了连续的地址空间也没有必要非得使用指针数组结构。文件OSCFGH中的常数OSMAXQS定义了在µCOSII中可以使用的最大消息队列数这个值最小应为。µCOSII在初始化时建立一个空闲的队列控制块链表如图F所示。图F用于消息队列的数据结构Figure图F空闲队列控制块链表Figure队列控制块是一个用于维护消息队列信息的数据结构它包含了以下的一些域。这里仍然在各个变量前加入一个来表示它们是数据结构中的一个域。OSQPtr在空闲队列控制块中链接所有的队列控制块。一旦建立了消息队列该域就不再有用了。OSQStart是指向消息队列的指针数组的起始地址的指针。用户应用程序在使用消息队列之前必须先定义该数组。OSQEnd是指向消息队列结束单元的下一个地址的指针。该指针使得消息队列构成一个循环的缓冲区。OSQIn是指向消息队列中插入下一条消息的位置的指针。当OSQIn和OSQEnd相等时OSQIn被调整指向消息队列的起始单元。OSQOut是指向消息队列中下一个取出消息的位置的指针。当OSQOut和OSQEnd相等时OSQOut被调整指向消息队列的起始单元。OSQSize是消息队列中总的单元数。该值是在建立消息队列时由用户应用程序决定的。在µCOSII中该值最大可以是,。OSQEntries是消息队列中当前的消息数量。当消息队列是空的时该值为。当消息队列满了以后该值和OSQSize值一样。在消息队列刚刚建立时该值为。消息队列最根本的部分是一个循环缓冲区如图F。其中的每个单元包含一个指针。队列未满时OSQInF()指向下一个存放消息的地址单元。如果队列已满(OSQEntries与OSQSize相等)OSQInF()则与OSQOut指向同一单元。如果在OSQIn指向的单元插入新的指向消息的指针就构成FIFO(FirstInFirstOut)队列。相反如果在OSQOut指向的单元的下一个单元插入新的指针就构成LIFO队列(LastInFirstOut)F()。当OSQEntries和OSQSize相等时说明队列已满。消息指针总是从OSQOutF()指向的单元取出。指针OSQStart和OSQEndF()定义了消息指针数组的头尾以便在OSQIn和OSQOut到达队列的边缘时进行边界检查和必要的指针调整实现循环功能。图F消息队列是一个由指针组成的循环缓冲区Figure建立一个消息队列OSQCreate()程序清单L是OSQCreate()函数的源代码。该函数需要一个指针数组来容纳指向各个消息的指针。该指针数组必须声名为void类型。OSQCreate()首先从空闲事件控制块链表中取得一个事件控制块(见图F)L()并对剩下的空闲事件控制块列表的指针做相应的调整使它指向下一个空闲事件控制块L()。接着OSQCreate()函数从空闲队列控制块列表中取出一个队列控制块L()。如果有空闲队列控制块是可以的就对其进行初始化L()。然后该函数将事件控制块的类型设置为OSEVENTTYPEQL()并使其OSEventPtr指针指向队列控制块L()。OSQCreate()还要调用OSEventWaitListInit()函数对事件控制块的等待任务列表初始化见节初始化一个事件控制块OSEventWaitListInit()L()。因为此时消息队列正在初始化显然它的等待任务列表是空的。最后OSQCreate()向它的调用函数返回一个指向事件控制块的指针L()。该指针将在调用OSQPend()OSQPost()OSQPostFront()OSQFlush()OSQAccept()和OSQQuery()等消息队列处理函数时使用。因此该指针可以被看作是对应消息队列的句柄。值得注意的是如果此时没有空闲的事件控制块OSQCreate()函数将返回一个指针。如果没有队列控制块可以使用为了不浪费事件控制块资源OSQCreate()函数将把刚刚取得的事件控制块重新返还给空闲事件控制块列表L()。另外消息队列一旦建立就不能再删除了。试想如果有任务正在等待某个消息队列中的消息而此时又删除该消息队列将是很危险的。程序清单L建立一个消息队列OSEVENT*OSQCreate(void**start,INTUsize){OSEVENT*peventOSQ*pqOSENTERCRITICAL()pevent=OSEventFreeList()if(OSEventFreeList!=(OSEVENT*)){OSEventFreeList=(OSEVENT*)OSEventFreeList>OSEventPtr()}OSEXITCRITICAL()if(pevent!=(OSEVENT*)){OSENTERCRITICAL()pq=OSQFreeList()if(OSQFreeList!=(OSQ*)){OSQFreeList=OSQFreeList>OSQPtr}OSEXITCRITICAL()if(pq!=(OSQ*)){pq>OSQStart=start()pq>OSQEnd=startsizepq>OSQIn=startpq>OSQOut=startpq>OSQSize=sizepq>OSQEntries=pevent>OSEventType=OSEVENTTYPEQ()pevent>OSEventPtr=pq()OSEventWaitListInit(pevent)()}else{OSENTERCRITICAL()pevent>OSEventPtr=(void*)OSEventFreeList()OSEventFreeList=peventOSEXITCRITICAL()pevent=(OSEVENT*)}}return(pevent)()}等待一个消息队列中的消息OSQPend()程序清单L是OSQPend()函数的源代码。OSQPend()函数首先检查事件控制块是否是由OSQCreate()函数建立的L()接着该函数检查消息队列中是否有消息可用(即OSQEntries是否大于)L()。如果有OSQPend()函数将指向消息的指针复制到msg变量中并让OSQOut指针指向队列中的下一个单元L(),然后将队列中的有效消息数减L()。因为消息队列是一个循环的缓冲区OSQPend()函数需要检查OSQOut是否超过了队列中的最后一个单元L()。当发生这种越界时就要将OSQOut重新调整到指向队列的起始单元L()。这是我们调用OSQPend()函数时所期望的也是执行OSQPend()函数最快的路径。程序清单L在一个消息队列中等待一条消息void*OSQPend(OSEVENT*pevent,INTUtimeout,INTU*err){void*msgOSQ*pqOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEQ){()OSEXITCRITICAL()*err=OSERREVENTTYPEreturn((void*))}pq=pevent>OSEventPtrif(pq>OSQEntries!=){()msg=*pq>OSQOut()pq>OSQEntries()if(pq>OSQOut==pq>OSQEnd){()pq>OSQOut=pq>OSQStart()}OSEXITCRITICAL()*err=OSNOERR}elseif(OSIntNesting>){()OSEXITCRITICAL()*err=OSERRPENDISR}else{OSTCBCur>OSTCBStat|=OSSTATQ()OSTCBCur>OSTCBDly=timeoutOSEventTaskWait(pevent)OSEXITCRITICAL()OSSched()()OSENTERCRITICAL()if((msg=OSTCBCur>OSTCBMsg)!=(void*)){()OSTCBCur>OSTCBMsg=(void*)OSTCBCur>OSTCBStat=OSSTATRDYOSTCBCur>OSTCBEventPtr=(OSEVENT*)()OSEXITCRITICAL()*err=OSNOERR}elseif(OSTCBCur>OSTCBStatOSSTATQ){()OSEventTO(pevent)()OSEXITCRITICAL()msg=(void*)()*err=OSTIMEOUT}else{msg=*pq>OSQOut()pq>OSQEntriesif(pq>OSQOut==pq>OSQEnd){pq>OSQOut=pq>OSQStart}OSTCBCur>OSTCBEventPtr=(OSEVENT*)()OSEXITCRITICAL()*err=OSNOERR}}return(msg)()}如果这时消息队列中没有消息(OSEventEntries是)OSQPend()函数检查它的调用者是否是中断服务子程序L()。象OSSemPend()和OSMboxPend()函数一样不能在中断服务子程序中调用OSQPend()因为中断服务子程序是不能等待的。但是如果消息队列中有消息即使从中断服务子程序中调用OSQPend()函数也一样是成功的。如果消息队列中没有消息调用OSQPend()函数的任务被挂起L()。当有其它的任务向该消息队列发送了消息或者等待时间超时并且该任务成为最高优先级任务时OSSched()返回L()。这时OSQPend()要检查是否有消息被放到该任务的任务控制块中L()。如果有那么该次函数调用成功把任务的任务控制块中指向消息队列的指针删除L()并将对应的消息被返回到调用函数L()。在OSQPend()函数中通过检查任务的任务控制块中的OSTCBStat域可以知道是否等到时间超时。如果其对应的OSSTATQ位被置说明任务等待已经超时L()。这时通过调用函数OSEventTo()可以将任务从消息队列的等待任务列表中删除L()。这时因为消息队列中没有消息所以返回的指针是L()。如果任务控制块标志位中的OSSTATQ位没有被置说明有任务发出了一条消息。OSQPend()函数从队列中取出该消息L()。然后将任务的任务控制中指向事件控制块的指针删除L()。向消息队列发送一个消息(FIFO)OSQPost()程序清单L是OSQPost()函数的源代码。在确认事件控制块是消息队列后L()OSQPost()函数检查是否有任务在等待该消息队列中的消息L()。当事件控制块的OSEventGrp域为非值时说明该消息队列的等待任务列表中有任务。这时调用OSEventTaskRdy()函数见节使一个任务进入就绪状态OSEventTaskRdy()从列表中取出最高优先级的任务L()并将它置于就绪状态。然后调用函数OSSched()L()进行任务的调度。如果上面取出的任务的优先级在整个系统就绪的任务里也是最高的而且OSQPost()函数不是中断服务子程序调用的就执行任务切换该最高优先级任务被执行。否则的话OSSched()函数直接返回调用OSQPost()函数的任务继续执行。程序清单L向消息队列发送一条消息INTUOSQPost(OSEVENT*pevent,void*msg){OSQ*pqOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEQ){()OSEXITCRITICAL()return(OSERREVENTTYPE)}if(pevent>OSEventGrp){()OSEventTaskRdy(pevent,msg,OSSTATQ)()OSEXITCRITICAL()OSSched()()return(OSNOERR)}else{pq=pevent>OSEventPtrif(pq>OSQEntries>=pq>OSQSize){()OSEXITCRITICAL()return(OSQFULL)}else{*pq>OSQIn=msg()pq>OSQEntriesif(pq>OSQIn==pq>OSQEnd){pq>OSQIn=pq>OSQStart}OSEXITCRITICAL()}return(OSNOERR)}}如果没有任务等待该消息队列中的消息而且此时消息队列未满L()指向该消息的指针被插入到消息队列中L()。这样下一个调用OSQPend()函数的任务就可以马上得到该消息。注意如果此时消息队列已满那么该消息将由于不能插入到消息队列中而丢失。此外如果OSQPost()函数是由中断服务子程序调用的那么即使产生了更高优先级的任务也不会在调用OSSched()函数时发生任务切换。这个动作一直要等到中断嵌套的最外层中断服务子程序调用OSIntExit()函数时才能进行(见节µCOSII中的中断)。向消息队列发送一个消息(后进先出LIFO)OSQPostFront()OSQPostFront()函数和OSQPost()基本上是一样的只是在插入新的消息到消息队列中时使用OSQOut作为指向下一个插入消息的单元的指针而不是OSQIn。程序清单L是它的源代码。值得注意的是OSQOut指针指向的是已经插入了消息指针的单元所以再插入新的消息指针前必须先将OSQOut指针在消息队列中前移一个单元。如果OSQOut指针指向的当前单元是队列中的第一个单元L()这时再前移就会发生越界需要特别地将该指针指向队列的末尾L()。由于OSQEnd指向的是消息队列中最后一个单元的下一个单元因此OSQOut必须被调整到指向队列的有效范围内L()。因为QSQPend()函数取出的消息是由OSQPend()函数刚刚插入的因此OSQPostFront()函数实现了一个LIFO队列。程序清单L向消息队列发送一条消息(LIFO)INTUOSQPostFront(OSEVENT*pevent,void*msg){OSQ*pqOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEQ){OSEXITCRITICAL()return(OSERREVENTTYPE)}if(pevent>OSEventGrp){OSEventTaskRdy(pevent,msg,OSSTATQ)OSEXITCRITICAL()OSSched()return(OSNOERR)}else{pq=pevent>OSEventPtrif(pq>OSQEntries>=pq>OSQSize){OSEXITCRITICAL()return(OSQFULL)}else{if(pq>OSQOut==pq>OSQStart){()pq>OSQOut=pq>OSQEnd()}pq>OSQOut()*pq>OSQOut=msgpq>OSQEntriesOSEXITCRITICAL()}return(OSNOERR)}}无等待地从一个消息队列中取得消息,OSQAccept()如果试图从消息队列中取出一条消息而此时消息队列又为空时也可以不让调用任务等待而直接返回调用函数。这个操作可以调用OSQAccept()函数来完成。程序清单L是该函数的源代码。OSQAccept()函数首先查看pevent指向的事件控制块是否是由OSQCreate()函数建立的L()然后它检查当前消息队列中是否有消息L()。如果消息队列中有至少一条消息那么就从OSQOut指向的单元中取出消息L()。OSQAccept()函数的调用函数需要对OSQAccept()返回的指针进行检查。如果该指针是值说明消息队列是空的其中没有消息可以L()。否则的话说明已经从消息队列中成功地取得了一条消息。当中断服务子程序要从消息队列中取消息时必须使用OSQAccept()函数而不能使用OSQPend()函数。程序清单L无等待地从消息队列中取一条消息void*OSQAccept(OSEVENT*pevent){void*msgOSQ*pqOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEQ){()OSEXITCRITICAL()return((void*))}pq=pevent>OSEventPtrif(pq>OSQEntries!=){()msg=*pq>OSQOut()pq>OSQEntriesif(pq>OSQOut==pq>OSQEnd){pq>OSQOut=pq>OSQStart}}else{msg=(void*)()}OSEXITCRITICAL()return(msg)}清空一个消息队列,OSQFlush()OSQFlush()函数允许用户删除一个消息队列中的所有消息重新开始使用。程序清单L是该函数的源代码。和前面的其它函数一样该函数首先检查pevent指针是否是执行一个消息队列L()然后将队列的插入指针和取出指针复位使它们都指向队列起始单元同时将队列中的消息数设为L()。这里没有检查该消息队列的等待任务列表是否为空因为只要该等待任务列表不空OSQEntries就一定是。唯一不同的是指针OSQIn和OSQOut此时可以指向消息队列中的任何单元不一定是起始单元。程序清单L清空消息队列INTUOSQFlush(OSEVENT*pevent){OSQ*pqOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEQ){()OSEXITCRITICAL()return(OSERREVENTTYPE)}pq=pevent>OSEventPtrpq>OSQIn=pq>OSQStart()pq>OSQOut=pq>OSQStartpq>OSQEntries=OSEXITCRITICAL()return(OSNOERR)}查询一个消息队列的状态OSQQuery()OSQQuery()函数使用户可以查询一个消息队列的当前状态。程序清单L是该函数的源代码。OSQQuery()需要两个参数:一个是指向消息队列的指针pevent。它是在建立一个消息队列时由OSQCreate()函数返回的另一个是指向OSQDATA(见uCOSIIH)数据结构的指针pdata。该结构包含了有关消息队列的信息。在调用OSQQuery()函数之前必须先定义该数据结构变量。OSQDATA结构包含下面的几个域:OSMsg如果消息队列中有消息它包含指针OSQOut所指向的队列单元中的内容。如果队列是空的OSMsg包含一个指针。OSNMsgs是消息队列中的消息数(OSQEntries的拷贝)。OSQSize是消息队列的总的容量OSEventTbl和OSEventGrp是消息队列的等待任务列表。通过它们OSQQuery()的调用函数可以得到等待该消息队列中的消息的任务总数。OSQQuery()函数首先检查pevent指针指向的事件控制块是一个消息队列L()然后复制等待任务列表L()。如果消息队列中有消息L()OSQOut指向的队列单元中的内容被复制到OSQDATA结构中L()否则的话就复制一个指针L()。最后复制消息队列中的消息数和消息队列的容量大小L()。程序清单L程序消息队列的状态INTUOSQQuery(OSEVENT*pevent,OSQDATA*pdata){OSQ*pqINTUiINTU*psrcINTU*pdestOSENTERCRITICAL()if(pevent>OSEventType!=OSEVENTTYPEQ){()OSEXITCRITICAL()return(OSERREVENTTYPE)}pdata>OSEventGrp=pevent>OSEventGrp()psrc=pevent>OSEventTblpdest=pdata>OSEventTblfor(i=i<OSEVENTTBLSIZEi){*pdest=*psrc}pq=(OSQ*)pevent>OSEventPtrif(pq>OSQEntries>){()pdata>OSMsg=pq>OSQOut()}else{pdata>OSMsg=(void*)()}pdata>OSNMsgs=pq>OSQEntries()pdata>OSQSize=pq>OSQSizeOSEXITCRITICAL()return(OSNOERR)}使用消息队列读取模拟量的值在控制系统中经常要频繁地读取模拟量的值。这时可以先建立一个定时任务OSTimeDly()见节延时一个任务OSTimeDly()并且给出希望的抽样周期。然后如图F所示让AD采样的任务从一个消息队列中等待消息。该程序最长的等待时间就是抽样周期。当没有其它任务向该消息队列中发送消息时AD采样任务因为等待超时而退出等待状态并进行执行。这就模仿了OSTimeDly()函数的功能。也许读者会提出疑问既然OSTimeDly()函数能完成这项工作为什么还要使用消息队列呢?这是因为借助消息队列我们可以让其它的任务向消息队列发送消息来终止AD采样任务等待消息使其马上执行一次AD采样。此外我们还可以通过消息队列来通知AD采样程序具体对哪个通道进行采样告诉它增加采样频率等等从而使得我们的应用更智能化。换句话说我们可以告诉AD采样程序“现在马上读取通道的输入值!”之后该采样任务将重新开始在消息队列中等待消息准备开始一次新的扫描过程。图F读模拟量输入Figure使用一个消息队列作为计数信号量在消息队列初始化时可以将消息队列中的多个指针设为非值(如void*)来实现计数信号量的功能。这里初始化为非值的指针数就是可用的资源数。系统中的任务可以通过OSQPend()来请求“信号量”然后通过调用OSQPost()来释放“信号量”如程序清单L。如果系统中只使用了计数信号量和消息队列使用这种方法可以有效地节省代码空间。这时将OSSEMEN设为就可以不使用信号量而只使用消息队列。值得注意的是这种方法为共享资源引入了大量的指针变量。也就是说为了节省代码空间牺牲了RAM空间。另外对消息队列的操作要比对信号量的操作慢因此当用计数信号量同步的信号量很多时这种方法的效率是非常低的。程序清单L使用消息队列作为一个计数信号量OSEVENT*QSemvoid*QMsgTblNRESOURCESvoidmain(void){OSInit()QSem=OSQCreate(QMsgTbl,NRESOURCES)for(i=i<NRESOURCESi){OSQPost(Qsem,(void*))}OSTaskCreate(Task,,,)OSStart()}voidTask(void*pdata){INTUerrfor(){OSQPend(QSem,,err)*得到对资源的访问权**任务获得信号量,对资源进行访问*OSMQPost(QSem,(void*))*释放对资源的访问权*}}

用户评价(0)

关闭

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

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

提示

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

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/45

第6章

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利