之前说到事件,讲了事件,信号量和互斥信号量,还有一个队列没说,今天说说队列.
队列是用在任务之间传送多个消息的时候,a任务发送消息,b任务发送消息,然后c任务可以依次去提取出b和a传递的消息,不会造成系统的阻塞,他的实现结构如下
在队列的实现中,也是使用事件ecb,OSEventType为OS_EVENT_TYPE_Q类型,而其OSEventPtr指向一个QS_Q结构的指针,该结构的定义如下
typedef struct os_q { /* QUEUE CONTROL BLOCK */
struct os_q *OSQPtr; /* 指向下一个队列控制块 */
void **OSQStart; /* 指向消息指针数组的起始地址*/
void **OSQEnd; /* 指向消息指针数组的下一个单元,构成循环数组缓冲区 */
void **OSQIn; /*指向插入消息的位置,其与OSQEnd相等时,调整到指向数组的起始单元*/
void **OSQOut; /*指向被取出消息的位置,其与OSQEnd相等时,调整到指向数组的起始单元 */
INT16U OSQSize; /*数组的长度*/
INT16U OSQEntries; /*以存放消息指针的元素数目*/
} OS_Q;
我们可以称呼这种结构叫做QCB,队列控制快.在系统初始化的时候会有很多个队列控制快,系统初始化的时候将队列控制快连接成一个链表,如下
OS_EXT OS_Q OSQTbl[OS_MAX_QS];
队列控制快数量为OS_MAX_QS,该参数在os_cfg.h文件中指定
#define OS_MAX_QS 5u
系统存放的空闲队列控制块链表指针为
OS_EXT OS_Q *OSQFreeList;
在系统初始化的时候os_init函数中调用OS_QInit函数,该函数中有如下代码
for (ix = 0u; ix < (OS_MAX_QS - 1u); ix++) { /* Init. list of free QUEUE control blocks */
ix_next = ix + 1u;
pq1 = &OSQTbl[ix];
pq2 = &OSQTbl[ix_next];
pq1->OSQPtr = pq2;
}
pq1 = &OSQTbl[ix];
pq1->OSQPtr = (OS_Q *)0;
OSQFreeList = &OSQTbl[0];
可以看到,这一段类似于tcb和ecb的初始化,将OSQTbl结构连接成链表,链表头为OSQFreeList,之后,当创建队列的时候从空闲链表中取出链表头作为队列事件,如下
if (pq != (OS_Q *)0) {
OSQFreeList = OSQFreeList->OSQPtr;
OS_EXIT_CRITICAL();
pq->OSQStart = start;
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;
可以看到第二句话里面就是讲空闲队列控制块链表的链表头后移动到下一个元素,然后将start元素进行赋值,这个start元素师创建的时候给定的,参数如下
OS_EVENT *OSQCreate (void **start,INT16U size)
第一个参数为一个指针数组,第二个参数为指针数组大小,也就是说,start指向一个指针数组的第一个元素地址,而数组里面存放的是指向具体的消息的指针,我们可以形象的说,start相当于一个messagetable的首地址.
系统发送消息队列的时候使用函数OSQPost函数,其中核心代码为
*pq->OSQIn++ = pmsg;
pq->OSQEntries++;
if (pq->OSQIn == pq->OSQEnd) {
pq->OSQIn = pq->OSQStart;
这段代码向列表中添加一个元素,然后当添加的元素超过end的时候循环到头上,一般来说,队列取出元素会分为先进先出和后进先出两种,我们先来看看申请队列的函数就能明白ucos采用了什么方式,申请队列的函数为OSQPend函数
if (pq->OSQEntries > 0u) {
pmsg = *pq->OSQOut++;
pq->OSQEntries--;
if (pq->OSQOut == pq->OSQEnd) {
pq->OSQOut = pq->OSQStart;
}
这段代码需要和上面两段结合起来看,初始化的时候OSQIn和OSQOut都指向start头,插入的时候OSQIn++,取出的时候OSQOut++,当插入第三个元素之后, OSQIn指向了start+3,但是OSQOut依然是指向start的,取出元素的时候取出的是最早添加到队列中的元素,因此可以认为,系统的队列维护方式是先进先出, OSQEntries标识系统中该队列含有的信号数量,只有当数量不为0的时候,才能从队列中取出元素
另外,为了系统灵活性,ucos提供了函数可以发送消息到队列开头,函数为OSQPostFront,实现如下
pq->OSQOut--;
*pq->OSQOut = pmsg;
pq->OSQEntries++;
可以看到,这个函数的实现和OSQPost的实现方式并不同, OSQPost的实现依靠的是OSQIn,而OSQPostFront依靠的是OSQOut,原因仔细想想也很简单,因为OSQIn和OSQOut是相关联的,我们来打个比方,如果此时OSQOut和OSQIn都指向start+1的位置,此时OSQPost三个元素,那么OSQIn为start+4,但是OSQOut依然是start+1,这个时候我们要在队列的头部插入一个元素,那么选择有两种,第一个是将之前插入的元素每一个都往后移动,使得OSQOut指向刚刚插入的元素,这样需要循环移植3个元素,时间不可测,犯了操作系统的大忌,所以不可取,不如直接将元素加入队列, OSQOut往前移动一位,此时OSQOut为start,到OSQIn距离为四个元素,很好的实现了目标
但是,实现这个的前提是我们实现了循环队列,而信号队列正好是循环队列.
说到这里队列的原理基本讲清楚了,接下来总结一下
创建消息队列
QSQCreate
请求消息队列
OSQPend
向消息队列发送消息
OSQPost
发送消息到队列头部
OSQPostFront
清空消息队列
OSQFlush
删除消息队列
OSQDel
查询消息队列
OSQQuery
消息队列使用了消息队列控制块qcb,队列控制块构成空闲链表,创建队列每次从空闲链表中去取出一个元素,消息控制块内部拥有一个循环链表,over