1、实验简介

参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/a5_kernel_queue

1.1、实验目的

本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 消息队列(Message Queue) 的基本概念与编程方法。通过本实验,你将学会:

  1. 理解消息队列在任务间通信中的作用与 FIFO 特性;
  2. 使用 CMSIS-RTOS v2 标准接口创建、写入和读取消息队列;
  3. 通过消息队列实现生产者-消费者模式的多线程协作;
  4. 完成案例代码的编译、烧录与串口调试。

1.2、实验内容

本案例在 LZ3863-星闪开发板上创建 一个消息队列两个协作线程

线程名称 优先级 主要行为
msg_write_thread 较高(Normal+1) 每隔约 1 秒向队列写入一条递增的无符号整型消息
msg_read_thread 普通(Normal) 阻塞等待队列消息,获取成功后打印日志

msg_write_thread 作为 生产者 持续向队列投递消息;msg_read_thread 作为 消费者 从队列中取出消息。当队列为空时,读线程将阻塞等待,直到写线程写入新消息后才会被唤醒,从而直观展示消息队列在任务间传递数据与同步阻塞的效果。

1.3、实验环境

项目 说明
硬件 LZ3863-星闪开发板、USB 数据线
软件 OpenHarmony v5.1.0 源码、hb 编译工具
调试工具 串口助手(波特率 115200)
案例路径 applications/sample/wifi-iot/app/a5_kernel_queue/

2、基础知识

2.1、什么是消息队列

消息队列(Message Queue) 是一种内核提供的任务间通信机制,常用于:

  • 任务间数据传递:一个任务将消息放入队列,另一个任务从队列中取出消息;
  • 任务与中断间通信:中断服务程序可向队列投递消息,任务在合适时机读取处理;
  • 异步解耦:发送方无需等待接收方立即处理,消息暂存在队列中。

消息队列可以存储 有限数量、固定大小 的数据项。创建队列时需要指定 单条消息的大小队列长度(最多可存放的消息条数)。

2.2、FIFO 先进先出原则

当多个消息依次写入队列时,读取时按照 先进先出(FIFO) 的顺序取出:最先写入的消息最先被读出。这一特性保证了消息传递的顺序性,适用于事件通知、数据流传递等场景。

2.3、消息队列的工作原理

可以把消息队列想象成 带编号的信箱

  1. 发送方将消息 放入(Put) 信箱,若信箱已满则阻塞或超时返回;
  2. 接收方从信箱 取出(Get) 消息,若信箱为空则阻塞或超时返回;
  3. 队列内部维护当前消息数量,发送与接收操作会相应增减计数;
  4. 阻塞时间可由用户通过 timeout 参数自行设定,osWaitForever 表示永久等待。

在本实验中,msg_write_thread 扮演 生产者 角色(写入消息),msg_read_thread 扮演 消费者 角色(读取消息),通过消息队列实现线程间的数据传递与同步。

2.4、消息队列与信号量的区别

对比项 消息队列 信号量
传递内容 实际数据(固定大小的消息体) 仅传递同步信号(计数值/token)
数据存储 队列内部缓存多条消息 不存储用户数据,仅维护计数值
典型用途 任务间传递结构化数据、事件通知 资源互斥、任务同步、流量控制
读取行为 取出消息同时拷贝数据到用户缓冲区 获取令牌,不传递具体数据内容

2.5、核心 API 介绍

2.5.1、头文件
#include <stdio.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
2.5.2、常用消息队列 API
API 名称 功能说明
osMessageQueueNew 创建并初始化消息队列
osMessageQueuePut 向队列写入 1 条消息,队列满时阻塞或超时返回
osMessageQueueGet 从队列读取 1 条消息,队列空时阻塞或超时返回
osMessageQueueGetCount 获取队列中当前消息数量
osMessageQueueGetSpace 获取队列中剩余可存放的消息数量
osMessageQueueGetCapacity 获取队列最大消息容量
osMessageQueueGetMsgSize 获取单条消息的最大字节数
osMessageQueueGetName 获取消息队列名称
osMessageQueueReset 将队列重置为初始空状态
osMessageQueueDelete 删除消息队列
2.5.3、osMessageQueueNew — 创建消息队列
osMessageQueueId_t osMessageQueueNew(uint32_t msg_count, uint32_t msg_size,
                                     const osMessageQueueAttr_t *attr);
