在多任务并发编程中,互斥量(Mutex)和二进制信号量(Binary Semaphore)是常用的同步原语。它们在功能上有一些相似之处,但也存在明显的区别,其中在 FreeRTOS 中,互斥量通过 “优先级继承” 来解决 “优先级反转” 问题,而二进制信号量则不具备此特性。本文将详细探讨互斥量与二进制信号量的区别,并深入解析优先级反转和优先级继承的概念,通过代码示例帮助读者更好地理解。
二进制信号量本质上是一种特殊的信号量,其计数值只能为 0 或 1。它主要用于实现任务之间或任务与中断之间的同步。例如,一个任务可以等待一个二进制信号量,当信号量被释放(设置为 1)时,等待的任务被唤醒并继续执行。
互斥量用于保护共享资源,确保在同一时刻只有一个任务能够访问该资源。当一个任务获取互斥量时,其他任务如果试图获取相同的互斥量将被阻塞,直到持有互斥量的任务释放它。
优先级反转是指在基于优先级的可抢占调度系统中,低优先级任务持有一个高优先级任务所需的共享资源,导致高优先级任务被阻塞,而此时中优先级任务却能够抢占低优先级任务执行的现象。这违背了高优先级任务应该优先执行的原则,可能会导致系统的实时性得不到保障。
假设我们有三个任务:高优先级任务 Task_High、中优先级任务 Task_Medium 和低优先级任务 Task_Low,以及一个共享资源 Shared_Resource,由互斥量 mutex 保护。
// 共享资源
int Shared_Resource = 0;
// 互斥量
osMutexId_t mutex;
// 低优先级任务
void Task_Low(void *argument)
{
// 获取互斥量
osMutexWait(mutex, osWaitForever);
// 模拟低优先级任务使用共享资源,耗时操作
for (int i = 0; i < 10000; i++)
{
Shared_Resource++;
}
// 释放互斥量
osMutexRelease(mutex);
while (1)
{
// 低优先级任务的其他操作
osDelay(1000);
}
}
// 中优先级任务
void Task_Medium(void *argument)
{
while (1)
{
// 中优先级任务的操作,可能会抢占低优先级任务
osDelay(500);
}
}
// 高优先级任务
void Task_High(void *argument)
{
// 尝试获取互斥量,由于低优先级任务持有互斥量,高优先级任务被阻塞
osMutexWait(mutex, osWaitForever);
// 高优先级任务使用共享资源的操作
Shared_Resource += 100;
// 释放互斥量
osMutexRelease(mutex);
while (1)
{
// 高优先级任务的其他操作
osDelay(100);
}
}在上述代码中,Task_Low 先获取了互斥量并开始操作共享资源。在其操作过程中,Task_High 被调度运行,由于需要获取被 Task_Low 持有的互斥量,Task_High 被阻塞。此时,Task_Medium 由于其优先级高于 Task_Low,可以抢占 Task_Low 执行,导致 Task_High 等待的时间变长,出现了优先级反转现象。
优先级继承是解决优先级反转问题的一种策略。当高优先级任务因低优先级任务持有互斥量而被阻塞时,系统将低优先级任务的优先级提升到与高优先级任务相同(或稍高)的优先级,直到低优先级任务释放互斥量。这样可以避免中优先级任务抢占低优先级任务,保证高优先级任务能够尽快获取到互斥量并执行。
在支持优先级继承的 FreeRTOS 环境中,对上述代码进行修改:
// 共享资源
int Shared_Resource = 0;
// 互斥量,设置为支持优先级继承
osMutexId_t mutex;
// 低优先级任务
void Task_Low(void *argument)
{
// 获取互斥量,此时若高优先级任务需要该互斥量,低优先级任务优先级将提升
osMutexWait(mutex, osWaitForever);
// 模拟低优先级任务使用共享资源,耗时操作
for (int i = 0; i < 10000; i++)
{
Shared_Resource++;
}
// 释放互斥量,低优先级任务优先级恢复
osMutexRelease(mutex);
while (1)
{
// 低优先级任务的其他操作
osDelay(1000);
}
}
// 中优先级任务
void Task_Medium(void *argument)
{
while (1)
{
// 中优先级任务的操作,由于低优先级任务优先级提升,无法抢占
osDelay(500);
}
}
// 高优先级任务
void Task_High(void *argument)
{
// 尝试获取互斥量,若低优先级任务持有,低优先级任务优先级提升后,高优先级任务等待
osMutexWait(mutex, osWaitForever);
// 高优先级任务使用共享资源的操作
Shared_Resource += 100;
// 释放互斥量
osMutexRelease(mutex);
while (1)
{
// 高优先级任务的其他操作
osDelay(100);
}
}在这个修改后的代码中,当 Task_High 尝试获取被 Task_Low 持有的互斥量时,Task_Low 的优先级会被提升,Task_Medium 就无法抢占 Task_Low 的执行,从而减少了 Task_High 的阻塞时间,有效解决了优先级反转问题。
如前面所述,互斥量在 FreeRTOS 中通过优先级继承机制来应对优先级反转,而二进制信号量没有这种机制。在使用二进制信号量保护共享资源时,如果出现类似优先级反转的情况,高优先级任务只能等待低优先级任务释放信号量,期间可能会被中优先级任务长时间抢占,导致系统的实时性下降。
互斥量和二进制信号量在多任务编程中都有着重要的作用,但它们在处理优先级反转问题上存在差异。理解优先级反转和优先级继承的概念,以及互斥量和二进制信号量的区别,对于开发可靠的多任务实时系统至关重要。在实际应用中,应根据系统的需求和特点,合理选择使用互斥量或二进制信号量,以确保系统的稳定性、可靠性和实时性。
“学如逆水行舟,不进则退。”愿此篇文章成为你在技术之舟上的有力浆橹。有任何感悟或困惑,可于评论区交流探讨。若觉有益,点赞,收藏不妨一试,也期待你关注我。在技术的漫漫征途中,愿与君相伴而行,共赏知识繁花盛景,同历成长蜕变之喜。