互斥锁的所有权:
互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起,这个特性与一般的二值信号量有很大的不同:在信号量中,线程递归持有会发生主动挂起(最终形成死锁)。
一、为什么互斥量不能在中断服务例程中使用?
首先必须明确一点,无论是在裸机还是RTOS实时操作系统,在中断服务ISR中,都要求中断处理快速执行,绝对不允许中断出现任何阻塞的操作,否则会影响确保其他任务的实时运行,破坏系统的稳定性。
对于任何RTOS实时操作系统,在中断服务ISR中,无论是对于互斥锁 Mutex 还是 信号量 sem,绝对不允许出现任何可能阻塞中断处理的行为和操作。
即:坚决不能在中断服务ISR中,调用 take mutex 或者 take sem。因为当其他线程中一旦已经获取 mutex 或 sem 时,当中断 INT 打断线程,进行中断上下文切换时,在中断服务ISR中,执行take mutex 或者 take sem操作,将使的整个中断进入阻塞状态。严重影响破坏整个系统的实时性和稳定性。
1. 根本原因:阻塞特性
互斥量(mutex)的核心特性是可能引发阻塞:
当互斥量已被占用时,
rt_mutex_take()
会阻塞当前线程中断上下文不允许阻塞(必须快速执行并退出),因为 阻塞操作会触发线程调度
rt_schedule(),保存线程切换时的上下文到TCB
,但是 中断上下文没有线程控制块TCB可被调度使用。
2. 中断上下文限制
中断服务例程 ISR 运行在中断上下文中,在中断中是没有所谓的线程上下文的(例如当前线程控制块),而互斥量的操作依赖于 Thread 线程上下文(eg:记录持有hold互斥量的当前线程)
同时源码中明确表示,在中断中禁用:
3. 优先级继承的不可行性
互斥量有优先级继承机制:
中断没有"线程任务优先级"的概念,无法参与优先级继承
中断没有线程控制块 TCB(
struct rt_thread
),没有办法去记录线程的优先级相关参数
总结: 为什么中断中不可以释放互斥锁(mutex)和 获取 mutex?
互斥锁具有所有权概念,只有持有锁的线程才能释放它。中断上下文没有线程控制块(即没有线程身份),因此无法满足互斥锁的所有权要求。此外,互斥锁的释放操作可能涉及优先级继承的解除,这需要修改持有锁的线程的优先级,而中断上下文无法安全地执行这些操作(因为可能涉及调度,而中断中不能调度)。
二、线程多次持有互斥量的理解
1. 递归锁特性
互斥量有一个特性叫做 " 递归锁(Recursive Lock)",即同一个线程可以多次获取(take)同一个互斥量,而不会造成死锁。每次获取互斥量后,必须对应相同次数的释放(release)操作,互斥量才会真正被释放给其他线程。
例如如下使用:
rt_mutex_t mutex;
void thread_function(void)
{// 第一次获取互斥量rt_mutex_take(mutex, RT_WAITING_FOREVER);// ... 执行一些操作 ...// 第二次获取同一个互斥量(同一个线程)rt_mutex_take(mutex, RT_WAITING_FOREVER); // 这里不会阻塞,因为已经持有该互斥量// ... 更多操作 ...// 第一次释放互斥量(互斥量仍然被持有,因为获取了两次)rt_mutex_release(mutex);// 第二次释放互斥量,此时互斥量才真正被释放rt_mutex_release(mutex);
}
Mutex 为什么需要多次获取?
这种情况通常出现在递归函数中,或者在一个函数中调用了另一个函数,而这两个函数都需要操作同一个互斥量保护的数据。例如:
void function_a(void)
{rt_mutex_take(mutex, RT_WAITING_FOREVER);function_b(); // 这个函数内部也会获取同一个互斥量rt_mutex_release(mutex);
}
void function_b(void)
{rt_mutex_take(mutex, RT_WAITING_FOREVER);// 做一些操作rt_mutex_release(mutex);
}
如果没有递归锁的特性,那么当线程在`function_a`中已经持有互斥量,再进入`function_b`尝试获取同一个互斥量时,就会发生死锁(因为线程会等待自己释放互斥量)。而递归锁允许这种情况发生,因为同一个线程多次获取同一个互斥量是允许的。
2. 递归深度
RT-Thread的互斥量支持有限次数的递归获取(具体次数由`RT_MUTEX_VALUE_MAX`定义,默认是0xFFFF)。超过这个次数会导致获取失败。
3. 释放次数
必须确保 互斥锁mutex 获取和释放的次数匹配,否则会导致互斥量无法被其他线程获取,或者提前释放导致其他线程错误地获取到互斥量。