小盒子的小盒

UC/OS-II-6.1

0
阅读(2649)

程序清单 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);
 
        }
 
    }
 
}
 

 
6.1.1        无等待地从邮箱中得到一个消息, 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.1.2        查询一个邮箱的状态, 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)
 
{
 
    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);
 
}
 
 
6.1.3        用邮箱作二值信号量
一个邮箱可以被用作二值的信号量。首先,在初始化时,将邮箱设置为一个非零的指针(如 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);  /* 释放对资源的访问权 */
 
    }
 
}
 
 
6.1.4        用邮箱实现延时,而不使用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的延时 */
 
        .
 
        .
 
    }
 
}
 
 
6.2        消息队列
消息队列是µ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 任务、中断服务子程序和消息队列之间的关系——Figure 6.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 用于消息队列的数据结构——Figure 6.8
 

图F6.9 空闲队列控制块链表——Figure 6.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 消息队列是一个由指针组成的循环缓冲区——Figure 6.10
 
6.2.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, INT16U size)
 
{
 
    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.2.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, INT16U timeout, 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;
 
    } else if (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;
 
        } else if (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.2.3        向消息队列发送一个消息(FIFO),OSQPost()
程序清单 L6.23是OSQPost()函数的源代码。在确认事件控制块是消息队列后 [L6.23(1)],OSQPost()函数检查是否有任务在等待该消息队列中的消息[L6.23(2)]。当事件控制块的.OSEventGrp域为非0值时,说明该消息队列的等待任务列表中有任务。这时,调用OSEventTaskRdy()函数 [见6.02节,使一个任务进入就绪状态,OSEventTaskRdy()]从列表中取出最高优先级的任务[L6.23(3)],并将它置于就绪状态。然后调用函数OSSched() [L6.23(4)]进行任务的调度。如果上面取出的任务的优先级在整个系统就绪的任务里也是最高的,而且OSQPost()函数不是中断服务子程序调用的,就执行任务切换,该最高优先级任务被执行。否则的话,OSSched()函数直接返回,调用OSQPost()函数的任务继续执行。
 
程序清单 L6.23 向消息队列发送一条消息
 
INT8U OSQPost (OS_EVENT *pevent, void *msg)
 
{
 
    OS_Q   *pq;
 
 
 
 
 
    OS_ENTER_CRITICAL();
 
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {                      (1)
 
        OS_EXIT_CRITICAL();
 
        return (OS_ERR_EVENT_TYPE);
 
    }
 
    if (pevent->OSEventGrp) {                                          (2)
 
        OSEventTaskRdy(pevent, msg, OS_STAT_Q);                        (3)
 
        OS_EXIT_CRITICAL();
 
        OSSched();                                                           (4)
 
        return (OS_NO_ERR);
 
    } else {
 
        pq = pevent->OSEventPtr;
 
        if (pq->OSQEntries >= pq->OSQSize) {                           (5)
 
            OS_EXIT_CRITICAL();
 
            return (OS_Q_FULL);
 
        } else {
 
            *pq->OSQIn++ = msg;                                       (6)
 
            pq->OSQEntries++;
 
            if (pq->OSQIn == pq->OSQEnd) {
 
                pq->OSQIn = pq->OSQStart;
 
            }
 
            OS_EXIT_CRITICAL();
 
        }
 
        return (OS_NO_ERR);
 
    }
 
}
 
 
如果没有任务等待该消息队列中的消息,而且此时消息队列未满[L6.23(5)],指向该消息的指针被插入到消息队列中[L6.23(6)]。这样,下一个调用OSQPend()函数的任务就可以马上得到该消息。注意,如果此时消息队列已满,那么该消息将由于不能插入到消息队列中而丢失。
此外,如果OSQPost()函数是由中断服务子程序调用的,那么即使产生了更高优先级的任务,也不会在调用OSSched()函数时发生任务切换。这个动作一直要等到中断嵌套的最外层中断服务子程序调用OSIntExit()函数时才能进行(见3.09节,µC/OS-II中的中断)。
6.2.4        向消息队列发送一个消息(后进先出LIFO),OSQPostFront()
OSQPostFront()函数和OSQPost()基本上是一样的,只是在插入新的消息到消息队列中时,使用.OSQOut作为指向下一个插入消息的单元的指针,而不是.OSQIn。程序清单 L6.24是它的源代码。值得注意的是,.OSQOut指针指向的是已经插入了消息指针的单元,所以再插入新的消息指针前,必须先将.OSQOut指针在消息队列中前移一个单元。如果.OSQOut指针指向的当前单元是队列中的第一个单元[L6.24(1)],这时再前移就会发生越界,需要特别地将该指针指向队列的末尾[L6.24(2)]。由于.OSQEnd指向的是消息队列中最后一个单元的下一个单元,因此.OSQOut必须被调整到指向队列的有效范围内[L6.24(3)]。因为QSQPend()函数取出的消息是由OSQPend()函数刚刚插入的,因此OSQPostFront()函数实现了一个LIFO队列。
 
程序清单 L6.24 向消息队列发送一条消息(LIFO)
 
INT8U OSQPostFront (OS_EVENT *pevent, void *msg)
 
{
 
    OS_Q   *pq;
 
 
 
 
 
    OS_ENTER_CRITICAL();
 
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {
 
        OS_EXIT_CRITICAL();
 
        return (OS_ERR_EVENT_TYPE);
 
    }
 
    if (pevent->OSEventGrp) {
 
        OSEventTaskRdy(pevent, msg, OS_STAT_Q);
 
        OS_EXIT_CRITICAL();
 
        OSSched();
 
        return (OS_NO_ERR);
 
    } else {
 
        pq = pevent->OSEventPtr;
 
        if (pq->OSQEntries >= pq->OSQSize) {
 
            OS_EXIT_CRITICAL();
 
            return (OS_Q_FULL);
 
        } else {
 
            if (pq->OSQOut == pq->OSQStart) {                          (1)
 
                pq->OSQOut = pq->OSQEnd;                               (2)
 
            }
 
            pq->OSQOut--;                                              (3)
 
            *pq->OSQOut = msg;
 
            pq->OSQEntries++;
 
            OS_EXIT_CRITICAL();
 
        }
 
        return (OS_NO_ERR);
 
    }
 
}
 
 
6.2.5        无等待地从一个消息队列中取得消息, OSQAccept()
如果试图从消息队列中取出一条消息,而此时消息队列又为空时,也可以不让调用任务等待而直接返回调用函数。这个操作可以调用OSQAccept()函数来完成。程序清单 L6.25是该函数的源代码。OSQAccept()函数首先查看pevent指向的事件控制块是否是由OSQCreate()函数建立的[L6.25(1)],然后它检查当前消息队列中是否有消息[L6.25(2)]。如果消息队列中有至少一条消息,那么就从.OSQOut指向的单元中取出消息[L6.25(3)]。OSQAccept()函数的调用函数需要对OSQAccept()返回的指针进行检查。如果该指针是NULL值,说明消息队列是空的,其中没有消息可以 [L6.25(4)]。否则的话,说明已经从消息队列中成功地取得了一条消息。当中断服务子程序要从消息队列中取消息时,必须使用OSQAccept()函数,而不能使用OSQPend()函数。
 
程序清单 L6.25 无等待地从消息队列中取一条消息
 
void *OSQAccept (OS_EVENT *pevent)
 
{
 
    void  *msg;
 
    OS_Q  *pq;
 
 
 
 
 
    OS_ENTER_CRITICAL();
 
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {                      (1)
 
        OS_EXIT_CRITICAL();
 
        return ((void *)0);
 
    }
 
    pq = pevent->OSEventPtr;
 
    if (pq->OSQEntries != 0) {                                         (2)
 
        msg = *pq->OSQOut++;                                           (3)
 
        pq->OSQEntries--;
 
        if (pq->OSQOut == pq->OSQEnd) {
 
            pq->OSQOut = pq->OSQStart;
 
        }
 
    } else {
 
        msg = (void *)0;                                               (4)
 
    }
 
    OS_EXIT_CRITICAL();
 
    return (msg);
 
}
 
 
6.2.6        清空一个消息队列, OSQFlush()
OSQFlush()函数允许用户删除一个消息队列中的所有消息,重新开始使用。程序清单 L6.26是该函数的源代码。和前面的其它函数一样,该函数首先检查pevent指针是否是执行一个消息队列[L6.26(1)],然后将队列的插入指针和取出指针复位,使它们都指向队列起始单元,同时,将队列中的消息数设为0 [L6.26(2)]。这里,没有检查该消息队列的等待任务列表是否为空,因为只要该等待任务列表不空,.OSQEntries就一定是0。唯一不同的是,指针.OSQIn和.OSQOut此时可以指向消息队列中的任何单元,不一定是起始单元。
 
程序清单 L6.26 清空消息队列
 
INT8U OSQFlush (OS_EVENT *pevent)
 
{
 
    OS_Q  *pq;
 
 
 
 
 
    OS_ENTER_CRITICAL();
 
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {                      (1)
 
        OS_EXIT_CRITICAL();
 
        return (OS_ERR_EVENT_TYPE);
 
    }
 
    pq             = pevent->OSEventPtr;
 
    pq->OSQIn      = pq->OSQStart;                                     (2)
 
    pq->OSQOut     = pq->OSQStart;
 
    pq->OSQEntries = 0;
 
    OS_EXIT_CRITICAL();
 
    return (OS_NO_ERR);
 
}
 
 
6.2.7        查询一个消息队列的状态,OSQQuery()
OSQQuery()函数使用户可以查询一个消息队列的当前状态。程序清单 L6.27是该函数的源代码。OSQQuery()需要两个参数:一个是指向消息队列的指针pevent。它是在建立一个消息队列时,由OSQCreate()函数返回的;另一个是指向OS_Q_DATA(见uCOS_II.H)数据结构的指针pdata。该结构包含了有关消息队列的信息。在调用OSQQuery()函数之前,必须先定义该数据结构变量。OS_Q_DATA结构包含下面的几个域:
.OSMsg 如果消息队列中有消息,它包含指针.OSQOut所指向的队列单元中的内容。如果队列是空的,.OSMsg包含一个NULL指针。
.OSNMsgs是消息队列中的消息数(.OSQEntries的拷贝)。
.OSQSize是消息队列的总的容量
.OSEventTbl[]和.OSEventGrp是消息队列的等待任务列表。通过它们, OSQQuery()的调用函数可以得到等待该消息队列中的消息的任务总数。
OSQQuery()函数首先检查pevent指针指向的事件控制块是一个消息队列[L6.27(1)],然后复制等待任务列表[L6.27(2)]。如果消息队列中有消息[L6.27(3)],.OSQOut指向的队列单元中的内容被复制到OS_Q_DATA结构中[L6.27(4)],否则的话,就复制一个NULL指针[L6.27(5)]。最后,复制消息队列中的消息数和消息队列的容量大小[L6.27(6)]。
 
程序清单 L6.27 程序消息队列的状态
 
INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata)
 
