ucos实时操作系统学习笔记——任务间通信(队列)

      ucos操作系统中的queue机制同样使用了event机制来实现,其实和前面的sem,mutex实现类似,所不同的是对sem而言,任务想获得信号量,对mutex而言,任务想获得的是互斥锁。任务间通信的queue机制则是想获得在queue中的消息,通过队列先进先出的形式存放消息。其实queue中存放的是放消息的内存的地址,通过读取地址可以获得消息的内容。

      queue机制是有一段循环使用的内存来存放增加的消息,然后从这段内存中读取消息的一个过程。有专门的操作系统queue结构(OS_Q)来描述这段内存。系统中OS_Q的个数也是有限的,在创建queue时,每一个OS_Q和event是一一对应的。OS_Q的结构体代码如下所示:

typedef struct os_q {                   /* QUEUE CONTROL BLOCK                                         */
    struct os_q   *OSQPtr;              /* Link to next queue control block in list of free blocks     */
    void         **OSQStart;            /* Pointer to start of queue data                              */
    void         **OSQEnd;              /* Pointer to end   of queue data                              */
    void         **OSQIn;               /* Pointer to where next message will be inserted  in   the Q  */
    void         **OSQOut;              /* Pointer to where next message will be extracted from the Q  */
    INT16U         OSQSize;             /* Size of queue (maximum number of entries)                   */
    INT16U         OSQEntries;          /* Current number of entries in the queue                      */
} OS_Q;

      结构体中第1个参数是一个os_q指针,指向下一个空闲的os_q;第2和第3个参数是描述queue内存地址开始和结束的二维指针;第4和第5个参数是描述queue内存地址中消息放入和取出的地址的指针,第6个参数是这个queue的大小;第7个参数则是表示有多少个消息实体在这个queue里;这些中OSQStart,OSQEnd,OSQIn和OSQOut都是二维指针,其中放的都是一些信息的地址,这些信息可以在不同的地方创建,地址可以不连续。

      使用语言描述一下这个结构体具体实现的是一个什么东东,OSQStart和OSQEnd唯一确定一个OSQSize大小的queue;OSQIn在创建queue之初的初始化过程中指向的位置是OSQStart,每次当有新的信息加入到queue中时,OSQIn会向OSQEnd方向地址加一操作,同时OSQEntries会加一操作,当OSQIn的地址值等于OSQEnd时,表示queue已经满了,需要从心开始OSQIn等于OSQStart;OSQOut同样也是指向OSQStart的位置,当有信息从queue中取出时,OSQOut同样会向OSQEnd方向加一操作,同时OSQEntries会减一操作。

      从queue的创建开始看其具体的实现机制是如何的,queue的创建时通过OSQCreate函数实现的,代码如下:

OS_EVENT  *OSQCreate (void **start, INT16U size)
{
    OS_EVENT  *pevent;
    OS_Q      *pq;

    if (OSIntNesting > 0) {                      /* See if called from ISR ...                         */
        return ((OS_EVENT *)0);                  /* ... can't CREATE from an ISR                       */
    }
    OS_ENTER_CRITICAL();
(1)========================================================================================================== pevent
= OSEventFreeList; /* Get next free event control block */ if (OSEventFreeList != (OS_EVENT *)0) { /* See if pool of free ECB pool was empty */ OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; } OS_EXIT_CRITICAL();
(2)=========================================================================================================
if (pevent != (OS_EVENT *)0) { /* See if we have an event control block */ OS_ENTER_CRITICAL(); pq = OSQFreeList; /* Get a free queue control block */ if (pq != (OS_Q *)0) { /* Were we able to get a queue control block ? */ OSQFreeList = OSQFreeList->OSQPtr; /* Yes, Adjust free list pointer to next free*/ OS_EXIT_CRITICAL(); pq->OSQStart = start; /* Initialize the queue */ pq->OSQEnd = &start[size]; pq->OSQIn = start; pq->OSQOut = start; pq->OSQSize = size; pq->OSQEntries = 0; pevent->OSEventType = OS_EVENT_TYPE_Q; pevent->OSEventCnt = 0; pevent->OSEventPtr = pq; OS_EventWaitListInit(pevent); /* Initalize the wait list */ } else { pevent->OSEventPtr = (void *)OSEventFreeList; /* No, Return event control block on error */ OSEventFreeList = pevent; OS_EXIT_CRITICAL(); pevent = (OS_EVENT *)0; } } return (pevent);
(3)======================================================================================================= }

      可以从代码中看出,queue创建的开始部分和sem,mutex相同,就是不要在中断中创建,第二部分也相同就是从空闲event中取一个空闲的event结构体。不同的是第三部分,sem,mutex没有专门的描述机制相关的结构体,所以说不需要对其进行初始化,只要对event结构初始化就可以,但是对于queue来说除了初始化event之外,还需要初始化queue结构,上面的代码已经很清晰的做了相关操作,结合struct os_q的参数介绍,可以很好的理解,最后queue的地址放在event的OSEventPtr指针中。