参数 说明
msg_count 队列最多可存放的消息条数(队列长度)
msg_size 单条消息的字节大小
attr 队列属性(名称等,可为 NULL)

返回值:成功返回消息队列 ID;失败返回 NULL

注意:不能在中断服务程序(ISR)中调用 osMessageQueueNew

2.5.4、osMessageQueuePut — 写入消息
osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id, const void *msg_ptr,
                             uint8_t msg_prio, uint32_t timeout);
参数 说明
mq_id 消息队列 ID
msg_ptr 指向待写入消息数据的指针
msg_prio 消息优先级(本实验设为 0,按 FIFO 顺序处理)
timeout 超时时间(tick 数),osWaitForever 表示永久等待

返回值:osOK 表示写入成功;队列已满且超时则返回 osErrorTimeout

2.5.5、osMessageQueueGet — 读取消息
osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id, void *msg_ptr,
                             uint8_t *msg_prio, uint32_t timeout);
参数 说明
mq_id 消息队列 ID
msg_ptr 指向接收缓冲区的指针,用于存放读出的消息数据
msg_prio 输出参数,存放读出消息的优先级(可为 NULL
timeout 超时时间(tick 数),osWaitForever 表示永久等待

返回值:osOK 表示读取成功;队列为空且超时则返回 osErrorTimeout

2.5.6、APP_FEATURE_INIT — 应用入口注册
APP_FEATURE_INIT(queue_example);

该宏将 queue_example 注册为应用特性初始化函数,系统启动完成后自动调用,无需手动修改 main() 函数。


3、程序设计

3.1、程序架构

本案例文件目录结构

a5_kernel_queue/
├── kernel_queue_example.c   # 主程序源码
├── BUILD.gn                 # GN 编译配置
├── README_zh.md             # 案例简要说明
└── 实验手册.md               # 本实验手册

本案例采用 生产者-消费者 协作结构:

系统启动
    │
    ▼
queue_example()                    ← APP_FEATURE_INIT 注册,系统启动时自动执行
    │
    ├── osMessageQueueNew()        ← 创建消息队列(16 条 unsigned int 消息)
    ├── msg_write_thread()         ← 生产者:每隔约 1 秒写入一条递增消息
    └── msg_read_thread()          ← 消费者:阻塞等待并读取队列消息

3.2、源文件说明

文件 说明
kernel_queue_example.c 主程序源码,包含消息队列创建与读写线程的实现
BUILD.gn GN 编译配置文件,定义静态库 queue_example

3.3、关键代码分析

(1)全局定义与队列句柄
#define MSG_QUEUE_LENGTH 16
static osMessageQueueId_t m_msg_queue;

MSG_QUEUE_LENGTH 定义队列最多存放 16 条消息,全局句柄 m_msg_queue 供读写两个线程共享使用。单条消息类型为 unsigned int(4 字节)。

(2)应用启动入口 — queue_example

系统启动后,通过 APP_FEATURE_INIT 宏自动调用此函数,创建消息队列和两个协作线程:

void queue_example(void)
{
    osThreadAttr_t attr_write = {
        .name = "msg_write_thread",
        .stack_size = 2048,
        .priority = osPriorityNormal + 1,
    };
    osThreadAttr_t attr_read = {
        .name = "msg_read_thread",
        .stack_size = 2048,
        .priority = osPriorityNormal,
    };

    /* 创建消息队列:最多16个 unsigned int 消息 */
    m_msg_queue = osMessageQueueNew(MSG_QUEUE_LENGTH, sizeof(unsigned int), NULL);
    if (m_msg_queue == NULL) {
        osal_printk("Failed to create Message Queue\n");
        return;
    }

    /* 创建写线程 */
    if (osThreadNew(msg_write_thread, NULL, &attr_write) == NULL) {
        osal_printk("Failed to create msg_write_thread\n");
        return;
    }

    /* 创建读线程 */
    if (osThreadNew(msg_read_thread, NULL, &attr_read) == NULL) {
        osal_printk("Failed to create msg_read_thread\n");
        return;
    }
}

写线程优先级略高于读线程(osPriorityNormal + 1),确保在有消息待处理时写操作能优先得到调度。

(3)生产者线程 — msg_write_thread

每隔约 1 秒向队列写入一条递增的无符号整型消息:

void msg_write_thread(void *arg)
{
    (void)arg;
    unsigned int data = 0;
    while (1) {
        data++;
        if (osMessageQueuePut(m_msg_queue, &data, 0, osWaitForever) != osOK) {
            osal_printk("%s write Message Queue msg fail\n", __func__);
        } else {
            osal_printk("%s write Message Queue msg:%u\n", __func__, data);
        }

        /* delay 1s */
        osDelay(100);
    }
}
  • data 从 1 开始递增,每条消息携带当前计数值;
  • osMessageQueuePuttimeout 设为 osWaitForever,队列满时永久阻塞等待;
  • osDelay(100) 延时约 1 秒(本系统 1 tick = 10 ms)。
(4)消费者线程 — msg_read_thread

持续阻塞等待队列消息,读取成功后打印日志:

void msg_read_thread(void *arg)
{
    (void)arg;
    unsigned int data;
    while (1) {
        /* wait for message */
        if (osMessageQueueGet(m_msg_queue, &data, NULL, osWaitForever) == osOK) {
            osal_printk("%s read Message Queue msg:%u\n", __func__, data);
        } else {
            osal_printk("%s read Message Queue fail\n", __func__);
        }
    }
}
  • osMessageQueueGettimeout 设为 osWaitForever,队列为空时永久阻塞等待;
  • 读出的消息值存入局部变量 data,与写线程发送的值一一对应。

3.4、程序执行流程

msg_read_thread 消息队列 msg_write_thread queue_example 系统启动 msg_read_thread 消息队列 msg_write_thread queue_example 系统启动 loop [每约 1 秒] APP_FEATURE_INIT 触发 osMessageQueueNew(16, 4字节) osThreadNew(msg_write_thread) osThreadNew(msg_read_thread) data++ osMessageQueuePut(&data) osDelay(100) 打印写入日志 唤醒阻塞的读线程 osMessageQueueGet(&data) 打印读取日志

3.5、协作行为说明

场景 写线程行为 读线程行为
队列为空 写入消息成功,打印 write ... msg:N 此前阻塞等待,被唤醒后读出消息,打印 read ... msg:N
写线程快于读线程 消息在队列中暂存,按 FIFO 顺序等待被读取 连续读取队列中积压的消息
读写速率一致(本实验) 每 1 秒写入 1 条 几乎同时读出对应消息,编号一一匹配
队列已满(16 条消息) osMessageQueuePut 阻塞等待 持续读取腾出空间后,写线程方可继续写入

本实验中写线程每 1 秒才写入 1 条消息,读线程无额外延时,因此队列中通常最多只有 0~1 条消息,输出呈现 先写后读、编号递增 的规律。


4、编译步骤

4.1、确认案例目录

确认案例已位于 OpenHarmony 源码目录下:

applications/sample/wifi-iot/app/a5_kernel_queue/
├── kernel_queue_example.c
├── BUILD.gn
└── 实验手册.md

若从外部复制,请将 a5_kernel_queue 目录放到上述 app/ 路径下。

4.2、修改 BUILD.gn(注册编译组件)

编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:

lite_component("app") {
  features = [
    "startup",
    "a5_kernel_queue:queue_example",   // 添加此行
  ]
}

4.3、修改 SDK 配置文件

步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py

找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:

"queue_example"

步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake

找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:

"queue_example"

4.4、执行编译

在 OpenHarmony 源码根目录下执行:

rm -rf out
hb set -root .
# 通过上下方向键选择 ws63 对应的编译分支(如 ws63-liteos-app)
hb build -f

编译成功后,固件输出路径通常在 out/ws63/ 目录下。

4.5、烧录固件

使用开发板配套的烧录工具,将编译生成的固件烧写到 LZ3863-星闪开发板。具体烧录步骤请参考开发板用户手册。


5、运行结果

5.1、串口配置

参数
波特率 115200
数据位 8
停止位 1
校验位

5.2、预期输出

烧录并复位开发板后,串口助手应显示类似以下日志:

msg_write_thread write Message Queue msg:1
msg_read_thread read Message Queue msg:1
msg_write_thread write Message Queue msg:2
msg_read_thread read Message Queue msg:2
msg_write_thread write Message Queue msg:3
msg_read_thread read Message Queue msg:3
msg_write_thread write Message Queue msg:4
msg_read_thread read Message Queue msg:4
...

5.3、结果分析

现象 说明
write Message Queue msg:N 写线程成功向队列投递第 N 条消息,N 从 1 开始递增
read Message Queue msg:N 读线程成功从队列取出消息,N 与对应写入值一致
每条 write 后紧跟对应 read 消息队列 FIFO 传递生效,读写线程通过队列正确协作
消息编号持续递增 写线程 data++ 逻辑正常,队列未丢失消息
约每 1 秒出现一对 write/read 日志 osDelay(100) 延时生效,写线程按预期周期运行

5.4、常见问题排查

问题 可能原因 解决方法
无任何串口输出 串口参数错误或线序接错 确认波特率 115200,检查 TX/RX 接线
编译报错找不到 queue_example BUILD.gn 或 config.py 未正确修改 逐步核对 4.2、4.3 节的三处配置
打印 Failed to create Message Queue 系统资源不足或队列参数错误 检查 MSG_QUEUE_LENGTH 和消息大小是否合理
打印 Failed to create msg_write_thread 线程资源耗尽或栈配置不合理 检查是否创建了过多线程,适当增大栈大小
只有 write 日志,无 read 日志 读线程未成功创建 检查启动日志,确认两个线程均已创建
出现 write Message Queue msg fail 队列已满且写入超时(本实验不应出现) 检查读线程是否正常运行,或增大 MSG_QUEUE_LENGTH
出现 read Message Queue fail 队列读取超时(本实验不应出现) 检查写线程是否正常运行,确认队列创建成功
read 的消息编号与 write 不对应 多线程调度异常或队列被其他代码干扰 确认仅本案例在使用该队列,重新烧录固件后复测

6、实验扩展

完成基本实验后,可尝试以下扩展练习:

  1. 修改队列长度:将 MSG_QUEUE_LENGTH 改为 2,加快写线程频率(减小 osDelay),观察队列满时写线程的阻塞行为;
  2. 增加超时机制:将 osMessageQueueGettimeout 改为有限超时(如 50),在超时分支打印"等待超时"提示;
  3. 传递结构体消息:定义包含多个字段的结构体(如 {uint32_t id; uint32_t value;}),修改 msg_size 并观察读写效果;
  4. 增加第二个消费者线程:创建 msg_read_thread2,观察同一队列多读者场景下的消息分配(每条消息仅被一个读者取走);
  5. 查询队列状态:在写线程中调用 osMessageQueueGetCountosMessageQueueGetSpace,打印当前队列占用情况;
  6. 结合中断场景:在中断回调中调用 osMessageQueuePut 向队列投递事件,任务侧读取并处理,体会队列在 ISR 与任务间通信的用法。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