基于小智派-LZ3863星闪开发版:OpenHarmony 内核互斥锁编程
1、实验简介
参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/a4_kernel_mutex
1.1、实验目的
本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 互斥锁(Mutex) 的基本概念与编程方法。通过本实验,你将学会:
- 理解互斥锁在多任务环境下保护共享资源的作用;
- 使用 CMSIS-RTOS v2 标准接口创建、获取和释放互斥锁;
- 通过读写两个线程协作,体会互斥锁对临界区的独占式访问控制;
- 完成案例代码的编译、烧录与串口调试。
1.2、实验内容
本案例在 LZ3863-星闪开发板上创建 一个互斥锁 和 两个协作线程,共同访问共享变量 m_data:
| 线程名称 | 优先级 | 主要行为 |
|---|---|---|
write_thread |
普通(Normal) | 获取互斥锁 → 写入并递增 m_data → 延时约 3 秒 → 释放互斥锁 |
read_thread |
低于普通(BelowNormal) | 延时约 1 秒后启动 → 获取互斥锁 → 读取 m_data → 延时约 1 秒 → 释放互斥锁 |
write_thread 在持有互斥锁期间长时间占用临界区(约 3 秒),read_thread 尝试获取锁时将被阻塞,直到写线程释放锁后才能读取数据。通过串口日志可以直观看到互斥锁对共享资源的保护效果。
1.3、实验环境
| 项目 | 说明 |
|---|---|
| 硬件 | LZ3863-星闪开发板、USB 数据线 |
| 软件 | OpenHarmony v5.1.0 源码、hb 编译工具 |
| 调试工具 | 串口助手(波特率 115200) |
| 案例路径 | applications/sample/wifi-iot/app/a4_kernel_mutex/ |
2、基础知识
2.1、什么是互斥锁
互斥锁(Mutex,Mutual Exclusion Lock) 也叫做互斥型信号量,是一种特殊的二值同步机制,用于实现对共享资源的 独占式访问。
在任意时刻,互斥锁只有两种状态:
- 开锁(Available):没有任务持有,其他任务可以获取;
- 闭锁(Locked):已有任务持有,其他任务无法获取,只能阻塞等待。
当任务调用 osMutexAcquire 成功获取互斥锁后,该任务进入 临界区,对共享资源的访问受到保护;使用完毕后调用 osMutexRelease 释放锁,其他等待的任务才有机会继续访问。
2.2、互斥锁与信号量的区别
| 对比项 | 互斥锁(Mutex) | 信号量(Semaphore) |
|---|---|---|
| 本质 | 特殊的二值同步对象 | 计数型同步对象 |
| 所有权 | 有所有权,只有持有者才能释放 | 无所有权,任意任务均可释放 |
| 典型用途 | 保护共享资源,实现互斥访问 | 任务同步、资源计数、流量控制 |
| 优先级翻转 | 通常支持优先级继承,可缓解优先级翻转问题 | 二值信号量可能出现优先级翻转 |
| 中断中使用 | 不能 在中断服务程序(ISR)中使用 | 部分场景可用,但需谨慎 |
本实验使用互斥锁保护全局变量 m_data,确保读写操作不会同时发生,避免数据竞争。
2.3、互斥锁的工作原理
可以把互斥锁想象成 卫生间门锁:
- 进入卫生间(访问共享资源)前,先 上锁(Acquire);
- 若门已被他人锁住,后来者只能在门外 排队等待;
- 使用完毕后 开锁(Release),等待队列中的任务获得进入机会;
- 同一时刻 只有一把钥匙,保证独占访问。
在本实验中,write_thread 写入数据时持有锁并延时 3 秒,read_thread 在此期间尝试获取锁会被阻塞,直到写线程释放后才能读取,从而保证读到的数据是完整、一致的。
2.4、核心 API 介绍
2.4.1、头文件
#include "ohos_init.h"
#include "cmsis_os2.h"
#include <stdio.h>
2.4.2、常用互斥锁 API
| API 名称 | 功能说明 |
|---|---|
osMutexNew |
创建并初始化互斥锁 |
osMutexAcquire |
获取互斥锁,若已被占用则阻塞或超时返回 |
osMutexRelease |
释放互斥锁,唤醒等待中的任务 |
osMutexGetName |
获取互斥锁名称 |
osMutexGetOwner |
获取当前持有互斥锁的线程 |
osMutexDelete |
删除互斥锁 |
2.4.3、osMutexNew — 创建互斥锁
osMutexId_t osMutexNew(const osMutexAttr_t *attr);
| 参数 | 说明 |
|---|---|
attr |
互斥锁属性(名称、递归属性等,可为 NULL) |
返回值:成功返回互斥锁 ID;失败返回 NULL。
注意:不能在中断服务程序(ISR)中调用
osMutexNew。
2.4.4、osMutexAcquire — 获取互斥锁
osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout);
| 参数 | 说明 |
|---|---|
mutex_id |
互斥锁 ID |
timeout |
超时时间(tick 数),osWaitForever 表示永久等待 |
返回值:osOK 表示成功获取;超时则返回 osErrorTimeout;若互斥锁已被其他任务持有且未超时,当前任务将阻塞等待。
2.4.5、osMutexRelease — 释放互斥锁
osStatus_t osMutexRelease(osMutexId_t mutex_id);
只有 当前持有该互斥锁的任务 才能成功释放。释放后,若有其他任务正在等待,内核将唤醒其中一个(通常按优先级调度)。
2.4.6、APP_FEATURE_INIT — 应用入口注册
APP_FEATURE_INIT(mutex_example);
该宏将 mutex_example 注册为应用特性初始化函数,系统启动完成后自动调用,无需手动修改 main() 函数。
3、程序设计
3.1、程序架构
本案例文件目录结构:
a4_kernel_mutex/
├── kernel_mutex_example.c # 主程序源码
├── BUILD.gn # GN 编译配置
├── README_zh.md # 案例简要说明
└── 实验手册.md # 本实验手册
本案例采用 读写互斥 协作结构:
系统启动
│
▼
mutex_example() ← APP_FEATURE_INIT 注册,系统启动时自动执行
│
├── osMutexNew() ← 创建互斥锁
├── write_thread() ← 写线程:递增共享数据,持有锁约 3 秒
└── read_thread() ← 读线程:读取共享数据,持有锁约 1 秒
3.2、源文件说明
| 文件 | 说明 |
|---|---|
kernel_mutex_example.c |
主程序源码,包含互斥锁创建与读写两个线程的实现 |
BUILD.gn |
GN 编译配置文件,定义静态库 mutex_example |
3.3、关键代码分析
(1)全局定义
// 互斥锁
static osMutexId_t m_mutex_id;
// 共享数据
static unsigned int m_data = 0;
m_mutex_id 为互斥锁句柄,供读写两个线程共享;m_data 为被保护的共享变量,所有读写操作均需在持有互斥锁的情况下进行。
(2)应用启动入口 — mutex_example
系统启动后,通过 APP_FEATURE_INIT 宏自动调用此函数,创建互斥锁和两个协作线程:
void mutex_example(void)
{
osThreadAttr_t attr1 = {0};
osThreadAttr_t attr2 = {0};
// 创建互斥锁
m_mutex_id = osMutexNew(NULL);
if (m_mutex_id == NULL)
{
printf("Failed to create Mutex\n");
return;
}
// 配置写线程
attr1.name = "write_thread";
attr1.stack_size = 2048;
attr1.priority = osPriorityNormal;
if (osThreadNew(write_thread, NULL, &attr1) == NULL)
{
printf("Failed to create write_thread\n");
return;
}
// 配置读线程
attr2.name = "read_thread";
attr2.stack_size = 2048;
attr2.priority = osPriorityBelowNormal;
if (osThreadNew(read_thread, NULL, &attr2) == NULL)
{
printf("Failed to create read_thread\n");
return;
}
}
APP_FEATURE_INIT(mutex_example);
写线程优先级高于读线程,因此在两者同时就绪等待互斥锁时,写线程优先获得调度机会。
(3)写线程 — write_thread
写线程在循环中获取互斥锁,递增共享数据,打印日志,并在 持有锁期间延时约 3 秒 后才释放:
void write_thread(void *arg)
{
(void)arg;
while (1)
{
if (osMutexAcquire(m_mutex_id, osWaitForever) == osOK)
{
m_data++;
printf("write_thread write data:%u\n", m_data);
// 延时300个系统ticks。系统运行时,tick为10ms
osDelay(300);
osMutexRelease(m_mutex_id);
}
}
}
osDelay(300) 约等于 3000 ms(3 秒)。在延时期间互斥锁仍被持有,其他线程无法访问 m_data。
(4)读线程 — read_thread
读线程启动后先延时约 1 秒,再进入循环尝试获取互斥锁并读取数据:
void read_thread(void *arg)
{
(void)arg;
// 延时100个系统ticks。系统运行时,tick为10ms
osDelay(100);
while (1)
{
if (osMutexAcquire(m_mutex_id, osWaitForever) == osOK)
{
printf("read_thread read data:%u\n", m_data);
// 延时100个系统ticks。系统运行时,tick为10ms
osDelay(100);
osMutexRelease(m_mutex_id);
}
}
}
读线程在写线程持有锁期间调用 osMutexAcquire 会被阻塞;写线程释放锁后,读线程才能进入临界区读取数据。
3.4、程序执行流程
3.5、同步行为说明
| 时间点(约) | write_thread 状态 | read_thread 状态 |
|---|---|---|
| 0 s | 获取锁,写入 data:1,开始延时 3 s | 刚创建,尚未启动读循环 |
| 1 s | 仍持有锁,延时中 | 延时结束,尝试获取锁 → 阻塞 |
| 3 s | 延时结束,释放锁 | 被唤醒,获取锁,读取 data:1 |
| 4 s | 等待获取锁(读线程仍持有) | 延时 1 s 后释放锁 |
| 4 s 后 | 获取锁,写入 data:2,再次延时 3 s | 再次尝试获取锁 → 阻塞,直至写线程释放 |
由于写线程优先级高于读线程,释放锁后写线程通常会更快再次获得锁;读线程则在写线程每次完成写入并释放锁后,才能读取到与之对应的最新值。因此串口输出呈现 先写后读、数值一一对应 的规律。
4、编译步骤
4.1、确认案例目录
确认案例已位于 OpenHarmony 源码目录下:
applications/sample/wifi-iot/app/a4_kernel_mutex/
├── kernel_mutex_example.c
├── BUILD.gn
└── 实验手册.md
若从外部复制,请将 a4_kernel_mutex 目录放到上述 app/ 路径下。
4.2、修改 BUILD.gn(注册编译组件)
编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:
lite_component("app") {
features = [
"startup",
"a4_kernel_mutex:mutex_example", // 添加此行
]
}
4.3、修改 SDK 配置文件
步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py
找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:
"mutex_example"
步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake
找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:
"mutex_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、预期输出
烧录并复位开发板后,串口助手应显示类似以下日志:
write_thread write data:1
read_thread read data:1
write_thread write data:2
read_thread read data:2
write_thread write data:3
read_thread read data:3
write_thread write data:4
read_thread read data:4
...
5.3、结果分析
| 现象 | 说明 |
|---|---|
write_thread write data:N 先出现 |
写线程优先获取互斥锁,完成写入后再释放 |
read_thread read data:N 紧随其后 |
读线程在写线程释放锁后被唤醒,读到的值与刚写入的值相同 |
| 读写数值始终一致(如 write:2 → read:2) | 互斥锁有效保护了共享变量 m_data,读操作不会读到写入过程中的中间状态 |
| 写日志间隔明显长于读日志间隔 | 写线程持有锁期间延时 3 秒,读线程持有锁期间仅延时 1 秒 |
无 Failed to create 类错误日志 |
互斥锁与两个线程均创建成功 |
5.4、常见问题排查
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 无任何串口输出 | 串口参数错误或线序接错 | 确认波特率 115200,检查 TX/RX 接线 |
编译报错找不到 mutex_example |
BUILD.gn 或 config.py 未正确修改 | 逐步核对 4.2、4.3 节的三处配置 |
打印 Failed to create Mutex |
系统互斥锁资源耗尽 | 检查是否有其他模块占用过多互斥锁,或减少互斥锁数量 |
打印 Failed to create write_thread |
线程资源耗尽或栈配置不合理 | 检查是否创建了过多线程,适当增大栈大小 |
| 只有写日志、无读日志 | 读线程未成功创建 | 检查是否打印 Failed to create read_thread |
| 读到的数值与写的不一致 | 未正确使用互斥锁保护共享资源 | 确认 m_data 的读写均在 Acquire/Release 之间进行 |
| 长时间无读日志输出 | 写线程持有锁时间过长(3 秒) | 属正常现象;可缩短 write_thread 中的 osDelay 参数验证 |
6、实验扩展
完成基本实验后,可尝试以下扩展练习:
- 去掉互斥锁:注释掉
osMutexAcquire和osMutexRelease调用,观察读写线程并发访问时输出是否出现数据不一致; - 调整持有锁时间:将写线程的
osDelay(300)改为osDelay(50),观察读写日志交替频率的变化; - 增加超时机制:将
osMutexAcquire(m_mutex_id, osWaitForever)改为有限超时(如100tick),在超时分支打印提示信息; - 查询锁持有者:在读线程获取锁前调用
osMutexGetOwner(m_mutex_id),打印当前持有互斥锁的线程信息; - 对比信号量案例:结合
a2_kernel_semaphore实验,比较信号量同步与互斥锁保护共享资源两种方式的适用场景。
更多推荐



所有评论(0)