{
 
    OS_Q   *pq;
 
    INT8U   i;
 
    INT8U  *psrc;
 
    INT8U  *pdest;
 
 
 
 
 
    OS_ENTER_CRITICAL();
 
    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {                      (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++;
 
    }
 
    pq = (OS_Q *)pevent->OSEventPtr;
 
    if (pq->OSQEntries > 0) {                                          (3)
 
        pdata->OSMsg = pq->OSQOut;                                     (4)
 
    } else {
 
        pdata->OSMsg = (void *)0;                                      (5)
 
    }
 
    pdata->OSNMsgs = pq->OSQEntries;                                   (6)
 
    pdata->OSQSize = pq->OSQSize;
 
    OS_EXIT_CRITICAL();
 
    return (OS_NO_ERR);
 
}
 
 
6.2.8        使用消息队列读取模拟量的值
在控制系统中,经常要频繁地读取模拟量的值。这时,可以先建立一个定时任务OSTimeDly() [见5.00节,延时一个任务,OSTimeDly()],并且给出希望的抽样周期。然后,如图 F6.11所示,让A/D采样的任务从一个消息队列中等待消息。该程序最长的等待时间就是抽样周期。当没有其它任务向该消息队列中发送消息时,A/D采样任务因为等待超时而退出等待状态并进行执行。这就模仿了OSTimeDly()函数的功能。
也许,读者会提出疑问,既然OSTimeDly()函数能完成这项工作,为什么还要使用消息队列呢?这是因为,借助消息队列,我们可以让其它的任务向消息队列发送消息来终止A/D采样任务等待消息,使其马上执行一次A/D采样。此外,我们还可以通过消息队列来通知A/D采样程序具体对哪个通道进行采样,告诉它增加采样频率等等,从而使得我们的应用更智能化。换句话说,我们可以告诉A/D采样程序,“现在马上读取通道3的输入值!”之后,该采样任务将重新开始在消息队列中等待消息,准备开始一次新的扫描过程。
 
图 F6.11 读模拟量输入——Figure 6.11
 
6.2.9        使用一个消息队列作为计数信号量
在消息队列初始化时,可以将消息队列中的多个指针设为非NULL值(如void* 1),来实现计数信号量的功能。这里,初始化为非NULL值的指针数就是可用的资源数。系统中的任务可以通过OSQPend()来请求“信号量”,然后通过调用OSQPost()来释放“信号量”,如程序清单 L6.28。如果系统中只使用了计数信号量和消息队列,使用这种方法可以有效地节省代码空间。这时将OS_SEM_EN设为0,就可以不使用信号量,而只使用消息队列。值得注意的是,这种方法为共享资源引入了大量的指针变量。也就是说,为了节省代码空间,牺牲了RAM空间。另外,对消息队列的操作要比对信号量的操作慢,因此,当用计数信号量同步的信号量很多时,这种方法的效率是非常低的。
 
程序清单 L6.28 使用消息队列作为一个计数信号量
 
OS_EVENT *QSem;
 
void     *QMsgTbl[N_RESOURCES]
 
 
 
 
 
void main (void)
 
{
 
    OSInit();
 
    .
 
    .
 
    QSem = OSQCreate(&QMsgTbl[0], N_RESOURCES);
 
    for (i = 0; i < N_RESOURCES; i++) {
 
        OSQPost(Qsem, (void *)1);
 
    }
 
    .
 
    .
 
    OSTaskCreate(Task1, .., .., ..);
 
    .
 
    .
 
    OSStart();
 
}
 
 
 
 
 
void Task1 (void *pdata)
 
{
 
    INT8U err;
 
 
 
 
 
    for (;;) {
 
        OSQPend(&QSem, 0, &err);         /* 得到对资源的访问权  */
 
        .
 
        .    /* 任务获得信号量,对资源进行访问  */
 
        .
 
        OSMQPost(QSem, (void*)1);       /* 释放对资源的访问权 */
 
    }
 
}