rt thread 互斥锁的学习
1:互斥锁主要用于rtos的线程同步,核心作用是:保证同一时间只有一个线程访问“共享资源”
2:可以把互斥锁想象成公共卫生间的门锁:
-
一个人进入卫生间后,会锁上门(对应线程
take互斥锁); -
其他人想进来,必须在门外等待(对应线程阻塞,直到锁被释放);
-
里面的人用完后,打开锁出来(对应线程
release互斥锁),下一个人才能进去。
3:互斥锁的主要用途
- 所有权:哪个线程拿到锁,必须由该线程释放。
- 临界区保护:临界区同一时间绝对只允许一个线程执行。
- 优先级继承:如果高优先级线程在等低优先级线程的锁,低优先级线程的优先级会临时提升到和高优先级一样
4:主要函数讲解
1. rt_mutex_create (创建互斥锁)
-
作用: 在系统内核的动态堆(Heap)中划出一块物理内存,初始化一把全新的“锁”。
-
参数 1
name("metex"): 给锁起个字符串名字,主要为了调试时方便在终端里查它的状态。 -
参数 2
flag(RT_IPC_FLAG_PRIO): 极其关键!PRIO代表**“优先级继承”**机制。如果一个低优先级线程拿了锁,高优先级线程在等锁,操作系统会把低优先级线程的优先级强行提升,逼它赶紧干完活解锁,防止系统发生“优先级反转”的死锁灾难。 -
返回值: 返回一个
rt_mutex_t类型的指针(句柄)。如果系统内存耗尽创建失败,会返回RT_NULL。
2. rt_mutex_take (获取锁 / 上锁)
-
作用: 尝试把锁拿走。如果这把锁当前没人用,直接拿走,往下执行;如果锁已经被别的线程拿了,当前线程立刻交出 CPU 控制权,进入阻塞休眠状态。
-
参数 1
mutex(mutex_t): 你要申请的那把锁的句柄。 -
参数 2
time(RT_WAITING_FOREVER): 愿意等多久。传入RT_WAITING_FOREVER意味着“只要拿不到锁,我就死等,哪怕等到宇宙毁灭”。如果填数字比如100,意味着等 100 个 Tick 后如果还没拿到,我就不等了,直接往下走。 -
返回值:
-
RT_EOK(0):成功拿到锁。 -
-RT_ETIMEOUT:超时了,没拿到锁。在实战中,如果不死等,必须判断这个返回值!
-
3. rt_mutex_release (释放锁 / 解锁)
-
作用: 归还这把锁。内核会瞬间去查看“等锁休眠链表”,如果有其他线程正在排队等这把锁,内核会立刻唤醒排在第一位的线程,并把锁交给它。
-
参数 1
mutex(mutex_t): 你要归还的那把锁。 -
返回值: 永远返回
RT_EOK。 -
铁血规定: 谁上的锁,必须由谁来解! 这是互斥量(Mutex)和二值信号量(Semaphore)在底层物理逻辑上最大的区别。
5:代码例子
例子一:对时间结构体进行访问与读取,在时间增加的过程中,不允许被打断。在读取时间的过程中,不允许被打断。
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#define LED_1_PIN GET_PIN(B, 1)
#define LED_0_PIN GET_PIN(B, 0)
// 1. 定义时间结构体
typedef struct {
int year;
int month;
int day;
int hour;
int min;
int sec;
} time_struct;
// 2. 全局共享资源(临界数据)
static time_struct time = {2026, 3, 19, 11, 12, 35};
// 3. 声明全局互斥量句柄
rt_mutex_t mutex_t;
// ---------------------------------------------------------
// 线程 1:写线程(负责模拟时钟,每秒钟将时间累加)
// ---------------------------------------------------------
void task_writer(void *bb)
{
while(1)
{
// 【加锁】:申请进入临界区
rt_mutex_take(mutex_t, RT_WAITING_FOREVER);
// ===== 绝对安全的临界区 开始 =====
time.sec++;
if(time.sec > 59)
{
time.sec = 0;
time.min++;
}
if(time.min > 59)
{
time.min = 0;
time.hour++;
}
if(time.hour > 23)
{
time.hour = 0;
time.day++;
}
// ===== 绝对安全的临界区 结束 =====
// 【解锁】:修改完毕,立刻交出互斥量
rt_mutex_release(mutex_t);
// 👑【核心修复 1】:必须延时!
// 延时 1000 毫秒(1秒)。这不仅是模拟真实钟表的走动速度,
// 更重要的是把 CPU 的控制权交还给操作系统,让其他线程(读线程)有机会运行。
rt_thread_mdelay(1000);
}
}
// ---------------------------------------------------------
// 线程 2:读线程(负责安全地读取并打印屏幕)
// ---------------------------------------------------------
void task_read(void *cc)
{
time_struct aa; // 局部变量,存在该线程的独立栈中
while(1)
{
// 【加锁】:申请进入临界区,准备拷贝数据
rt_mutex_take(mutex_t, RT_WAITING_FOREVER);
// ===== 临界区 =====
aa = time; // 瞬间完成结构体的全量内存拷贝
// 【解锁】:数据已经安全拿到了自己的私有房间,立刻解锁,不耽误写线程工作
rt_mutex_release(mutex_t);
// 离开临界区后,再从容地进行极其耗时的串口打印操作
// 优化:%02d 表示如果数字是一位数(如 3),前面自动补 0(变成 03),非常规整
rt_kprintf("%d/%02d/%02d %02d:%02d:%02d \n",
aa.year, aa.month, aa.day, aa.hour, aa.min, aa.sec);
// 👑【核心修复 2】:必须延时!
// 延时 500 毫秒(一秒钟刷新两次屏幕)。防止这个 while(1) 疯狂全速打印,
// 瞬间填满串口发送缓冲区并把系统彻底卡死。
rt_thread_mdelay(500);
}
}
// ---------------------------------------------------------
// 主函数:系统初始化
// ---------------------------------------------------------
int main(void)
{
// 1. 创建互斥量:开启优先级继承机制 (RT_IPC_FLAG_PRIO)
mutex_t = rt_mutex_create("metex", RT_IPC_FLAG_PRIO);
if (mutex_t == RT_NULL)
{
rt_kprintf("互斥量创建失败!\n");
return -1;
}
// 2. 创建并启动写线程
rt_thread_t T1 = rt_thread_create("task1", task_writer, RT_NULL, 1024, 12, 10);
if (T1 != RT_NULL) {
rt_thread_startup(T1);
}
// 3. 创建并启动读线程
rt_thread_t T2 = rt_thread_create("task2", task_read, RT_NULL, 1024, 13, 10);
if (T2 != RT_NULL) {
rt_thread_startup(T2);
}
return RT_EOK;
}
代码分析:
写线程:
void task_writer(void *bb)
{
while(1)
{
// 【加锁】:申请进入临界区
rt_mutex_take(mutex_t, RT_WAITING_FOREVER);
// ===== 绝对安全的临界区 开始 =====
time.sec++;
if(time.sec > 59)
{
time.sec = 0;
time.min++;
}
if(time.min > 59)
{
time.min = 0;
time.hour++;
}
if(time.hour > 23)
{
time.hour = 0;
time.day++;
}
// ===== 绝对安全的临界区 结束 =====
// 【解锁】:修改完毕,立刻交出互斥量
rt_mutex_release(mutex_t);
// 👑【核心修复 1】:必须延时!
// 延时 1000 毫秒(1秒)。这不仅是模拟真实钟表的走动速度,
// 更重要的是把 CPU 的控制权交还给操作系统,让其他线程(读线程)有机会运行。
rt_thread_mdelay(1000);
}
}
-
take和release都在while(1)内部:确保 “每次修改数据都有锁保护”,不会出现 “改到一半被其他线程打断” 的情况。 -
RT_WAITING_FOREVER:如果锁被读线程拿着,写线程会一直等,直到拿到锁为止。
读线程:
void task_read(void *cc)
{
time_struct aa; // 局部变量,存在该线程的独立栈中
while(1)
{
// 【加锁】:申请进入临界区,准备拷贝数据
rt_mutex_take(mutex_t, RT_WAITING_FOREVER);
// ===== 临界区 =====
aa = time; // 瞬间完成结构体的全量内存拷贝
// 【解锁】:数据已经安全拿到了自己的私有房间,立刻解锁,不耽误写线程工作
rt_mutex_release(mutex_t);
// 离开临界区后,再从容地进行极其耗时的串口打印操作
// 优化:%02d 表示如果数字是一位数(如 3),前面自动补 0(变成 03),非常规整
rt_kprintf("%d/%02d/%02d %02d:%02d:%02d \n",
aa.year, aa.month, aa.day, aa.hour, aa.min, aa.sec);
// 👑【核心修复 2】:必须延时!
// 延时 500 毫秒(一秒钟刷新两次屏幕)。防止这个 while(1) 疯狂全速打印,
// 瞬间填满串口发送缓冲区并把系统彻底卡死。
rt_thread_mdelay(500);
}
}
-
先拿锁,再读取
time:确保 “读取数据时,写线程不会同时修改”,避免读到 “改了一半的脏数据”(比如秒数从 59 改成 0,但分钟还没加 1)。
二:优先级继承例子
#include <rtthread.h>
#define PRIO_HIGH 5
#define PRIO_MID 10
#define PRIO_LOW 15
static rt_mutex_t test_mutex = RT_NULL;
/* 👑 核心重写:只计算自己“实打实”拥有 CPU 的工作时间 */
static void busy_wait_real_work(rt_uint32_t seconds)
{
rt_tick_t target_work_ticks = seconds * RT_TICK_PER_SECOND; // 需要干活的总 Tick 数
rt_tick_t valid_work_ticks = 0; // 已经干了多少活
rt_tick_t last_tick = rt_tick_get();
while (valid_work_ticks < target_work_ticks)
{
rt_tick_t current_tick = rt_tick_get();
rt_tick_t delta = current_tick - last_tick;
if (delta > 0)
{
// 如果 delta 很小 (比如 <= 2),说明我是连贯执行的,这段时间归我!
if (delta <= 2)
{
valid_work_ticks += delta;
}
// ⚠️ 如果 delta 非常大 (比如被 t_mid 抢占了 1 秒,delta就是 1000)
// 它就不满足 delta <= 2。这 1000 个 Tick 会被无情抛弃,不计入有效工作量!
// 刷新上一次的记录点,继续死循环干活
last_tick = current_tick;
}
}
}
/* 1. 低优先级线程 (优先级 15) */
static void thread_low_entry(void *param)
{
rt_kprintf("[0s] t_low 启动,尝试获取互斥锁...\n");
rt_mutex_take(test_mutex, RT_WAITING_FOREVER);
rt_kprintf("[0s] t_low 拿到锁!必须实打实搬完 6 秒的砖...\n");
/* 此时它必须实打实霸占 CPU 满 6 秒才会退出 */
busy_wait_real_work(6);
rt_kprintf("[7s] t_low 6秒的砖终于搬完了!释放互斥锁。\n");
rt_mutex_release(test_mutex);
}
/* 2. 中优先级线程 (优先级 10) */
static void thread_mid_entry(void *param)
{
rt_thread_mdelay(1000); // 晚 1 秒启动
rt_kprintf("[1s] t_mid 启动!因为优先级(10)>t_low(15),我抢占了 CPU!\n");
rt_kprintf("[1s] t_mid 开始实打实搬 4 秒的砖...\n");
busy_wait_real_work(4);
rt_kprintf("[11s] t_mid 计算完成!\n");
}
/* 3. 高优先级线程 (优先级 5) */
static void thread_high_entry(void *param)
{
rt_thread_mdelay(2000); // 晚 2 秒启动
rt_kprintf("[2s] t_high 启动!我优先级最高(5),我抢占了 CPU!\n");
rt_kprintf("[2s] t_high 我需要互斥锁,尝试获取...\n");
rt_mutex_take(test_mutex, RT_WAITING_FOREVER);
rt_kprintf("[7s] t_high 终于拿到锁了!快速执行 1 秒计算...\n");
busy_wait_real_work(1);
rt_kprintf("[8s] t_high 计算完成,释放锁并退出。\n");
rt_mutex_release(test_mutex);
}
int main(void)
{
test_mutex = rt_mutex_create("mutex", RT_IPC_FLAG_PRIO);
rt_thread_t t_low = rt_thread_create("low", thread_low_entry, RT_NULL, 1024, PRIO_LOW, 10);
rt_thread_startup(t_low);
rt_thread_t t_mid = rt_thread_create("mid", thread_mid_entry, RT_NULL, 1024, PRIO_MID, 10);
rt_thread_startup(t_mid);
rt_thread_t t_high = rt_thread_create("high", thread_high_entry, RT_NULL, 1024, PRIO_HIGH, 10);
rt_thread_startup(t_high);
return 0;
}

