如果把邮箱比作信号量的升级版,那消息队列就是邮箱的升级版,邮箱可以实现从一个任务向另一个任务发送一个指针变量,消息队列则可以实现从一个任务向
另一个任务发送多个指针变量,而且每个指针指向的数据结构变量也可以有不同。
使用消息队列需要注意的恩地方是:一个任务或者中断服务子程序可以调用OSQPost(),OSQPostFront(),OSQFlush(),或者OSQAccept函数,但是只有任务
可以调用OSQPend()和OSQQQuery函数。
我们知道,每个事件有一个对应事件控制块,用于记录有关这个事件的消息,消息队列也不理瓦,一个消息队列对应一个队列控制块。
相对而言,邮箱而言,ucosii定义了一个数据结构来存储消息队列的信息,
typedef struct os_q{
struct os_q *OSQPtr;
void **OSQStart;
void **OSQEnd;
void **OSQOut;
INT16U OSQSize;
INT16U OSQEtries;
}OS_Q;
OSQPtr在空闲队列控制块中链接所有的队列控制块,一旦建立了消息队列,该域就不再有用了
OSQStart是指向消息队列的指针数组的起始地址的指针,用户应用程序在使用消息队列之前必须先定义该数组。
OSQEnd是指向消息队列结束单元的下一个地址的指针,该指针使得消息队列构成成一个循环的缓冲区。
OSQIn 是指向消息队列中插入下一条消息的位置的指针,当OSQIn和OSQEnd相等时,OSQIn被调整指向消息队列的起始单元。
OSQOut是指向消息队列中下一个取出消息的位置的指针,当OSQOut和OSQEnd相等时,OSQOut被调整指向消息队列的起始单元。
OSQSize是消息队列中总的单元数,该值是在建立消息队列时由用户应用程序决定的,该值最大可以是65535
OSQEntries是消息队列中当前的消息数量,当消息队列是空的时,该值为0,当消息队列满了以后,改制和OSQSize值一样,在消息uil刚建立时,该值为0.
ucosii提供了7个消息队列进行操作的函数
1.建立一个消息队列,OSQCreat();
2.等待一个消息队列中的消息 OSQPend();
3.向消息队列发送一个消息(FIFO)OSQPost();
4.向消息队列发送一个消息(后进先出FIFO),OSQPostFront();
5.无等待地从一个消息队列中取得消息OSQAccept()
6.清空一个消息队列OSQFlush();
7.查询一个消息队列的状态OSQQuery();
消息队列最根本的部分时一个缓冲区,其中的每个单元包含一个指针,队列未满时,OSQIn指向下一个存放消息的地址单元,如果队列已满,OSQIn则与OSQOut指向同一单元,如果在OSQIn指向的单元插入新的消息的指针,就构成FIFO队列,相反,如果在OSQOut指向的单元的下一个单元插入新的指针,就构成LIFO队列。当OSQEntries和OSQSize相等,说明队列已满,消息指针总是从OSQOut指向的单元取出,指针OSQStart和OSQEnd定义了消息指针数组的头尾,以便在OSQIn和OSQOut到达队列的边缘时,进行编辑检查和必要的调整,实现循环功能。
消息队列通常可以应用一下两个地方:
1.存储外部事件:外部事件由中断收集,然后存储到队列中。
2.串口接收程序中的接收缓冲区,可以理解为消息队列。
使用一个消息队列的步骤如下:
1.建立一个指向消息数组的指针和数组的大小,该指针数组必须为void类型
void *MyArrayOfMsg[SIZE];
2.声明一个OS_EVENT 类型的指针指向生成的队列
OS_EVENT *QSem;
3.调用OSQcreate()函数创建消息队列,如下:
QSem =OSQcreate(&MyArrayOfMsg[0],SIZE);
4.等待消息队列中的消息,OSQPend();
5.向消息队列发送一则消息。
建立一个消息队列,OSQCreate()的实现代码如下:
OS_EVENT *OSQCreate(void **start,INT16U size)
{
OS_EVENT *pevent;
OS_Q *pq;
OS_EVENT_CRITICAL();
pevent=OSEventFreeList; (1)
if(OSEventFreeList!=(OS_EVENT*)0)
{
OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr; (2)
}
OS_EIXT_CRITICAL();
if(pevent!=(OS_EVENT*)0)
{
OS_ENTER_CRITICAL();
pq=OSQFreeList; (3)
if(OSQFreeList!=(OS_Q*)0)
{
OSQFreeList=OSQFreeList->OSQPtr;
}
OS_EXIT_CRITICAL();
if(pq!=(OS_Q*)0)
{
pq->OSQStart =start; (4)
pq->OSQEnd= &start[SIze];
pq->OSQIn= start;
pq->OSQOut=start;
pq->OSQSize=size;
pq->OSQEntries =0;
pevent->OSEventType=OS_EVENT_TYPE_Q; (5)
pevent->OSEventPtr=pq; (6)
OSEventWaitListInit(pevent) (7)
}
else
{
OS_ENTER_CRITICAL();
pevent->OSEventPtr=(void*)OSEventFreeList; (8)
OSEventFreeList=pevent;
OS_EIXT_CRITICAL();
pevent=(OS_EVENT*)0;
}
}
return(pevent); (9)
}
它和创建邮箱,创建信号量过程很相似,首先申请控制块,接着初始化这个控制块,和创建邮箱,信号量不同,创建消息队列过程
是多申请一个队列控制块
OSQCreate()首先从空闲事件控制块链表中取得一个事件控制块(1);并对剩余的空闲事件控制块列表的指针做相应的调整,
使它执行下一个空闲事件控制块(2),接着OSQCreate()函数从空闲队列控制块列表中取出一个队列控制块(3),如果空闲队列控制块是可以
就对其进行初始(4).然后该函数将事件控制块的类型设置为OS_EVENT_TYPE_Q(5),使其OSEventPtr指针指向队列控制块(6),OSQCreate还要调用OSEventWaitListInit()
向它的调用函数返回一个指向事件控制块的指针(9),该指针将在调用OSQPend(),OSQPost(),OSQPostFront(),OSQFlush(),OSQAccept和OSQQuery
等消息队列处理函数的使用,因此,该指针可以被看作是对消息队列的句柄,值得注意的是,如果此时没有空闲的事件控制块,OSQCreate函数将返回一个NULL指针,如果没有
队列控制块可以使用,为了不浪费事件控制块资源,OSQCreate函数将把刚刚取得的事件控制块反给空闲控制块列表【8】。
#define TASK_STK_SIZE 512
#define N_MESSAGES 128
OS_STK StartTaskStk[TASK_STK_SIZE]
OS_STK MyTaskStk[TASK_STK_SIZE]
OS_STK YouTaskStk[TASK_STK_SIZE];
char *s_flag; //该字符串指示哪个任务在运行
char *ss; //存放接收到的消息指针
char *s100;//存放发送消息的指针
char *s;
char *s500;
void *MsgGrp[N_MESSAGES];//定义消息指针数组
//创消息队列,首先需要定义一个指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中,最后调用函数OSQCreate()来创建消息队列
INT8U err;
INT8U y=0;
OS_EVENT *Str_Q; //定义事件控制块指针 队列的事件控制块指针,用于存放创建消息队列的指针
void MyTask(void *data)
void StartTask(void *data);
void YouTask(void *data);
void main(void)
{
OSInit();
Str_Q=OSQCreate(&MsgGrp[0],N_MESSAGES);//创建消息队列
//函数的第一个参数&MsgGrp[0]是void **start,是存放系哦啊新缓冲区指针数组的地址,它是指向指针数组的指针
//可以用指针数组的首个元素的地址表示 N_MESSAGES是该数组的大小 返回值是消息队列的指针,Str_Q是OS_EVENT型指针
OSTaskCreate(StartTask,(void*)0,&StartTaskStk[TASK_STK_SIZE-1],0);
OSStart();
}
void StartTask(void *pdata)
{
#if OS_CRITICAL_METHOD==3
OS_CPU_SR cpu_sr;
#endif
INT16S key;
pdata=pdata;
OSStatInit();
OSTaskCreate(MyTask,(void*)0,&MyTaskStk[TASK_STK_SIZE-1],3);
OSTaskCreate(YouTask,(void*)0,&YouTaskStk[TASK_STK_SIZE-1],4);
for(;;)
{
s_flag="The StartTask is runnig!";
if(OSTimeGet()>100&&OSTimeGet()<500)
{
s500="The value ofOSTIME is from 1000 to 1500NOW";
OSQPostFront(Str_Q,s100); //发送消息,以LIFO后进先出的方式发送
//发送消息,以LIFO后进先出的方式发送
//第一个参数Str_Q是消息队列的指针,是OSQCreate的返回值,第二个参数s是消息指针
s="The string belongs to which task";
OSQPostFront(Str_Q,s); //发送消息,以LIFO方式发送,所以如果要申请消息时,会先得到s,然后才是s100;
}
if(OSTimeGet()>1000&&OSTimeGet()<1500)
{
s500="The value of OSTIME is from 1000 to 1500 NOW";
OSQPostFront(Str_Q,s500);
}
if(PC_GetKey(&key)==TRUE)
{
if(key==0x1B)
{
PC_DOSReturn();
}
}
OSTimeDlyHMSM(0,0,1,0);
}
}
void MyTask(void *pdata)
{
#if OS_CRITICAL_METHOD==3
OS_CPU_SR cpu_sr;
#endif
pdata=pdata;
for(;;)
{
s_flag="The MyTask is running!";
ss=OSQPend(Str_Q,0,&err);//请求消息队列,参数分别是:Str_Q为请求的消息队列的指针,第二参数为等待时间
//0表示无限等待,&err为错误信息,返回值为队列控制块OS_Q成员OSQOut指向的消息,如果没有消息可用,在
//使调用OSQPend的任务挂起来,使之处于等待状态,并引发一次任务调度
//因为前面发送的消息是使用的是LIFO的方式,所以第一次得到的消息是上面最后发送的消息
PC_DispStr(3,y,ss,DISP_FGND_BLACK+DISP_BGND_LIGHT_GRAY)//显示得到的消息
PC_DispStr(0,y,"My",DISP_FGND_RED+DISP_BGND_LIGHT_GRAY);
OSTimeDlyHMSM(0,0,1,0);
}
}
运行的现象说明上面分析时正确的,因为当时钟节拍大于100,小于500时,会发送第一个if语句中的两个字符串
s100和s
下面运行的任务接收到并且显示,当时钟节拍数大于1000小于1500时,发送第二个if语句的字符,下面运行的、人
//接收并显示,当时钟节拍数大于1500时,就不再发送消息,下面的任务得不到就无限等待下去,所以就不再显示
从运行的现象不难可以看出,有时MyTask或YouTask运行了,但是没有得到消息而处于等待状态,使用上面的
方法很清纯地看出来任务调度和运行的关系,MyTask和YouTask是交运行的,因为延时时间相等