关注“嵌入式软件开发学习圈”免费获取更多学习教程
邮箱
邮箱是C/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构。为了在C/OS-II中使用邮箱,必须将OS_CFG.H中的OS_MBOX_EN常数置为1。
使用邮箱之前,必须先建立该邮箱。该操作可以通过调用OSMboxCreate()函数来完成,并且要指定指针的初始值。一般情况下,这个初始值是NULL,但也可以初始化一个邮箱,使其在最开始就包含一条消息。如果使用邮箱的目的是用来通知一个事件的发生(发送一条消息),那么就要初始化该邮箱为NULL,因为在开始时,事件还没有发生。如果用户用邮箱来共享某些资源,那么就要初始化该邮箱为一个非NULL的指针。在这种情况下,邮箱被当成一个二值信号量使用。
C/OS-II提供了5种对邮箱的操作:OSMboxCreate(),OSMboxPend(),OSMboxPost(),OSMboxAccept()和OSMboxQuery()函数。图F6.6描述了任务、中断服务子程序和邮箱之间的关系,这里用符号“I”表示邮箱。邮箱包含的内容是一个指向一条消息的指针。一个邮箱只能包含一个这样的指针(邮箱为满时),或者一个指向NULL的指针(邮箱为空时)。从图F6.6可
以看出,任务或者中断服务子程序可以调用函数OSMboxPost(),但是只有任务可以调用函数OSMboxPend()和OSMboxQuery()。
图F6.6任务、中断服务子程序和邮箱之间的关系
建立一个邮箱,OSMboxCreate()
程序清单L6.14是OSMboxCreate()函数的源代码,基本上和函数OSSemCreate()相似。不同之处在于事件控制块的类型被设置成OS_EVENT_TYPE_MBOX[L6.14(1)],以及使用.OSEventPtr域来容纳消息指针,而不是使用.OSEventCnt域[L6.14(2)]。
OSMboxCreate()函数的返回值是一个指向事件控制块的指针[L6.14(3)]。这个指针在调用函数OSMboxPend(),OSMboxPost(),OSMboxAccept()和OSMboxQuery()时使用。因此,该指针可以看作是对应邮箱的句柄。值得注意的是,如果系统中已经没有事件控制块可用,函数OSMboxCreate()将返回一个NULL指针。
邮箱一旦建立,是不能被删除的。比如,如果有任务正在等待一个邮箱的信息,这时删除该邮箱,将有可能产生灾难性的后果。
程序清单L6.14建立一个邮箱
| OS_EVENT *OSMboxCreate (void *msg)
| {
| OS_EVENT *pevent;
|
|
| OS_ENTER_CRITICAL();
| pevent = OSEventFreeList;
| if (OSEventFreeList != (OS_EVENT *)0) {
| OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
| }
| OS_EXIT_CRITICAL();
| if (pevent != (OS_EVENT *)0) {
| pevent->OSEventType = OS_EVENT_TYPE_MBOX;(1)
| pevent->OSEventPtr= msg;(2)
| OSEventWaitListInit(pevent);
| }
| return (pevent);(3)
| }
|
等待一个邮箱中的消息,OSMboxPend()
OSMboxPend()首先检查该事件控制块是由OSMboxCreate()函数建立的[L6.15(1)]。当.OSEventPtr域是一个非NULL的指针时,说明该邮箱中有可用的消息[L6.15(2)]。这种情况下,OSMboxPend()函数将该域的值复制到局部变量msg中,然后将.OSEventPtr置为NULL[L6.15(3)]。这正是我们所期望的,也是执行OSMboxPend()函数最快的路径。
如果此时邮箱中没有消息是可用的(.OSEventPtr域是NULL指针),OSMboxPend()函数检查它的调用者是否是中断服务子程序[L6.15(4)]。象OSSemPend()函数一样,不能在中断服务子程序中调用OSMboxPend(),因为中断服务子程序是不能等待的。这里的代码同样是为了以防万一。但是,如果邮箱中有可用的消息,即使从中断服务子程序中调用OSMboxPend()函数,也一样是成功的。
如果邮箱中没有可用的消息,OSMboxPend()的调用任务就被挂起,直到邮箱中有了消息或者等待超时[L6.15(5)]。当有其它的任务向该邮箱发送了消息后(或者等待时间超时),这时,该任务再一次成为最高优先级任务,OSSched()返回。这时,OSMboxPend()函数要检查是否有消息被放到该任务的任务控制块中[L6.15(6)]。如果有,那么该次函数调用成功,对应的消息被返回到调用函数。
程序清单L6.15等待一个邮箱中的消息
| void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
| {
| void*msg;
|
| OS_ENTER_CRITICAL();
| if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) {(1)
| OS_EXIT_CRITICAL();
| *err = OS_ERR_EVENT_TYPE;
| return ((void *)0);
| }
| msg = pevent->OSEventPtr;
| if (msg != (void *)0) {(2)
| pevent->OSEventPtr = (void *)0;(3)
| OS_EXIT_CRITICAL();
| *err = OS_NO_ERR;
| } else if (OSIntNesting > 0) {(4)
| OS_EXIT_CRITICAL();
| *err = OS_ERR_PEND_ISR;
| } else {
| OSTCBCur->OSTCBStat |= OS_STAT_MBOX;(5)
| OSTCBCur->OSTCBDly= timeout;
| OSEventTaskWait(pevent);
| OS_EXIT_CRITICAL();
| OSSched();
| OS_ENTER_CRITICAL();
| if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) {(6)
| OSTCBCur->OSTCBMsg= (void *)0;
| OSTCBCur->OSTCBStat= OS_STAT_RDY;
| OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
| OS_EXIT_CRITICAL();
| *err= OS_NO_ERR;
| } else if (OSTCBCur->OSTCBStat & OS_STAT_MBOX) {(7)
| OSEventTO(pevent);(8)
| OS_EXIT_CRITICAL();
| msg= (void *)0;(9)
| *err= OS_TIMEOUT;
| } else {
| msg= pevent->OSEventPtr;(10)
| pevent->OSEventPtr= (void *)0;(11)
| OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;(12)
| OS_EXIT_CRITICAL();
| *err= OS_NO_ERR;
| }
| }
| return (msg);
| }
|
在OSMboxPend()函数中,通过检查任务控制块中的.OSTCBStat域中的OS_STAT_MBOX位,可以知道是否等待超时。如果该域被置1,说明任务等待已经超时[L6.15(7)]。这时,通过调用函数OSEventTo()可以将任务从邮箱的等待列表中删除[L6.15(8)]。因为此时邮箱中没有消息,所以返回的指针是NULL[L6.15(9)]。如果OS_STAT_MBOX位没有被置1,说明所等待的消息已经被发出。OSMboxPend()的调用函数得到指向消息的指针[L6.15(10)]。此后,OSMboxPend()函数通过将邮箱事件控制块的.OSEventPtr域置为NULL清空该邮箱,并且要将任务任务控制块中指向邮箱事件控制块的指针删除[L6.15(12)]。
发送一个消息到邮箱中,OSMboxPost()
检查了事件控制块是否是一个邮箱后[L6.16(1)],OSMboxPost()函数还要检查是否有任务在等待该邮箱中的消息[L6.16(2)]。如果事件控制块中的OSEventGrp域包含非零值,就暗示着有任务在等待该消息。这时,调用OSEventTaskRdy()将其中的最高优先级任务从等待列表中删除[见6.02节,使一个任务进入就绪状态,OSEventTaskRdy()][L6.16(3)],加入系统的就绪任务列表中,准备运行。然后,调用OSSched()函数[L6.16(4)],检查该任务是否是系统中最高优先级的就绪任务。如果是,执行任务切换[仅当OSMboxPost()函数是由任务调用时],该任务得以执行。如果该任务不是最高优先级的任务,OSSched()返回,OSMboxPost()的调用函数继续执行。如果没有任何任务等待该消息,指向消息的指针就被保存到邮箱中[L6.16(6)](假设此时邮箱中的指针不是非NULL的[L6.16(5)])。这样,下一个调用OSMboxPend()函数的任务就可以立刻得到该消息了。
注意,如果OSMboxPost()函数是从中断服务子程序中调用的,那么,这时并不发生上下文的切换。如果需要,中断服务子程序引起的上下文切换只发生在中断嵌套的最外层中断服务子程序对OSIntExit()函数的调用时(见3.09节,C/OS-II中的中断)。
程序清单L6.16向邮箱中发送一条消息
| INT8U OSMboxPost (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);
| }
| }
| }
|
无等待地从邮箱中得到一个消息,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)
| }
|
查询一个邮箱的状态,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查询邮箱的状态
| INT8U OSMboxQuery (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 < OS_EVENT_TBL_SIZE; i++) {
| *pdest++ = *psrc++;
| }
| pdata->OSMsg= pevent->OSEventPtr;(3)
| OS_EXIT_CRITICAL();
| return (OS_NO_ERR);
| }
|
uCOS-II基础入门相关链接:
uCOS-II基础入门一介绍:实时系统概念、前后台系统、代码的临界段、任务切换。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(一)
uCOS-II基础入门二介绍:内核概述、调度含义、不可剥夺型内核、可剥夺内核、可重入性的含义。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(二)
uCOS-II基础入门三介绍:任务优先级、静态(动态)优先级、优先级反转、任务优先级分配。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(三)
uCOS-II基础入门四介绍:资源共享时,互斥条件包括:关中断、使用测试并置位指令、禁止做任务切换、利用信号量。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(四)
uCOS-II基础入门五介绍:死锁,同步,事件标记等概念。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(五)
uCOS-II基础入门六介绍:消息邮箱,消息队列,中断延迟,中断响应等概念。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(六)
uCOS-II基础入门七介绍:中断响应时间等概念。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(七)
uCOS-II基础入门八介绍:中断响应时间等概念。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(八)
uCOS-II基础入门九介绍:内核结构,任务等概念。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(九)
uCOS-II基础入门十介绍:任务控制块等概念。如需了解更多详情可以点击如下:
嵌入式学习之uCOS-II基础入门(十)
最近和大家分享嵌入式学习之uCOS-II基础入门学习资料。
获取如下uCOS-II基础入门学习资料,关注分享微信公众号“嵌入式软件开发学习圈”,回复“uCOS-II基础入门学习资料”即可。
关注分享微信公众号“嵌入式软件开发学习圈”,获取更多学习资料、以及资讯。你们的关注是我最大动力。
|