void  *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *perr)
{
    void      *pmsg;
    OS_Q      *pq;

    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {/* Validate event block type                          */
        *perr = OS_ERR_EVENT_TYPE;
        return ((void *)0);
    }
    if (OSIntNesting > 0) {                      /* See if called from ISR ...                         */
        *perr = OS_ERR_PEND_ISR;                 /* ... can't PEND from an ISR                         */
        return ((void *)0);
    }
    if (OSLockNesting > 0) {                     /* See if called with scheduler locked ...            */
        *perr = OS_ERR_PEND_LOCKED;              /* ... can't PEND when locked                         */
        return ((void *)0);
    }
    OS_ENTER_CRITICAL();
(1)==================================================================================================== pq
= (OS_Q *)pevent->OSEventPtr; /* Point at queue control block */ if (pq->OSQEntries > 0) { /* See if any messages in the queue */ pmsg = *pq->OSQOut++; /* Yes, extract oldest message from the queue */ pq->OSQEntries--; /* Update the number of entries in the queue */ if (pq->OSQOut == pq->OSQEnd) { /* Wrap OUT pointer if we are at the end of the queue */ pq->OSQOut = pq->OSQStart; } OS_EXIT_CRITICAL(); *perr = OS_ERR_NONE; return (pmsg); /* Return message received */ }
(2)===================================================================================================== OSTCBCur
->OSTCBStat |= OS_STAT_Q; /* Task will have to pend for a message to be posted */ OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; OSTCBCur->OSTCBDly = timeout; /* Load timeout into TCB */ OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */ OS_EXIT_CRITICAL(); OS_Sched(); /* Find next highest priority task ready to run */ OS_ENTER_CRITICAL(); switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */ case OS_STAT_PEND_OK: /* Extract message from TCB (Put there by QPost) */ pmsg = OSTCBCur->OSTCBMsg; *perr = OS_ERR_NONE; break; case OS_STAT_PEND_ABORT: pmsg = (void *)0; *perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */ break; case OS_STAT_PEND_TO: default: OS_EventTaskRemove(OSTCBCur, pevent); pmsg = (void *)0; *perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */ break; }+ OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */ OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */ OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */ OSTCBCur->OSTCBMsg = (void *)0; /* Clear received message */ OS_EXIT_CRITICAL(); return (pmsg); /* Return received message */
(3)========================================================================================================
}

      queue队列的pend函数是OSQPend,从函数的代码看,其与sem主要的不同在第二部分,而与mutex的不同主要是在第二和第三部分(因为mutex有优先级的继承操作)。pend操作的第二部分主要的实现是从event的OSEventPtr中取出queue的描述结构体os_q,然后通过OSQEntries是否大于0判断在当前的queue中是否有信息,如果有则从OSQOut中取出信息,并将OSQOut加操作,让其指向下一个信息地址,同时将queue中代表信息个数的OSQEntries剪操作;如果OSQOut已经达到queue的最后一个位置即OSQOut==OSQEnd,则循环开始从OSQOut=OSQStart,重新获取信息;如果queue队列中没有信息,则直接将当前调度pend的任务挂起,并且重新进行任务调度,当任务重新获得运行的时候表示已经获得了event中的queue信息,则重新将当前任务加到运行等待列表中,这是第三部分的内容。

INT8U  OSQPost (OS_EVENT *pevent, void *pmsg)
{
    OS_Q      *pq;

    if (pevent->OSEventType != OS_EVENT_TYPE_Q) {      /* Validate event block type                    */
        return (OS_ERR_EVENT_TYPE);
    }
    OS_ENTER_CRITICAL();
(1)====================================================================================================
if (pevent->OSEventGrp != 0) { /* See if any task pending on queue */ /* Ready highest priority task waiting on event */ (void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_Q, OS_STAT_PEND_OK); OS_EXIT_CRITICAL(); OS_Sched(); /* Find highest priority task ready to run */ return (OS_ERR_NONE); }
(2)==================================================================================================== pq
= (OS_Q *)pevent->OSEventPtr; /* Point to queue control block */ if (pq->OSQEntries >= pq->OSQSize) { /* Make sure queue is not full */ OS_EXIT_CRITICAL(); return (OS_ERR_Q_FULL); } *pq->OSQIn++ = pmsg; /* Insert message into queue */ pq->OSQEntries++; /* Update the nbr of entries in the queue */ if (pq->OSQIn == pq->OSQEnd) { /* Wrap IN ptr if we are at end of queue */ pq->OSQIn = pq->OSQStart; } OS_EXIT_CRITICAL(); return (OS_ERR_NONE);
(3)==================================================================================================== }

      queue的post函数是OSQPost,该函数的入参是event指针,和要加入queue的信息的地址pmsg,和sem以及mutex类似,queue首先判断的是event中是否有在等待queue信息的任务,如果有的话将该pmsg直接交给等待任务,从代码中可以找到任务TCB的结构体中有一个专门存放pmsg的变量OSTCBMsg,这样的话,任务会通过访问OSTCBMsg直接获得pmsg的信息;如果在event中没有等待信息的任务存在则会进入到第三部分,就是从event中取出描述queue的os_q结构体,然后判断当前的queue中存在的信息是否达到了queue的上限OSQSize,如果已经达到上限,则会返回queue满了的错误,如果没有的话,会将pmsg放到OSQIn中,并且将queue中表示信息个数的OSQEntries加操作,如果OSQIn已经达到queue的上限,则会循环从Queue开始的地方存放pmsg (OSQIn == OSQStart),其实OSQIn的范围和OSQOut的范围是一样的,OSQIn访问的内存地址,OSQOut必然会访问到。 在queue中如果有msg的存在的话,在event任务等待列表中就不会有任务在等待msg,这是和其他的机制相通的地方。

原文地址:https://www.cnblogs.com/MyLove-Summer/p/5196711.html