LiteOS内核开发(五)

本章将介绍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队列,随后解锁任务调度函数,写任务和读任务开始按顺序运行,不断打印出结果,当最后一次读任务没有读到数据时,自动退出并删除了消息队列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值