程序清单L6.16向邮箱中发送一条消息
INT8UOSMboxPost(OS_EVENT*pevent,void*msg)
{
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
if(pevent->OSEventGrp){(2)
OSEventTaskRdy(pevent,msg,OS_STAT_MBOX);(3)
OS_EXIT_CRITICAL();
OSSched();(4)
return(OS_NO_ERR);
}else{
if(pevent->OSEventPtr!=(void*)0){(5)
OS_EXIT_CRITICAL();
return(OS_MBOX_FULL);
}else{
pevent->OSEventPtr=msg;(6)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
}
}
6.7.4 无等待地从邮箱中得到一个消息,OSMboxAccept()
应用程序也可以以无等待的方式从邮箱中得到消息。这可以通过程序清单L6.17中的OSMboxAccept()函数来实现。OSMboxAccept()函数开始也是检查事件控制块是否是由OSMboxCreate()函数建立的 [L6.17(1)]。接着,它得到邮箱中的当前内容[L6.17(2)],并判断是否有消息是可用的[L6.17(3)]。如果邮箱中有消息,就把邮箱清空[L6.17(4)],而邮箱中原来指向消息的指针被返回给OSMboxAccept()的调用函数[L6.17(5)]。OSMboxAccept()函数的调用函数必须检查该返回值是否为NULL。如果该值是NULL,说明邮箱是空的,没有可用的消息。
如果该值是非NULL值,说明邮箱中有消息可用,而且该调用函数已经得到了该消息。中断服务子程序在试图得到一个消息时, 应该使用OSMboxAccept()函数, 而不能使用OSMboxPend()函数。
OSMboxAccept()函数的另一个用途是,用户可以用它来清空一个邮箱中现有的内容。
程序清单L6.17无等待地从邮箱中得到消息
void*OSMboxAccept(OS_EVENT*pevent)
{
void*msg;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)
OS_EXIT_CRITICAL();
return((void*)0);
}
msg=pevent->OSEventPtr;(2)
if(msg!=(void*)0){(3)
pevent->OSEventPtr=(void*)0;(4)
}
OS_EXIT_CRITICAL();
return(msg);(5)
}
6.7.5 查询一个邮箱的状态,OSMboxQuery()
OSMboxQuery()函数使应用程序可以随时查询一个邮箱的当前状态。程序清单L6.18是该函数的源代码。它需要两个参数:一个是指向邮箱的指针pevent。该指针是在建立该邮箱时,由OSMboxCreate()函数返回的;另一个是指向用来保存有关邮箱的信息的OS_MBOX_DATA(见
uCOS_II.H)数据结构的指针pdata。在调用OSMboxCreate()函数之前,必须先定义该结构变量,
用来保存有关邮箱的信息。之所以定义一个新的数据结构,是因为这里关心的只是和特定邮箱
有关的内容,而非整个OS_EVENT数据结构的内容。后者还包含了另外两个域
(.OSEventCnt和.OSEventType),而OS_MBOX_DATA只包含邮箱中的消息指针(.OSMsg)和该邮箱现有的等待任务列表(.OSEventTbl[]和.OSEventGrp)。
和前面的所以函数一样,该函数也是先检查事件控制是否是邮箱[L6.18(1)]。然后,将邮箱中的等待任务列表[L6.18(2)]和邮箱中的消息[L6.18(3)]从OS_EVENT数据结构复制到OS_MBOX_DATA数据结构。
程序清单L6.18查询邮箱的状态
INT8UOSMboxQuery(OS_EVENT*pevent,OS_MBOX_DATA*pdata)
{
INT8Ui;
INT8U*psrc;
INT8U*pdest;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
pdata->OSEventGrp=pevent->OSEventGrp;(2)
psrc=&pevent->OSEventTbl[0];
pdest=&pdata->OSEventTbl[0];
for(i=0;i
*pdest++=*psrc++;
}
pdata->OSMsg=pevent->OSEventPtr;(3)
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
6.7.6 用邮箱作二值信号量
一个邮箱可以被用作二值的信号量。 首先, 在初始化时, 将邮箱设置为一个非零的指针(如
void*1)。这样,一个任务可以调用OSMboxPend()函数来请求一个信号量,然后通过调用
OSMboxPost()函数来释放一个信号量。程序清单L6.19说明了这个过程是如何工作的。如果用
户只需要二值信号量和邮箱,这样做可以节省代码空间。这时可以将OS_SEM_EN设置为0,只使
用邮箱就可以了。
程序清单L6.19使用邮箱作为二值信号量
OS_EVENT*MboxSem;
voIDTask1(void*pdata)
{
INT8Uerr;
for(;;){
OSMboxPend(MboxSem,0,&err);/*获得对资源的访问权*/
.
./*任务获得信号量,对资源进行访问*/
.
OSMboxPost(MboxSem,(void*)1);/*释放对资源的访问权*/
}
}
6.7.7 用邮箱实现延时,而不使用OSTimeDly()
邮箱的等待超时功能可以被用来模仿OSTimeDly()函数的延时,如程序清单L6.20所示。
如果在指定的时间段TIMEOUT内,没有消息到来,Task1()函数将继续执行。这和OSTimeDly(TIMEOUT)功能很相似。但是,如果Task2()在指定的时间结束之前,向该邮箱发送了一个“哑”消息,Task1()就会提前开始继续执行。这和调用OSTimeDlyResume()函数的功能是一样的。注意,这里忽略了对返回的消息的检查,因为此时关心的不是得到了什么样的消息。
程序清单L6.20使用邮箱实现延时
OS_EVENT*MboxTimeDly;
voidTask1(void*pdata)
{
INT8Uerr;
for(;;){
OSMboxPend(MboxTimeDly, TIMEOUT,&err);/*延时该任务*/
.
./*延时结束后执行的代码*/
.
}
}
voidTask2(void*pdata)
{
INT8Uerr;
for(;;){
OSMboxPost(MboxTimeDly,(void*)1);/*取消任务1的延时*/
.
.
}
}
6.8 消息队列
消息队列是μC/OS-II中另一种通讯机制, 它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。因具体的应用有所不同,每个指针指向的数据结构变量也有所不同。为了使用μC/OS-II的消息队列功能,需要在OS_CFG.H文件中,将OS_Q_EN常数设置为1,并且通过常数OS_MAX_QS来决定μC/OS-II支持的最多消息队列数。
在使用一个消息队列之前, 必须先建立该消息队列。 这可以通过调用OSQCreate()函数 (见
6.07.01节),并定义消息队列中的单元数(消息数)来完成。
μC/OS-II提供了7个对消息队列进行操作的函数:OSQCreate(),OSQPend(),OSQPost(),
OSQPostFront(),OSQAccept(),OSQFlush()和OSQQuery()函数。图F6.7是任务、中断服务子程序和消息队列之间的关系。其中,消息队列的符号很像多个邮箱。实际上,我们可以将消息队列看作时多个邮箱组成的数组,只是它们共用一个等待任务列表。每个指针所指向的数据结构是由具体的应用程序决定的。N代表了消息队列中的总单元数。当调用OSQPend()或者OSQAccept()之前,调用N次OSQPost()或者OSQPostFront()就会把消息队列填满。从图F6.7中可以看出,一个任务或者中断服务子程序可以调用OSQPost(),OSQPostFront(),OSQFlush()或者OSQAccept()函数。但是,只有任务可以调用OSQPend()和OSQQuery()函数。
图F6.7任务、中断服务子程序和消息队列之间的关系——Figure6.7
图F6.8是实现消息队列所需要的各种数据结构。这里也需要事件控制块来记录等待任务列表[F6.8(1)],而且,事件控制块可以使多个消息队列的操作和信号量操作、邮箱操作相同的代码。当建立了一个消息队列时,一个队列控制块(OS_Q结构,见OS_Q.C文件)也同时被建立,并通过OS_EVENT中的.OSEventPtr域链接到对应的事件控制块[F6.8(2)]。 在建立一个消息队列之前,必须先定义一个含有与消息队列最大消息数相同个数的指针数组[F6.8(3)]。数组的起始地址以及数组中的元素数作为参数传递给OSQCreate()函数。事实上,如果内存占用了连续的地址空间,也没有必要非得使用指针数组结构。
文件OS_CFG.H中的常数OS_MAX_QS定义了在μC/OS-II中可以使用的最大消息队列数,这个值最小应为2。μC/OS-II在初始化时建立一个空闲的队列控制块链表,如图F6.9所示。
图F6.8用于消息队列的数据结构——Figure6.8
图F6.9空闲队列控制块链表——Figure6.9
队列控制块是一个用于维护消息队列信息的数据结构,它包含了以下的一些域。这里,仍然在各个变量前加入一个[.]来表示它们是数据结构中的一个域。
.OSQPtr在空闲队列控制块中链接所有的队列控制块。一旦建立了消息队列,该域就不再有用了。
.OSQStart是指向消息队列的指针数组的起始地址的指针。用户应用程序在使用消息队列之前必须先定义该数组。
.OSQEnd是指向消息队列结束单元的下一个地址的指针。该指针使得消息队列构成一个循环的缓冲区。
.OSQIn 是指向消息队列中插入下一条消息的位置的指针。当.OSQIn和.OSQEnd相等时,.OSQIn被调整指向消息队列的起始单元。
.OSQOut 是指向消息队列中下一个取出消息的位置的指针。当.OSQOut和.OSQEnd相等时,.OSQOut被调整指向消息队列的起始单元。
.OSQSize 是消息队列中总的单元数。该值是在建立消息队列时由用户应用程序决定的。在μC/OS-II中,该值最大可以是65,535。
.OSQEntries 是消息队列中当前的消息数量。当消息队列是空的时,该值为0。当消息队列满了以后,该值和.OSQSize值一样。在消息队列刚刚建立时,该值为0。
消息队列最根本的部分是一个循环缓冲区,如图F6.10。其中的每个单元包含一个指针。
队列未满时,.OSQIn[F6.10(1)]指向下一个存放消息的地址单元。如果队列已满(.OSQEntries
与.OSQSize相等),.OSQIn[F6.10(3)]则与.OSQOut指向同一单元。如果在.OSQIn指向的单元
插入新的指向消息的指针,就构成 FIFO(First-In-First-Out)队列。相反,如果在.OSQOut
指向的单元的下一个单元插入新的指针,就构成LIFO队列(Last-In-First-Out)[F6.10(2)]。
当.OSQEntries和.OSQSize相等时,说明队列已满。消息指针总是从.OSQOut[F6.10(4)]指向
的单元取出。指针.OSQStart和.OSQEnd [F6.10(5)]定义了消息指针数组的头尾,以便在.OSQIn
和.OSQOut到达队列的边缘时,进行边界检查和必要的指针调整,实现循环功能。
图F6.10消息队列是一个由指针组成的循环缓冲区——Figure6.10
6.8.1 建立一个消息队列,OSQCreate()
程序清单L6.21是OSQCreate()函数的源代码。该函数需要一个指针数组来容纳指向各个消息的指针。该指针数组必须声名为void类型。
OSQCreate()首先从空闲事件控制块链表中取得一个事件控制块(见图F6.3)[L6.21(1)],并对剩下的空闲事件控制块列表的指针做相应的调整,使它指向下一个空闲事件控制块[L6.21(2)]。 接着, OSQCreate()函数从空闲队列控制块列表中取出一个队列控制块[L6.21(3)]。
如果有空闲队列控制块是可以的,就对其进行初始化[L6.21(4)]。然后该函数将事件控制块的类型设置为OS_EVENT_TYPE_Q[L6.21(5)],并使其.OSEventPtr指针指向队列控制块[L6.21(6)]。OSQCreate()还要调用OSEventWaitListInit()函数对事件控制块的等待任务列表初始化[见6.01节,初始化一个事件控制块,OSEventWaitListInit()][L6.21(7)]。因为此时消息队列正在初始化,显然它的等待任务列表是空的。最后,OSQCreate()向它的调用函数返回一个指向事件控制块的指针[L6.21(9)]。该指针将在调用OSQPend(),OSQPost(),OSQPostFront(),OSQFlush(),OSQAccept()和OSQQuery()等消息队列处理函数时使用。因此,该指针可以被看作是对应消息队列的句柄。值得注意的是,如果此时没有空闲的事件控制块,OSQCreate()函数将返回一个NULL指针。如果没有队列控制块可以使用,为了不浪费事件控制块资源,OSQCreate()函数将把刚刚取得的事件控制块重新返还给空闲事件控制块列表[L6.21(8)]。
另外,消息队列一旦建立就不能再删除了。试想,如果有任务正在等待某个消息队列中的消息,而此时又删除该消息队列,将是很危险的。
程序清单L6.21建立一个消息队列
OS_EVENT*OSQCreate(void**start,INT16Usize)
{
OS_EVENT*pevent;
OS_Q*pq;
OS_ENTER_CRITICAL();
pevent=OSEventFreeList;(1)
if(OSEventFreeList!=(OS_EVENT*)0){
OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr;(2)
}
OS_EXIT_CRITICAL();
if(pevent!=(OS_EVENT*)0){
OS_ENTER_CRITICAL();
pq=OSQFreeList;(3)
if(OSQFreeList!=(OS_Q*)0){
OSQFreeList=OSQFreeList->OSQPtr;
}
OS_EXIT_CRITICAL();
if(pq!=(OS_Q*)0){
pq->OSQStart=start;(4)
pq->OSQEnd=&start[size];
pq->OSQIn=start;
pq->OSQOut=start;
pq->OSQSize=size;
pq->OSQEntries=0;
pevent->OSEventType=OS_EVENT_TYPE_Q;(5)
pevent->OSEventPtr=pq;(6)
OSEventWaitListInit(pevent);(7)
}else{
OS_ENTER_CRITICAL();
pevent->OSEventPtr=(void*)OSEventFreeList;(8)
OSEventFreeList=pevent;
OS_EXIT_CRITICAL();
pevent=(OS_EVENT*)0;
}
}
return(pevent);(9)
}
6.8.2 等待一个消息队列中的消息,OSQPend()
程序清单L6.22是OSQPend()函数的源代码。OSQPend()函数首先检查事件控制块是否是由OSQCreate()函数建立的[L6.22(1)],接着,该函数检查消息队列中是否有消息可用(即.OSQEntries是否大于0) [L6.22(2)]。 如果有, OSQPend()函数将指向消息的指针复制到msg变量中, 并让.OSQOut指针指向队列中的下一个单元[L6.22(3)],然后将队列中的有效消息数减1[L6.22(4)]。因为消息队列是一个循环的缓冲区,OSQPend()函数需要检查.OSQOut是否超过了
队列中的最后一个单元[L6.22(5)]。当发生这种越界时,就要将.OSQOut重新调整到指向队列的起始单元[L6.22(6)]。这是我们调用OSQPend()函数时所期望的,也是执行OSQPend()函数最快的路径。
程序清单L6.22在一个消息队列中等待一条消息
void*OSQPend(OS_EVENT*pevent,INT16Utimeout,INT8U*err)
{
void*msg;
OS_Q*pq;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)
OS_EXIT_CRITICAL();
*err=OS_ERR_EVENT_TYPE;
return((void*)0);
}
pq=pevent->OSEventPtr;
if(pq->OSQEntries!=0){(2)
msg=*pq->OSQOut++;(3)
pq->OSQEntries--;(4)
if(pq->OSQOut==pq->OSQEnd){(5)
pq->OSQOut=pq->OSQStart;(6)
}
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}elseif(OSIntNesting>0){(7)
OS_EXIT_CRITICAL();
*err=OS_ERR_PEND_ISR;
}else{
OSTCBCur->OSTCBStat|=OS_STAT_Q;(8)
OSTCBCur->OSTCBDly=timeout;
OSEventTaskWait(pevent);
OS_EXIT_CRITICAL();
OSSched();(9)
OS_ENTER_CRITICAL();
if((msg=OSTCBCur->OSTCBMsg)!=(void*)0){(10)
OSTCBCur->OSTCBMsg=(void*)0;
OSTCBCur->OSTCBStat=OS_STAT_RDY;
OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(11)
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}elseif(OSTCBCur->OSTCBStat&OS_STAT_Q){(12)
OSEventTO(pevent);(13)
OS_EXIT_CRITICAL();
msg=(void*)0;(14)
*err=OS_TIMEOUT;
}else{
msg=*pq->OSQOut++;(15)
pq->OSQEntries--;
if(pq->OSQOut==pq->OSQEnd){
pq->OSQOut=pq->OSQStart;
}
OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(16)
OS_EXIT_CRITICAL();
*err=OS_NO_ERR;
}
}
return(msg);(17)
}
如果这时消息队列中没有消息(.OSEventEntries是0),OSQPend()函数检查它的调用者是否是中断服务子程序[L6.22(7)]。象OSSemPend()和OSMboxPend()函数一样,不能在中断服务子程序中调用OSQPend(), 因为中断服务子程序是不能等待的。 但是, 如果消息队列中有消息,即使从中断服务子程序中调用OSQPend()函数,也一样是成功的。
如果消息队列中没有消息,调用OSQPend()函数的任务被挂起[L6.22(8)]。当有其它的任
务向该消息队列发送了消息或者等待时间超时,并且该任务成为最高优先级任务时,OSSched()
返回[L6.22(9)]。这时,OSQPend()要检查是否有消息被放到该任务的任务控制块中[L6.22(10)]。如果有,那么该次函数调用成功,把任务的任务控制块中指向消息队列的指针删除[L6.22(17)],并将对应的消息被返回到调用函数[L6.22(17)]。
在OSQPend()函数中,通过检查任务的任务控制块中的.OSTCBStat域,可以知道是否等到时间超时。如果其对应的OS_STAT_Q位被置1,说明任务等待已经超时[L6.22(12)]。这时,通过调用函数OSEventTo()可以将任务从消息队列的等待任务列表中删除[L6.22(13)]。这时,因为消息队列中没有消息,所以返回的指针是NULL[L6.22(14)]。
如果任务控制块标志位中的OS_STAT_Q位没有被置1,说明有任务发出了一条消息。
OSQPend()函数从队列中取出该消息[L6.22(15)]。然后,将任务的任务控制中指向事件控制块的指针删除[L6.22(16)]。
6.8.3 向消息队列发送一个消息(FIFO),OSQPost()
程序清单L6.23是OSQPost()函数的源代码。在确认事件控制块是消息队列后
[L6.23(1)],OSQPost()函数检查是否有任务在等待该消息队列中的消息[L6.23(2)]。当事件控