本章将介绍LiteOS内核中的消息队列模块
1. 基本概念
消息队列是IPC的一种,也是一种常用于任务间进行通信的数据结构,消息队列通过接收任务或者中断的不固定长度消息,根据不同的接口来确定消息是否存放在队列空间中。
任务可以从队列中读取消息,当队列中没有消息时,挂起读取任务;当出现新消息时,挂起的任务被唤醒并处理新的消息。任务也可以往队列中写入内容,当消息队列已满时,挂起写入任务,当队列中有空闲时,挂起的写入任务被唤醒并写入消息,若将读写任务的超时时间设置为0,则在不满足条件时直接退出,而不是挂起。
LiteOS也支持异步通信机制,通过队列实现,具体特征表现为:消息以先进先出的方式排队,支持异步读写;读队列和写队列都支持超时机制;每读取一条消息,就会将该消息节点设置为空闲;发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息;一个任务能够从任意一个消息队列接收和发送消息;多个任务能够从同一个消息队列接收和发送消息;创建队列时所需的队列空间,默认支持接口内系统自行动态申请内存的方式,同时也支持将用户分配的队列空间作为接口入参传入的方式。
队列通过队列控制块记录当前队列状态,这将方便对队列的操作,队列控制块结构体代码如下所示
typedef enum {
OS_QUEUE_READ =0,
OS_QUEUE_WRITE =1,
OS_QUEUE_N_RW =2
} QueueReadWrite;
/**
* Queue information block structure
*/
typedef struct
{
UINT8 *queueHandle; /* 队列指针 */
UINT8 queueState; /* 队列状态,有使用中,未使用两种状态 */
UINT8 queueMemType; /* 创建队列时内存分配的方式,可以由系统自动动态申请,也可以由用户申请后传入接口 */
UINT16 queueLen; /* 队列中消息节点个数,即队列长度 */
UINT16 queueSize; /* 消息节点大小 */
UINT32 queueID; /* 队列ID */
UINT16 queueHead; /* 消息头节点位置(数组下标)*/
UINT16 queueTail; /* 消息尾节点位置(数组下标)*/
UINT16 readWriteableCnt[OS_QUEUE_N_RW]; /* 数组下标0的元素表示队列中可读消息数,
数组下标1的元素表示队列中可写消息数 */
LOS_DL_LIST readWriteList[OS_QUEUE_N_RW]; /* 读取或写入消息的任务等待链表,
下标0:读取链表,下标1:写入链表 */
LOS_DL_LIST memList; /* CMSIS-RTOS中的MailBox模块使用的内存块链表 */
} LosQueueCB;
借助队列控制块,我们可以方便地对队列进行读写,队列的读写模型如图所示
首先,当队列被创建时,会返回队列的ID号,队列中有Head和Tail两个位置,分别表示消息头结点的位置和尾结点的位置。头和尾中间的区域表示已占用的消息结点,其余部分表示空闲的消息结点,在队列创建之初,Head,Tail都指向起始位置。当写队列时,根据结构体中的readWriteableCnt[1]判断队列能否写入,对于readWriteableCnt[1]=0,表示队列已满,无法写入。目前有两种写入方式,一种是从尾部找到Tail结点,在Tail结点之后空间空间写入,如果Tail结点已经指向队尾则回卷;另一种是从头部找到Head结点,将Head的前一个结点作为写入对象,Head指向队首则同样回卷。
读队列时,根据readWriteableCnt[0]判断队列是否有消息需要读取,对全部空闲(readWriteableCnt[0]为0)队列进行读操作会引起任务挂起。如果队列可以读取消息,则根据Head找到最先写入队列的消息节点进行读取。如果Head已经指向队列尾部则采用回卷方式。
删除队列时,根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态。如果是通过系统动态申请内存方式创建的队列,还会释放队列所占内存。
2. 实例分析
2.1 前提条件
同样在menuconfig下配置队列模块,使能LOSCFG_BASE_IPC_QUEUE(队列模块裁剪开关),关闭LOSCFG_QUEUE_STATIC_ALLOCATION(支持以用户分配内存的方式创建队列),将LOSCFG_BASE_IPC_QUEUE_LIMIT(系统支持的最大队列数)的值设置为1024
2.2 具体步骤
在完成上述准备工作后,就可以在LiteOS Stdio下新建工程开始写程序了,主程序部分代码如下所示
#include "los_task.h"
#include "los_queue.h"
static UINT32 g_queue;//队列控制结构,公用
#define BUFFER_LEN 50//队列中的结点大小
VOID send_Entry(VOID)//向队列中发送消息的任务函数
{
UINT32 i = 0;
UINT32 ret = 0;
CHAR abuf[] = "test is message x";
UINT32 len = sizeof(abuf);
while (i < 5) {
abuf[len -2] = '0' + i;
i++;
ret = LOS_QueueWriteCopy(g_queue, abuf, len, 0);//将abuf中的内容复制到公用的g_queue中来
if(ret != LOS_OK) {
dprintf("send message failure, error: %x\n", ret);
}
LOS_TaskDelay(5);
}
}
VOID recv_Entry(VOID)//将队列消息读取出来的任务函数
{
UINT32 ret = 0;
CHAR readBuf[BUFFER_LEN] = {0};
UINT32 readLen = BUFFER_LEN;
while (1) {
ret = LOS_QueueReadCopy(g_queue, readBuf, &readLen, 0);//将g_queue中的值读到readBuf中去
if(ret != LOS_OK) {
dprintf("recv message failure, error: %x\n", ret);
break;
}
dprintf("recv message: %s\n", readBuf);//打印readBuf的值
LOS_TaskDelay(5);
}
while (LOS_OK != LOS_QueueDelete(g_queue))//删除公共队列
{
LOS_TaskDelay(1);
}
dprintf("delete the queue success!\n");
}
UINT32 Example_CreateTask(VOID)
{
UINT32 ret = 0;
UINT32 task1, task2;
TSK_INIT_PARAM_S initParam;
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)send_Entry;//将写任务的入口函数设置为send_Entry
initParam.usTaskPrio = 9;//设置优先级为9
initParam.uwStackSize = LOS_TASK_MIN_STACK_SIZE;
initParam.pcName = "sendQueue";
#ifdef LOSCFG_KERNEL_SMP
initParam.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//多核情况下设置任务的CPU亲和性
#endif
initParam.uwResved = LOS_TASK_STATUS_DETACHED;//任务在执行结束后自删除
LOS_TaskLock();//创建完写任务后锁住任务调度,防止执行时乱序
ret = LOS_TaskCreate(&task1, &initParam);//正式创建写任务
if(ret != LOS_OK) {
dprintf("create task1 failed, error: %x\n", ret);
return ret;
}
initParam.pcName = "recvQueue";
initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)recv_Entry;//将读任务的入口函数改为recv——Entry,其他配置和写任务相同
ret = LOS_TaskCreate(&task2, &initParam);//创建读任务
if(ret != LOS_OK) {
dprintf("create task2 failed, error: %x\n", ret);
return ret;
}
ret = LOS_QueueCreate("queue", 5, &g_queue, 0, BUFFER_LEN);//创建消息队列,队列长度为5个节点,公用通道为g_queue,队列模式为0,节点大小为50
if(ret != LOS_OK) {
dprintf("create queue failure, error: %x\n", ret);
}
dprintf("create the queue success!\n");
LOS_TaskUnlock();//解锁任务调度,程序正式开始运行
return ret;
}
执行结果如下
create the queue success!
recv message: test is message 0
recv message: test is message 1
recv message: test is message 2
recv message: test is message 3
recv message: test is message 4
recv message failure, error: 200061d
delete the queue success!
在上述程序中,主要进行的工作是,先创建写和读任务的入口函数,其中写任务在5s一次的循环中,将abuf中的x值变为从0到4的数字,每次循环都abuf写入g_queue,而读任务每5s将读取一次g_queue的内容,在读取出错后跳出循环并删除g_queue;而在主函数中,先初始化两个任务的属性,绑定各自的函数入口,设置相同的优先级,锁住任务调度后完成创建,在之后初始化创建g_queue队列,随后解锁任务调度函数,写任务和读任务开始按顺序运行,不断打印出结果,当最后一次读任务没有读到数据时,自动退出并删除了消息队列。