1、实验简介

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

1.1、实验目的

本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 互斥锁(Mutex) 的基本概念与编程方法。通过本实验,你将学会:

  1. 理解互斥锁在多任务环境下保护共享资源的作用;
  2. 使用 CMSIS-RTOS v2 标准接口创建、获取和释放互斥锁;
  3. 通过读写两个线程协作,体会互斥锁对临界区的独占式访问控制;
  4. 完成案例代码的编译、烧录与串口调试。

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、互斥锁的工作原理

可以把互斥锁想象成 卫生间门锁

  1. 进入卫生间(访问共享资源)前,先 上锁(Acquire)
  2. 若门已被他人锁住,后来者只能在门外 排队等待
  3. 使用完毕后 开锁(Release),等待队列中的任务获得进入机会;
  4. 同一时刻 只有一把钥匙,保证独占访问。

在本实验中,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、程序执行流程

互斥锁 m_mutex_id read_thread write_thread mutex_example 系统启动 互斥锁 m_mutex_id read_thread write_thread mutex_example 系统启动 循环往复,write/read 交替输出 APP_FEATURE_INIT 触发 osMutexNew() osThreadNew(write_thread) osThreadNew(read_thread) osMutexAcquire (成功) m_data++,打印 write data:1 osDelay(300) 约 3 秒 osDelay(100) 约 1 秒 osMutexAcquire (阻塞等待) osMutexRelease osMutexAcquire (成功) 打印 read data:1 osDelay(100) 约 1 秒 osMutexRelease osMutexAcquire (成功)

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、实验扩展

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

  1. 去掉互斥锁:注释掉 osMutexAcquireosMutexRelease 调用,观察读写线程并发访问时输出是否出现数据不一致;
  2. 调整持有锁时间:将写线程的 osDelay(300) 改为 osDelay(50),观察读写日志交替频率的变化;
  3. 增加超时机制:将 osMutexAcquire(m_mutex_id, osWaitForever) 改为有限超时(如 100 tick),在超时分支打印提示信息;
  4. 查询锁持有者:在读线程获取锁前调用 osMutexGetOwner(m_mutex_id),打印当前持有互斥锁的线程信息;
  5. 对比信号量案例:结合 a2_kernel_semaphore 实验,比较信号量同步与互斥锁保护共享资源两种方式的适用场景。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