这份日志完整展示了实时操作系统(RTOS)中**优先级继承(Priority Inheritance)处理优先级翻转(Priority Inversion)**问题的底层物理过程。
以下是对该过程的逻辑定量分析及内核实现机制的硬核解析。
一、 时间轴逻辑定量分析
假设系统时钟节拍(Tick)为 1ms,线程采用占先式调度。
1. [0s - 1s]:低优先级独占阶段
-
状态:
t_low处于就绪态,其他线程延时中。 -
动作:
t_low锁定互斥锁(Mutex),占用 CPU 开始执行。 -
消耗:
t_low累计完成 1s 实际工作。剩余工作量:5s。
2. [1s - 2s]:中优先级抢占阶段
-
状态:
t_mid延时结束,进入就绪态。 -
调度:由于
10 > 15,内核触发调度,t_mid抢占 CPU。t_low被迫进入就绪态,且仍持有锁。 -
消耗:
t_mid累计完成 1s 实际工作。剩余工作量:3s。t_low进度停滞。
3. [2s]:优先级继承触发瞬间
-
状态:
t_high延时结束,抢占t_mid。 -
冲突:
t_high调用rt_mutex_take()。内核检测到锁被t_low持有。 -
继承动作:
-
内核检测到持有者
t_low的当前优先级(15)低于申请者t_high(5)。 -
内核将
t_low的优先级临时修改为 5。 -
t_high进入阻塞态(挂起在锁的等待队列)。 -
重新调度:此时就绪队列中,
t_low优先级为 5,t_mid为 10。
-
-
结果:
t_low凭借“继承”来的优先级 5,反向抢占t_mid,重新获得 CPU。
4. [2s - 7s]:优先级继承运行阶段
-
动作:
t_low(此时 Prio 5)继续执行剩余的 5s 工作。 -
消耗:到 7s 时,
t_low完成所有 6s 任务。 -
释放:
t_low调用rt_mutex_release()。-
内核将
t_low恢复为初始优先级 15。 -
唤醒等待队列中最高优先级的线程
t_high。
-
5. [7s - 8s]:高优先级完成阶段
-
动作:
t_high拿到锁,执行其 1s 的任务。 -
结果:8s 时,
t_high执行完毕并退出。
6. [8s - 11s]:中优先级恢复阶段
-
状态:此时
t_high已退出,t_low优先级为 15。就绪队列中最高优先级为t_mid(Prio 10)。 -
调度:
t_mid恢复执行。 -
消耗:执行剩余的 3s 任务。
8s + 3s = 11s。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)