LiteOS 消息队列

参考:【野火】物联网操作系统 LiteOS 开发实战指南

3 LiteOS消息队列

3.1 消息队列简介

  1. 消息队列是一种常用于任务间通信的数据结构
  2. 可以在任务与任务间中断和任务间传递消息,实现接收来自任务或者中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在自己的空间
  3. 消息队列是一种异步的通信方式,用户在处理业务时,消息队列提供异步处理机制,允许将一个消息放入队列,但并不立即处理它
  4. 消息队列使用时需要包含头文件los_queue.h

3.2 LiteOS消息队列的特点

  1. 消息以先进先出方式排队,支持异步读写工作方式
  2. 读队列和写队列都支持超时机制
  3. 发送消息类型由通信双方约定,可以允许不同长度(不超过队列节点最大值)的任意类型消息
  4. 消息支持先进后出的方式排队,往队首发送消息(LIFO)
  5. 一个任务能够从任意一个消息队列接收和发送消息
  6. 多个任务能够从同一个消息队列接收和发送消息
  7. 当队列使用结束后,如果是动态申请的内存,需要通过释放内存函数回收

3.3 LiteOs消息队列的运作机制

  1. 创建队列时,用户根据传入的队列长度消息节点来开辟相应的内存空间
  2. 队列控制块中维护消息头结点位置usQueueHead和一个消息尾节点位置usQueueTail来表示消息队列的消息存储情况
  3. 可以将LiteOs消息队列看成一个_环形队列_
  4. 写队列是从usQueueTail所指的尾部的空闲节点写入区域,利用usReadWriteableCnt[OS_QUEUE_WRITE]来判断是否可以写入
  5. 读队列是从usQueueHead找到最先入队列的消息节点进行读取,利用usReadWriteableCnt[OS_QUEUE_READ]判断队列是否有消息可读取,若没有消息的队列进行读队列操作会引起任务挂起
  6. 删除队列根据传入的队列ID寻找到对应的队列,把队列状态设置为未使用,释放原队列所占空间
  7. LiteOs的消息队列采用两个双向链表来维护一个链表指向消息队列的头部,一个链表指向消息队列的尾尾(stReadWriteList[QUEUE_HEAD_TAIL]),通过访问这两个链表就能直接访问对应的消息空间,并且通过消息队列控制块 中的读写类型(usReadWriteableCnt[QUEUE_READ_WRITE])来操作消息队列
  8. 消息队列的运作过程如下图所示
    LiteOS 消息队列_第1张图片
    LiteOs消息队列运作图
    LiteOS 消息队列_第2张图片
    消息队列读写消息图

3.4 LiteOs消息队列的传输机制

  1. 消息队列是一种FIFO线性表,也支持LIFO

  2. 消息数据传输支持值传递(拷贝)和引用传递

  3. 两种传递方式比较

    消息传递方式 大小 重要性
    拷贝 数据量小的场合 数据重要性高
    引用方式 数据量大的场合 重要性一般场合

3.5 消息队列的阻塞机制

  • 顾名思义,就是指读/写消息队列时,能够完整的不受其他任务干扰的完成读/写,即阻塞机制

3.5.1 出队阻塞

  • 读操作,若消息队列中没有消息,会有三种选择
    • 该任务继续干别的事情,不会进入阻塞状态
    • 该任务等待消息队列的消息,等待时间由用户定制,等待过程即为阻塞状态,消息队列有了对应的消息后,该任务转为就绪状态,然后根据该任务的优先级读取队列消息,若超时,则该任务放弃等待,去干别的事情
    • 该任务死等,等不到消息就不干别的事情,该任务一直进入阻塞状态,直到完成读取队列的消息

3.5.2 入队阻塞

  • 写操作,若消息队列已经满了,会进行如下处理
    • 发送消息操作的时候,当且仅当队列被允许入队时,发送者才能成功发送消息
    • 队列中无可用消息空间时,系统会根据用户指定的阻塞超时时间将任务阻塞,在指定的超时时间内如果还不能完成入队操作,发送消息的任务或者中断服务程序会收到一个错误代码LOS_ERRNO_QUEUE_ISFULL ,然后解除阻塞状态
    • 只有在任务中发送消息才允许进行阻塞状态,在中断中发送消息不允许带有阻塞机制的,否则返回错误代码LOS_ERRNO_QUEUE_READ_IN_INTERRUPT
    • 如果有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权

3.6 常用消息队列的函数介绍

3.6.1 使用队列模块的典型流程

  1. 创建消息队列LOS_QueueCreate,在创建任务之前
  2. 创建成功后,可以得到消息队列的ID
  3. 写队列操作函数LOS_QueueWrite ,在对应需要传递消息的任务中使用
  4. 读队列操作函数LOS_QueueRead ,在对应需要读取消息的任务中使用
  5. 删除队列LOS_QueueDelete

