1. 实验目的
在多任务系统中,多个任务可能需要访问同一个共享资源。如果多个任务同时访问该资源,就可能产生数据竞争、资源冲突等问题。
HRTOS 提供了 Mutex(互斥锁) 机制,用于保证同一时刻只有一个任务能够进入受保护的资源访问区域。
本实验创建一个低优先级任务和一个高优先级任务,让两个任务竞争同一个 Mutex,并通过任务延时模拟共享资源的访问过程。同时演示当高优先级任务等待低优先级任务持有的 Mutex 时,系统执行优先级继承的调度机制。
2. 实验整体设计
本实验包含两个任务:
| 任务 | 优先级 | 作用 |
|---|---|---|
low_task |
3 | 获取 Mutex 并访问共享资源 |
high_task |
6 | 尝试获取同一个 Mutex |
其中:
#define MUTEX_KEY 1
两个任务使用相同的 Mutex,因此它们实际上是在竞争同一个资源访问权。
共享变量:
static u8 mutex_count = 0;
低优先级任务成功获取 Mutex 后,对该变量进行操作,并通过 LED 状态变化观察任务运行情况。
3. Mutex 与 Semaphore 的区别
虽然 Mutex 和 Semaphore 都可以用于任务之间的同步,但它们的设计目的并不完全相同。
Semaphore(信号量) 更适合用于:
-
任务同步
-
资源数量管理
-
任务通知
而 Mutex(互斥锁) 主要用于:
-
保护共享资源
-
保证互斥访问
-
处理任务之间的资源竞争
-
配合优先级继承解决优先级反转问题
例如两个任务同时访问一个共享变量:
mutex_count++;
如果没有互斥保护,多个任务同时执行相关操作,就可能产生竞争。
使用 Mutex 后,访问流程变成:
获取 Mutex
↓
访问共享资源
↓
释放 Mutex
在 Mutex 被占用期间,其他任务不能同时进入受保护的资源访问区域。
4. 低优先级任务设计
低优先级任务的优先级为:
#define PRIO_LOW 3
任务启动后首先点亮 LED:
LED_LOW = 1;
然后进入无限循环。
核心代码:
if (os_mutex_lock(MUTEX_KEY) == 1)
{
mutex_count++;
LED_LOW = ~LED_LOW;
os_delay(TICK_LOW);
os_mutex_unlock(MUTEX_KEY);
}
整个过程可以分成四个步骤。
第一步:获取 Mutex
os_mutex_lock(MUTEX_KEY)
任务首先尝试获取 Mutex。
只有成功获取 Mutex 后,任务才能进入受保护的资源访问区域。
第二步:访问共享资源
mutex_count++;
此处模拟对共享资源的访问。
实际项目中,这里可以替换成:
-
全局变量操作
-
外设访问
-
数据结构修改
-
缓冲区操作
-
总线资源访问
第三步:模拟资源占用
LED_LOW = ~LED_LOW;
os_delay(TICK_LOW);
这里通过延时模拟任务持有 Mutex 一段时间。
也就是说:
获取 Mutex
↓
占用共享资源
↓
执行一段时间
↓
释放 Mutex
第四步:释放 Mutex
os_mutex_unlock(MUTEX_KEY);
资源访问完成以后必须释放 Mutex。
只有释放之后,其他等待该 Mutex 的任务才有机会获得资源。
5. 高优先级任务设计
高优先级任务的优先级为:
#define PRIO_HIGH 6
明显高于低优先级任务:
HIGH = 6
LOW = 3
高优先级任务首先通过:
os_delay(TICK_HIGH);
等待一段时间,使低优先级任务有机会先获取 Mutex。
随后执行:
if (os_mutex_lock(MUTEX_KEY) == 1)
尝试获取同一个 Mutex。
6. 优先级继承
这个示例最重要的地方之一,就是 Mutex 不仅仅用于"锁住资源"。
当出现下面这种情况:
高优先级任务
│
│ 等待 Mutex
↓
低优先级任务
│
│ 持有 Mutex
↓
共享资源
高优先级任务虽然优先级更高,但因为 Mutex 已经被低优先级任务持有,所以它无法直接访问共享资源。
这时候就可能出现优先级反转问题。
例如:
HIGH(优先级6)
│
│ 等待 Mutex
↓
LOW(优先级3)
│
│ 持有 Mutex
↓
共享资源
如果系统中还有其他优先级介于两者之间的任务,它可能抢占 LOW,使 LOW 长时间无法运行。
最终结果就是:
HIGH 等待 LOW
LOW 又被其他任务阻塞
这就是典型的优先级反转场景。
HRTOS 的 Mutex 在这种竞争情况下执行优先级继承,使持有 Mutex 的低优先级任务临时继承等待者的高优先级,从而尽快完成资源访问并释放 Mutex。
逻辑可以理解为:
HIGH 等待 Mutex
↓
LOW 持有 Mutex
↓
LOW 临时继承 HIGH 的优先级
↓
LOW 尽快执行
↓
释放 Mutex
↓
HIGH 获得 Mutex
↓
恢复正常调度
这样可以明显降低高优先级任务因为低优先级任务持有资源而产生的额外等待。
7. 高优先级任务获得 Mutex 后
当:
os_mutex_lock(MUTEX_KEY) == 1
成立时,说明高优先级任务已经成功获得 Mutex。
然后:
LED_HIGH = ~LED_HIGH;
翻转高优先级任务对应的 LED。
再通过:
os_delay(20);
模拟访问共享资源。
最后:
os_mutex_unlock(MUTEX_KEY);
释放 Mutex。
这里有一个非常重要的原则:
只有成功获取 Mutex 的任务才能释放该 Mutex。
因此代码将 os_mutex_unlock() 放在成功获取 Mutex 的条件内部。
这也是使用 Mutex 时非常重要的编程习惯。
8. 系统初始化
系统入口:
void hrtos_main(void)
首先初始化 Mutex:
if (os_mutex_init(MUTEX_KEY) != 1)
{
while (1);
}
初始化成功后创建低优先级任务:
if (os_task_create(low_task, TASK_LOW, PRIO_LOW, STACK_LOW) != 1)
{
while (1);
}
然后创建高优先级任务:
if (os_task_create(high_task, TASK_HIGH, PRIO_HIGH, STACK_HIGH) != 1)
{
while (1);
}
因此系统启动后的基本结构为:
HRTOS
│
┌────────┴────────┐
│ │
LOW Task HIGH Task
Priority 3 Priority 6
│ │
└───────┬─────────┘
│
Mutex #1
│
共享资源
9. 完整程序
#include "hrtos.h"
/*==================== 配置 ====================*/
#define TASK_LOW 1
#define TASK_HIGH 2
#define PRIO_LOW 3
#define PRIO_HIGH 6
#define STACK_LOW 5
#define STACK_HIGH 5
#define MUTEX_KEY 1
#define TICK_LOW 50
#define TICK_HIGH 100
/*==================== LED 定义 ====================*/
sbit LED_LOW = P1^0; /* 低优先级任务指示灯 */
sbit LED_HIGH = P1^1; /* 高优先级任务指示灯 */
/*==================== 全局 ====================*/
static u8 mutex_count = 0;
/*==================== 低优先级任务 ====================*/
static void low_task(void)
{
LED_LOW = 1;
while (1)
{
/*
* 获取互斥锁
*/
if (os_mutex_lock(MUTEX_KEY) == 1)
{
mutex_count++;
/*
* 持有互斥锁,模拟访问共享资源
*/
LED_LOW = ~LED_LOW;
os_delay(TICK_LOW);
/*
* 释放互斥锁
*/
os_mutex_unlock(MUTEX_KEY);
}
os_delay(20);
}
}
/*==================== 高优先级任务 ====================*/
static void high_task(void)
{
LED_HIGH = 1;
while (1)
{
/*
* 等待低优先级任务持有互斥锁
*/
os_delay(TICK_HIGH);
/*
* 尝试获取互斥锁
*
* 如果互斥锁已经被低优先级任务占用,
* 系统执行优先级继承。
*/
if (os_mutex_lock(MUTEX_KEY) == 1)
{
/*
* 高优先级任务获得互斥锁
*/
LED_HIGH = ~LED_HIGH;
/*
* 模拟访问共享资源
*/
os_delay(20);
/*
* 只有成功获取锁的任务才能释放
*/
os_mutex_unlock(MUTEX_KEY);
}
}
}
/*==================== 系统入口 ====================*/
void hrtos_main(void)
{
if (os_mutex_init(MUTEX_KEY) != 1)
{
while (1);
}
if (os_task_create(low_task, TASK_LOW, PRIO_LOW, STACK_LOW) != 1)
{
while (1);
}
if (os_task_create(high_task, TASK_HIGH, PRIO_HIGH, STACK_HIGH) != 1)
{
while (1);
}
}
10. 程序运行逻辑
整个实验可以概括为:
系统启动
↓
初始化 Mutex
↓
创建 LOW / HIGH 两个任务
↓
LOW 获取 Mutex
↓
LOW 访问共享资源
↓
HIGH 尝试获取 Mutex
↓
如果 Mutex 被 LOW 持有
↓
发生资源竞争
↓
执行优先级继承
↓
LOW 完成资源访问
↓
LOW 释放 Mutex
↓
HIGH 获得 Mutex
↓
HIGH 访问共享资源
↓
HIGH 释放 Mutex
↓
继续循环
这个过程体现了 HRTOS 中任务调度与资源同步相结合的特点。
11. 与裸机开发的区别
在传统裸机程序中,如果多个执行流程需要访问共享资源,通常需要开发者自行设计保护机制。
例如可能通过:
flag = 1;
或者关闭中断等方式实现简单的临界区保护。
这种方式在简单程序中可以工作,但随着程序复杂度增加,就需要开发者自行处理:
-
谁占用了资源
-
谁正在等待资源
-
什么时候释放资源
-
多个执行流程如何同步
-
优先级反转如何处理
而在 HRTOS 中,可以直接使用:
os_mutex_lock()
和:
os_mutex_unlock()
建立明确的资源访问边界。
因此程序结构从:
代码逻辑
+ 自己维护各种资源状态
逐渐转变为:
任务
↓
Mutex
↓
共享资源
这种结构更加适合复杂的多任务应用。
12. AI 编写 HRTOS 程序时需要注意什么
这个示例对于 AI 生成 HRTOS 应用程序尤其具有参考价值。
AI 在生成 RTOS 代码时,很容易根据 FreeRTOS、RT-Thread、μC/OS 等其他系统的经验进行推测。
例如看到:
os_mutex_lock()
就直接假设它具有其他 RTOS 中对应 API 的完全相同语义。
这种做法并不可靠。
HRTOS 应用程序应该首先遵循 HRTOS 自身的 API 和调度机制。
本示例至少需要明确以下规则:
-
Mutex 必须先初始化。
-
任务通过
os_mutex_lock()获取 Mutex。 -
只有成功获取 Mutex 后才能访问受保护资源。
-
资源访问完成后调用
os_mutex_unlock()。 -
未成功获取 Mutex 的任务不能释放它。
-
高优先级任务与低优先级任务竞争 Mutex 时,需要考虑 HRTOS 的优先级继承机制。
-
不应简单套用其他 RTOS 的 Mutex API 和内部语义。
因此,对于 HRTOS 这样的专用 RTOS,AI 最重要的不是"知道 RTOS 是什么",而是准确理解 HRTOS 本身是怎么工作的。
13. 总结
本实验通过一个低优先级任务和一个高优先级任务,演示了 HRTOS Mutex 的基本使用方式。
核心流程为:
Mutex 初始化
↓
任务获取 Mutex
↓
访问共享资源
↓
释放 Mutex
同时,通过高、低优先级任务竞争同一个 Mutex,进一步体现了优先级继承机制在解决优先级反转问题中的作用。
相比信号量示例,Mutex 更强调资源所有权和互斥访问。这也是 HRTOS 从简单任务调度进一步走向复杂实时应用时非常重要的一项基础机制。
对于开发者和 AI 而言,理解这些机制的关键,是从 HRTOS 自身的 API、任务模型和调度规则出发,而不是简单将其他 RTOS 的经验直接套用到 HRTOS 上。