再读 ucosII源码(邵贝贝):任务之间的通讯与同步--邮箱

邮箱简介:

邮箱是µ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()或OSMboxAccept(),但是只有任务可以调用函数OSMboxPend()和OSMboxQuery()。
这里写图片描述

建立一个邮箱,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()

程序清单 L6.15是OSMboxPend()函数的源代码。
同样,它和OSSemPend()也很相似,因此,在这里只讲述其中的不同之处。
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是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。
在调用OSMboxQuery()函数之前,必须先定义该结构变量(OS_MBOX_DATA),用来保存有关邮箱的信息。
之所以定义一个新的数据结构,是因为这里关心的只是和特定邮箱有关的内容,而非整个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)
{
    INT8U  i;
    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);
}

使用:

用邮箱作二值信号量

一个邮箱可以被用作二值的信号量。
首先,在初始化时,将邮箱设置为一个非零的指针(如 void *1)。
这样,一个任务可以调用OSMboxPend()函数来请求一个信号量,
然后通过调用OSMboxPost()函数来释放一个信号量。
程序清单 L6.19说明了这个过程是如何工作的。
如果用户只需要二值信号量和邮箱,这样做可以节省代码空间。这时可以将OS_SEM_EN设置为0,只使用邮箱就可以了。
程序清单 L6.19 使用邮箱作为二值信号量:

OS_EVENT *MboxSem;


void Task1 (void *pdata)
{
    INT8U err;

    for (;;) {
        OSMboxPend(MboxSem, 0, &err);   /* 获得对资源的访问权  */
        .
        .    /* 任务获得信号量,对资源进行访问  */
        .
        OSMboxPost(MboxSem, (void*)1);  /* 释放对资源的访问权 */
    }
}

用邮箱实现延时,而不使用OSTimeDly()

邮箱的等待超时功能可以被用来模仿OSTimeDly()函数的延时,如程序清单 L6.20所示。
如果在指定的时间段TIMEOUT内,没有消息到来,Task1()函数将继续执行。
这和OSTimeDly(TIMEOUT)功能很相似。
但是,如果Task2()在指定的时间结束之前,向该邮箱发送了一个“哑”消息,Task1()就会提前开始继续执行。
这和调用OSTimeDlyResume()函数的功能是一样的。注意,这里忽略了对返回的消息的检查,因为此时关心的不是得到了什么样的消息。
程序清单 L6.20 使用邮箱实现延时

OS_EVENT *MboxTimeDly;


void Task1 (void *pdata)
{
    INT8U err;


    for (;;) {
        OSMboxPend(MboxTimeDly, TIMEOUT, &err);   /* 延时该任务 */
        .
        .    /* 延时结束后执行的代码  */
        .
    }
}


void Task2 (void *pdata)
{
    INT8U err;


    for (;;) {
        OSMboxPost(MboxTimeDly, (void *)1);       /* 取消任务1的延时 */
        .
        .
    }
}

疑问:

1、在调用OSMboxCreate()函数之前,必须先定义该结构变量(OS_MBOX_DATA),用来保存有关邮箱的信息。

显然,没什么必然关系。是该书的笔误,应该如下:
在调用OSMboxQuery()函数之前,必须先定义该结构变量(OS_MBOX_DATA),用来保存有关邮箱的信息。

猜你喜欢

转载自blog.csdn.net/guozhongwei1/article/details/80155670