消息队列是UCOS系统的一种通讯机制,它可以使任务向任务或者中断向任务发送一个指针变量.指针变量指向的数据结构由用户自定义,即我们常说的消息.首先来看一下UCOS消息队列结构体的设计.
本文使用的UCOS版本:V2.91.
消息队列结构体设计
消息队列结构体的本质设计是一个环形缓冲区.OSQStart,OSQEnd标识唤醒缓冲区的首尾边界. OSQIn,OSQOut在环形缓冲区内移动,标识当前队列内数据变化情况.
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) */
/* 消息队列中当前已存储的消息的数量. 消息入队时加1 出队时减1. * /
INT16U OSQEntries; /* Current number of entries in the queue */
} OS_Q;
消息队列创建
消息队列创建的传参需要一个指针数组来指向各个消息的指针.size表示创建消息的大小. OSQCreate函数的详解在代码内以注释的形式体现.
OS_EVENT *OSQCreate (void **start,
INT16U size)
{
OS_EVENT *pevent;
OS_Q *pq;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
// 中断中不允许创建消息队列.
if (OSIntNesting > 0u) { /* See if called from ISR ... */
return ((OS_EVENT *)0); /* ... can't CREATE from an ISR */
}
OS_ENTER_CRITICAL();
// 找到空闲可用的事件控制块.
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();
/*
找到一个可用的消息控制块.(OSQTbl[]数组中的空闲可用项)设置消息控制块的相关属性.
pevent->OSEventPtr = pq; // 通过事件控制块的OSEventPtr指针即可获取到整个消息控制块的属性.
OS_EventWaitListInit清除 OSEventGrp OSEventTbl值.
函数创建完成后, 我们得到了一个OS_EVENT *类型的事件控制块指针,通过这个事件控制块指针的OSEventPtr
指针变量可以访问到我们整个消息控制块.
*/
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 = 0u;
pevent->OSEventType = OS_EVENT_TYPE_Q;
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pq;
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
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);
}
请求消息队列
OSQPend函数的详解在代码内以注释的形式体现.
void *OSQPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
void *pmsg;
OS_Q *pq;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*perr = OS_ERR_PEVENT_NULL;
return ((void *)0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) {/* Validate event block type */
*perr = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
if (OSIntNesting > 0u) { /* See if called from ISR ... */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return ((void *)0);
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return ((void *)0);
}
OS_ENTER_CRITICAL();
/*
请求消息队列时, 可以通过pevent->OSEventPtr指针获取到消息队列指针.如果消息队列中存在消息.
就取出消息(获取消息首地址).并向后移动OSQOut. if (pq->OSQOut == pq->OSQEnd)是对环形缓冲区进行边界控制.
*/
pq = (OS_Q *)pevent->OSEventPtr; /* Point at queue control block */
if (pq->OSQEntries > 0u) { /* 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 */
}
/*
如果还没有消息,会运行到这里. 设置当前任务控制块的相关状态属性.
OS_EventTaskWait函数将当前任务的优先级放入此消息队列的结构体成员变量,
并将此任务的优先级从就绪表中移除.然后触发一次任务调度.CPU转到其他任务运行.
此任务即处于了挂起等待消息的状态.
*/
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 */
/*
当收到消息或超时时间到导致任务被放入就绪表, 且被调度器调度时,会从这里开始接着运行.
先检查pend到消息的原因, 如果是等待超时,就将此任务的优先级从信号量的等待事件中移除.
如果是正常收到消息. 则从OSTCBCur->OSTCBMsg取出消息.
*/
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 */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OSTCBCur->OSTCBMsg = (void *)0; /* Clear received message */
OS_EXIT_CRITICAL();
return (pmsg); /* Return received message */
}
消息队列发送
OSQPost函数用于一个任务或者中断服务子程序向另一个任务发送消息.OSQPost函数的详解在代码内以注释的形式体现.
INT8U OSQPost (OS_EVENT *pevent,
void *pmsg)
{
OS_Q *pq;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
/*
pevent->OSEventGrp非0表示有任务在等待接收消息, 此时调用OS_EventTaskRdy函数,找到等待此消息的那个任务的
优先级, 将此任务放入就绪表并从等待事件表中移除.然后触发一次任务调度.(如果等待消息的那个任务就绪表中
优先级最高的任务,就会立即运行.)
*/
if (pevent->OSEventGrp != 0u) { /* 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);
}
/*
如果在发送消息的时候.没有检测到有任务在等待消息. 就会运行到这里, 获取到消息指针pq,
首先进行缓冲区判满操作,若消息缓冲区不满, 就将消息指针放入OSQIn指向的位置并移动OSQIn的
位置,并累加消息计数值OSQEntries.if (pq->OSQIn == pq->OSQEnd)是对消息缓冲区进行边界控制.
*/
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);
}
问答环节
1.消息队列是怎样的传递消息的.
UCOS消息队列传递的是指向待传送数据的首地址的指针.UCOS的消息队列的消息传送并不产生内存拷贝.消息发送方组包好消息后,将发送消息的首地址放入消息队列.消息接收方从消息队列取出消息指针,按照约定的消息格式解析消息.
2.消息队列使用时有哪些注意事项.
因为UCOS消息队列是指针传递, 对于要传递的消息,可以是一块动态分配的内存.(注意这种情况需要处理好内存的分配释放操作,避免产生内存泄漏).也可以是全局变量/数据/自定义数据结构.或者是函数指针. 因为是指针形式传递,所以千万不能传递局部变量.如果消息内存是使用动态内存分配的话,需要做好内存管理.发送失败时在发送方释放内存, 发送成功时在接收方接收成功并处理消息后释放内存.避免产生内存泄漏.
版权声明:本文为w___dream原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。