3.6.2 创建消息队列LOS_QueueCreate()

  1. 创建消息队列的内存空间:
    内 存 空 间 = 消 息 队 列 控 制 块 大 小 + ( 单 个 消 息 空 间 大 小 + 4 字 节 ) ∗ 消 息 队 列 长 度 内存空间=消息队列控制块大小+(单个消息空间大小+4字节)*消息队列长度 =++4

  2. 当消息队列被创建时,系统会为控制块分配对应的内存空间,用于保存消息队列的(消息存储位置,头指针,尾指针,消息大小,以及队列长度等)

  3. UINT32 LOS_QueueCreate(CHAR *pcQueueName,//消息队列的名称,暂时未使用
                           UINT16 usLen,// 队列长度
                           UINT32 *puwQueueID,//成功创建的队列控制结构ID,需要用户在创建前定义
                           UINT32 uwFlags,//队列参数,保留参数,暂时不使用
                           UINT16 usMaxMsgSize )//最大消息字节
    {
        ...
    }
    
    // 队列控制块
    typedef struct tagQueueCB
    {
        UINT8       *pucQueue;                              /**< 队列指针 */
        UINT16      usQueueState;                           /**< 队列状态 */
        UINT16      usQueueLen;                             /**< 队列中消息个数 */
        UINT16      usQueueSize;                            /**< 消息节点大小 */
        UINT16      usQueueID;                              /**< 队列ID */
        UINT16      usQueueHead;                            /**< 消息头结点位置 */
        UINT16      usQueueTail;                            /**< 消息尾结点位置 */
        UINT16      usReadWriteableCnt[2];     /**< 可读或者可写资源的计数0:可读,1:可写 */
        LOS_DL_LIST stReadWriteList[2];        /**< 指向要读取或写入的链表的指针0: 读列表 */
        LOS_DL_LIST stMemList;                              /** 指向内存链表的指针 */
    } QUEUE_CB_S;
    
  4. 先定义队列ID,再调用LOS_QueueCreate() 函数创建,才能成功创建消息队列

3.6.2 消息队列删除函数LOS_QueueDelete()

  1. 队列删除函数是直接根据队列ID直接删除的

    UINT32 LOS_QueueDelete(UINT32 uwQueueID)
    
  2. 队列在使用或者阻塞中是不能被删除的

3.6.3 消息队列写数据函数

  1. 任务或者中断程序都可以给消息队列写入消息

  2. 当写入消息时,

    • 如果队列未满,LiteOS会将消息拷贝到消息队列的尾部,
    • 若满,会根据用户指定的阻塞超时时间进行阻塞,在这段时间中,如果队列还是满的,该任务将保持阻塞状态以等待有空闲的消息空间,如果其他任务从其等待的队列中读取了数据,则消息队列空出空间,该等待的任务自动由阻塞状态转为就绪态,并写入消息
    • 当任务等待的时间超过指定的阻塞时间,即使队列中还是满的,任务也会自动从阻塞状态变为就绪态,此时发送消息的任务/中断程序会收到一个错误码:LOS_ERRNO_QUEUE_ISFULL
  3. 不带拷贝写入函数LOS_QueueWrite()

    UINT32 LOS_QueueWrite(UINT32 uwQueueID,    // 队列ID
                          VOID *pBufferAddr,   // 存储写入的数据的起始地址
                          UINT32 uwBufferSize, // 存入缓存区的大小
                          UINT32 uwTimeOut);   // 等待时间(0~LOS_WAIT_FOREVER)
    
  4. 写入队列需要注意几点:

    • 在使用写入队列的操作前应先创建要写入的队列
    • 在中断上下文环境中, 必须使用非阻塞模式写入,也就是等待时间为 0 个 tick
    • 在初始化 LiteOS 之前无法调用此 API
    • 将写入由 uwBufferSize 指定大小的数据,其数据存储在 BufferAddr 指定的地址 ,那么该数据地址必须有效,否则会发生错误
    • 写入队列节点中的是数据的地址
  5. 带拷贝写入LOS_ QueueWriteCopy()

    UINT32 LOS_QueueWriteCopy(UINT32 uwQueueID,    // 队列ID
                          	  VOID *pBufferAddr,   // 存储写入的数据的起始地址
                          	  UINT32 uwBufferSize, // 存入缓存区的大小
                              UINT32 uwTimeOut);   // 等待时间(0~LOS_WAIT_FOREVER)
    
    1. 注意点同不带拷贝写入,区别是写入队列中的是存储在pBufferAddr中的数据

3.6.4 消息队列读数据函数

  1. LOS_ QueueRead()(不带拷贝方式读出)

    UINT32 LOS_QueueRead(UINT32 uwQueueID,    // 队列ID
                         VOID *pBufferAddr,   // 存储写入的数据的起始地址
                         UINT32 uwBufferSize, // 存入缓存区的大小
                         UINT32 uwTimeOut);   // 等待时间(0~LOS_WAIT_FOREVER)
    
  2. 注意点:

    • 在使用读取队列的操作前应先创建要写入的队列
    • 队列读取采用的是先进先出(FIFO)模式, 首先读取首先存储在队列中的数据
    • 必须要我们自己定义一个存储读取出来的数据的地方,并且把存储数据的起始地址传递给 LOS_ QueueRead()函数,否则,将发生地址非法的错误。
    • 在中断上下文环境中, 必须使用非阻塞模式写入,也就是等待时间为 0 个 tick
    • 在初始化 LiteOS 之前无法调用此 API
    • pBufferAddr里存放的是队列节点的地址
    • LOS_QueueReadCopy()LOS_QueueWriteCopy()是一组接口,LOS_QueueRead()LOS_QueueWrite()是一组接口,两组接口需要配套使用
  3. LOS_ QueueReadCopy()(带拷贝读出)

    UINT32 LOS_QueueReadCopy(UINT32  uwQueueID,		// 队列 ID
                             VOID *  pBufferAddr,	// 存储获取数据的起始地址
                             UINT32 * puwBufferSize,// 保存读取之后数据大小的值
                             UINT32  uwTimeOut)		// 等待时间
    
    1. 注意点同上,不同的是
      • pBufferAddr存放的是消息队列中的数据
      • 需要定义一个空间保存读取数据的大小

你可能感兴趣的:(LiteOS学习笔记)