消息邮箱只能保存一条消息,消息队列没有这一局限,可以容纳多条信息队列,按照先进先出(FIFO)的原则发送和接受消息。
消息队列的实体不是操作系统提供的,而是由用户任务提供的。操作系统提供的是对其进行管理的程序。
1:消息队列的数据结构
消息队列的数据结构主要包括消息队列、消息队列控制块(QCB)、消息队列控制块数组、空闲链表、事件控制块(ECB)等。
消息队列的数据结构定义为一个指针数组,定义格式为void *MessQ[SIZE],SIZE是消息队列里消息的数量,指针数组的每个成员都是指针,指向
消息实体的地址。
消息队列控制块(QCB)是消息队列管理的核心数据结构。每个QCB管理一个消息队列,是消息队列实体存在的唯一依据。
typedef struct os_q {
struct os_q *OSQPtr; //指向下一个QCB,主要是用于空闲QCB链表的时候
void **OSQStart; //指向消息队列的首地址
void **OSQEnd; //指向消息队列的尾地址
void **OSQIn; //插入消息的地址
void **OSQOut; //取消息的地址
INT16U OSQSize; //队列的最大容量
INT16U OSQEntries; //队列中当前的容量
} OS_Q
QCB的实体在UCOSii.h中定义
OS_EXT OS_Q OSQTbl[OS_MAX_QS]; OS_MAX_QS系统默认为4 我们可以根据自己的需求进行重新配置
QCB没有被使用会连接在在一个单向链表,OSQWaitList指针指向该链表表头。
2:消息队列的管理
2.1:消息队列的初始化OS_QInit
我们若使用消息队列,系统初始化函数OS_Init会在进行系统初始化的时候,会调用OS_QInit函数对消息队列控制块进行初始化。
OS_QInit的主要功能跟信号量的初始化很类似。
这里就是将OS_Q OSQTbl[OS_MAX_QS]中的数组成员进行初始化设置,同时将这些QCB连接成一个单链表,并且OSQWaitList指针指向该链表表头。
2.2:建立消息队列OSQCreate
OSQCreate的功能为从空闲QCB中取出一个QCB进行设置,返回ECB的地址
函数原型OS_EVENT *OSQCreate (void **start,INT16U size) start是用户定义的消息队列指针数组的地址的首地址。
其主要代码如下:
if (pevent != (OS_EVENT *)0) { //空闲ECB链表中是否为空
OS_ENTER_CRITICAL();
pq = OSQFreeList; //取QCB空闲链表表头地址
if (pq != (OS_Q *)0) { //QCB空闲链是否为空
OSQFreeList = OSQFreeList->OSQPtr; //去QCB空闲链表的第一个QCB
OS_EXIT_CRITICAL();
//初始化QCB
pq->OSQStart = start;
pq->OSQEnd = &start[size];
pq->OSQIn = start;
pq->OSQOut = start;
pq->OSQSize = size;
pq->OSQIn = start;
pq->OSQOut = start;
pq->OSQSize = size;
pq->OSQEntries = 0u;
//初始化ECB
pevent->OSEventType = OS_EVENT_TYPE_Q;
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pq;
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pq;
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent); //初始化事件控制块的等任务待数组和表
} else {
pevent->OSEventPtr = (void *)OSEventFreeList; //ECB空闲链表为空,没有空闲的QCB
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0; //pevent为空
}
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0; //pevent为空
}
}
2.3:删除消息队列OSQDel
删除消息队列与删除信号量差不多,对ECB的操作是一样的,但是消息队列多了一个QCB的处理。
对QCB处理的代码如下:
//下面就是将相关的QCB放到QCB空闲链表中
pq = (OS_Q *)pevent->OSEventPtr;
pq->OSQPtr = OSQFreeList;
OSQFreeList = pq;
2.4:消息队列刷新OSQFlush
该函数的功能就是刷新消息队列,清空消息队列。
其源码如下,将队列的队头和队尾都指向消息队列的基地址。
pq = (OS_Q *)pevent->OSEventPtr;
pq->OSQIn = pq->OSQStart;
pq->OSQOut = pq->OSQStart;
2.5申请消息队列中的消息OSQPend
该函数的功能是任务向消息队列申请消息,若是消息队列中有消息则取消息,若是没有消息,则取阻塞等待。
该函数的原型如下:void *OSQPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr)
函数的功能实现和消息邮箱差不多。只是在判断是否有消息上有点区别,如下源码:
pq = (OS_Q *)pevent->OSEventPtr; //QCB的地址
if (pq->OSQEntries > 0u) { //判断消息队列是否为空
pmsg = *pq->OSQOut++; //取队列中的消息
pq->OSQEntries--; //消息队列消息数目减1
if (pq->OSQOut == pq->OSQEnd) { //去消息的地址在队列的尾地址
pq->OSQOut = pq->OSQStart; //去消息的地址赋值为队列的首地址(循环队列)
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pmsg); //返回消息地址
}
//若是为空,就阻塞任务。事件阻塞任务的方式除了事件标志组有点不一样之外,其他的事件得不到满足阻塞任务的方式都是一样的。
需要注意的是阻塞后重新就绪,任务不回在QCB中取消息,而是通过任务控制块中的OSTCBMsg 成员获取消息。
因为会阻塞,所以回复就绪后,取队列中第一个消息。所以可以用OSTCBMsg。
对照后面消息发送,若是在提交消息的时候,没有阻塞,就会放到队列中,有阻塞,唤醒阻塞任务,消息地址存入唤醒人的任务控制快的OSTCBMsg
2.6放弃消息等待函数OSQPendAbort
该函数的主要功能为唤醒所有等待的消息而阻塞的任务。该函数只能由非等待阻塞的任务执行,等待消息阻塞的任务是不可能去执行该函数的。
该函数的原型为:INT8U OSQPendAbort (OS_EVENT *pevent, INT8U opt, INT8U *perr)
opt选项,两种情况
OS_POST_OPT_NONE 放弃等待,然后唤醒等待任务中的最高优先级的任务,就是执行OSMboxPost函数一样的功能
OS_POST_OPT_BROADCAST 放弃等待广播给所有的等待任务,唤醒所有等待任务
2.7发送消息到消息队列中OSQPost
该函数的原型为INT8U OSQPost (OS_EVENT *pevent, void *pmsg)
该函数的主要功能为向队列发送一个消息,若是有任务在等待队列消息,唤醒任务,重新调度,如果没有,则更新消息队列。
其主要代码如下:
if (pevent->OSEventGrp != 0u) { //是否有任务在等待队列中的消息
//唤醒最等待消息的最高优先级任务,并且将消息地址赋给阻塞最高优先级任务控制快的OSTCBMsg
(void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_Q, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); //执行调度,执行就绪最高优先的任务
return (OS_ERR_NONE);
}
}
pq = (OS_Q *)pevent->OSEventPtr; //
if (pq->OSQEntries >= pq->OSQSize) { //如果消息数目大于消息队列容量即队列已满,立即返回,不操作队列
OS_EXIT_CRITICAL();
return (OS_ERR_Q_FULL);
return (OS_ERR_Q_FULL);
}
*pq->OSQIn++ = pmsg; //队列没满,消息插入队列中
pq->OSQEntries++; //队列消息数目加一
if (pq->OSQIn == pq->OSQEnd) { //如果插入消息位置在队列的尾地址,则将插入位置移到队列的头地址
pq->OSQIn = pq->OSQStart;
}
2.8:发送消息到消息队列中OSQPostFront
该函数跟2.7中的函数最大的不同就是,提交消息插入的位置不是队列的尾部,而是队列的头部。这个就不像是队列的操作,而是有点像堆栈的操作。
2.9:单选项的消息发送OSQPostOpt
函数的原型为INT8U OSQPostOpt (OS_EVENT *pevent, void *pmsg, INT8U opt)
跟2.8 2.9不同的是,这个函数参数中有一个opt参数。
该参数有四种情况:
OS_POST_OPT_NONE 消息发送,而且唤醒单个等待任务
OS_POST_OPT_BROADCAST 消息广播发送,唤醒所有等待任务
OS_POST_OPT_FRONT 发送将消息,并且其插入到队列的头部。类似于2.8的功能
OS_POST_OPT_NO_SCHED 消息提交之后,但是并不执行任务调度。
2.10:不等待请求消息队列函数OSQAccept
函数的原型为void *OSQAccept (OS_EVENT *pevent, INT8U *perr)
该函数功能为向队列请求消息,若有消息,则返回非空指针。若是没有消息,不等待,返回空指针,转而执行其他部分。
2.11查询消息队列的状态OSQQuery