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 > ) { /* See if called from ISR ... */
return ((OS_EVENT *)); /* ... can't CREATE from an ISR */
}
OS_ENTER_CRITICAL();
(1)==========================================================================================================
pevent = OSEventFreeList; /* Get next free event control block */
if (OSEventFreeList != (OS_EVENT *)) { /* See if pool of free ECB pool was empty */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
(2)=========================================================================================================
if (pevent != (OS_EVENT *)) { /* See if we have an event control block */
OS_ENTER_CRITICAL();
pq = OSQFreeList; /* Get a free queue control block */
if (pq != (OS_Q *)) { /* 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 = ;
pevent->OSEventType = OS_EVENT_TYPE_Q;
pevent->OSEventCnt = ;
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 *);
}
}
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 *));
}
if (OSIntNesting > ) { /* See if called from ISR ... */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return ((void *));
}
if (OSLockNesting > ) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return ((void *));
}
OS_ENTER_CRITICAL();
(1)====================================================================================================
pq = (OS_Q *)pevent->OSEventPtr; /* Point at queue control block */
if (pq->OSQEntries > ) { /* 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 *);
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break; case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
pmsg = (void *);
*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 *); /* Clear event pointers */
OSTCBCur->OSTCBMsg = (void *); /* 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 != ) { /* 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,这是和其他的机制相通的地方。