1:互斥锁主要用于rtos的线程同步,核心作用是:保证同一时间只有一个线程访问“共享资源”

2:可以把互斥锁想象成公共卫生间的门锁:

  1. 一个人进入卫生间后,会锁上门(对应线程 take 互斥锁);

  2. 其他人想进来,必须在门外等待(对应线程阻塞,直到锁被释放);

  3. 里面的人用完后,打开锁出来(对应线程 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);
    }
}
  • takerelease 都在 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 持有。

  • 继承动作

    1. 内核检测到持有者 t_low 的当前优先级(15)低于申请者 t_high(5)。

    2. 内核将 t_low 的优先级临时修改为 5。

    3. t_high 进入阻塞态(挂起在锁的等待队列)。

    4. 重新调度:此时就绪队列中,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()

    1. 内核将 t_low 恢复为初始优先级 15。

    2. 唤醒等待队列中最高优先级的线程 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

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