1. 优先级反转
举个栗子:假设任务 A、B 都想使用串口,A 优先级比较低
- 任务 A 获得了串口的互斥量
- 任务 B 也想使用串口,它将会阻塞、等待 A 释放互斥量
- 高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)
如果涉及 3 个任务,可以让"优先级反转"的后果更加恶劣。
使用互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是
FreeRTOS 中互斥量和二级制信号量的差别。
1.1 代码实例分析:
main 函数创建了 3 个任务:LPTask/MPTask/HPTask(低/中/高优先级任务)
/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
int main(void)
{
prvSetupHardware();
/* 创建互斥量/二进制信号量 */
xLock = xSemaphoreCreateBinary();
if (xLock != NULL)
{
/* 创建 3 个任务: LP,MP,HP(低/中/高优先级任务)
*/
xTaskCreate(vLPTask, "LPTask", 1000, NULL, 1, NULL);
xTaskCreate(vMPTask, "MPTask", 1000, NULL, 2, NULL);
xTaskCreate(vHPTask, "HPTask", 1000, NULL, 3, NULL);
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建互斥量/二进制信号量 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
LPTask/MPTask/HPTask 三个任务的代码和运行过程如下图所示:
过程:
- A:HPTask 优先级最高,它最先运行。在这里故意打印,这样才可以观察到 flagHPTaskRun 的脉冲。
- HP Delay:HPTask 阻塞
- B:MPTask 开始运行。在这里故意打印,这样才可以观察到 flagMPTaskRun 的脉冲。
- MP Delay:MPTask 阻塞
- C:LPTask 开始运行,获得二进制信号量,然后故意打印很多字符
- D:HP Delay 时间到,HPTask 恢复运行,它无法获得二进制信号量,一直阻塞等待
- E:MP Delay 时间到,MPTask 恢复运行,它比 LPTask 优先级高,一直运行。导致 LPTask 无法运行,自然无法释放二进制信号量,于是 HPTask 用于无法运行。
总结:
- LPTask 先持有二进制信号量,但是 MPTask 抢占 LPTask,使得 LPTask 一直无法运行也就无法释放信号量
- 由于LPTask 无法释放信号量导致 HPTask 任务无法运行,优先级最高的 HPTask 竟然一直无法运行
程序运行的时序图如下:
2. 优先级继承
优先级反转的问题在于,LPTask 低优先级任务获得了锁,但是它优先级太低而无法运行。如果能提升 LPTask 任务的优先级,让它能尽快运行、释放锁,"优先级反转"的问题不就解决了吗?
2.1 优先级继承案例
- 假设持有互斥锁的是任务 A,如果更高优先级的任务 B 也尝试获得这个锁
- 任务 B 说:你既然持有宝剑,又不给我,那就继承我的愿望吧,于是任务 A 就继承了任务 B 的优先级
- 这就叫:优先级继承
- 等任务 A 释放互斥锁时,它就恢复为原来的优先级
- 互斥锁内部就实现了优先级的提升、恢复
2.2 代码实例分析:
/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
int main(void)
{
prvSetupHardware();
/* 创建互斥量/二进制信号量 */
//xLock = xSemaphoreCreateBinary( );
xLock = xSemaphoreCreateMutex( );
if (xLock != NULL)
{
/* 创建 3 个任务: LP,MP,HP(低/中/高优先级任务)
*/
xTaskCreate(vLPTask, "LPTask", 1000, NULL, 1, NULL);
xTaskCreate(vMPTask, "MPTask", 1000, NULL, 2, NULL);
xTaskCreate(vHPTask, "HPTask", 1000, NULL, 3, NULL);
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建互斥量/二进制信号量 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
把二进制信号量换为互斥锁
运行时序图如下图所示:
- A:HPTask 执行 xSemaphoreTake(xLock, portMAX_DELAY);,它的优先级被 LPTask 继承
- B:LPTask 抢占 MPTask,运行
- C:LPTask 执行 xSemaphoreGive(xLock);,它的优先级恢复为原来值
- D:HPTask 得到互斥锁,开始运行
- 互斥锁的"优先级继承",可以减小"优先级反转"的影响