一、谁上锁就由谁解锁?
互斥量、互斥锁,本来的概念确实是:谁上锁就得由谁解锁。
但是FreeRTOS并没有实现这点,只是要求程序员按照这样的惯例写代码。
main函数创建了2个任务:
任务1:高优先级,一开始就获得互斥锁,永远不释放。
任务2:任务1阻塞时它开始执行,它先尝试获得互斥量,失败的话就监守自盗(释放互斥量、开锁),然后再上锁
代码如下:
int main( void )
{prvSetupHardware();/* 创建互斥量 */xMutex = xSemaphoreCreateMutex( );if( xMutex != NULL ){/* 创建2个任务: 一个上锁, 另一个自己监守自盗(开别人的锁自己用)*/xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL );/* 启动调度器 */vTaskStartScheduler();}else{/* 无法创建互斥量 */}/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}
两个任务的代码和执行流程如下图所示:
A:任务1的优先级高,先运行,立刻上锁
B:任务1阻塞
C:任务2开始执行,尝试获得互斥量(上锁),超时时间设为0。根据返回值打印出:上锁失败
因为A流程那里没有解锁,所以会上锁失败,而且超时时间设置为0,不会等待,获取失败就直接返回BaseType_t,成功就返回的是pdTURE。
D:任务2监守自盗,开锁,成功!
E:任务2成功获得互斥量
F:任务2阻塞
可见,任务1上的锁,被任务2解开了。所以,FreeRTOS并没有实现"谁上锁就得由谁开锁"的功能。
二、优先级反转
假设任务A、B都想使用串口,A优先级比较低:
任务A获得了串口的互斥量
任务B也想使用串口,它将会阻塞、等待A释放互斥量
高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)
如果涉及3个任务,可以让"优先级反转"的后果更加恶劣。
互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是FreeRTOS中互斥量和二级制信号量的差别。
程序使用二级制信号量来演示"优先级反转"的恶劣后果。
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的脉
冲。
B:MPTask开始运行。在这里故意打印,这样才可以观察到flagMPTaskRun的脉冲。
C:LPTask开始运行,获得二进制信号量,然后故意打印很多字符
D:HP Delay时间到,HPTask恢复运行,它无法获得二进制信号量,一直阻塞等待
// #define portMAX_DELAY ( TickType_t ) 0xffffffffUL
查看串口打印情况更加直观:
E:MP Delay时间到,MPTask恢复运行,它比LPTask优先级高,一直运行。导致LPTask无法运
行,自然无法释放二进制信号量,于是HPTask无法运行。
总结:
LPTask先持有二进制信号量,但是MPTask抢占LPTask,使得LPTask一直无法运行也就无法释放信号量,导致HPTask任务无法运行,优先级最高的HPTask竟然一直无法运行!
static void vHPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL ); flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask start\r\n");/* 让LPTask先运行 */ vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){ flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask wait for Lock\r\n");/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);}
}
static void vMPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL ); flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;printf("MPTask start\r\n");/* 让LPTask、HPTask先运行 */ vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){ flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;}
}
static void vLPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL ); uint32_t i;char c = 'A';printf("LPTask start\r\n");/* 无限循环 */for( ;; ){ flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);/* 耗时很久 */printf("LPTask take the Lock for long time");for (i = 0; i < 26; i++) {flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;printf("%c", c + i);}printf("\r\n");/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);vTaskDelay(xTicksToWait);}
}
优先级继承
/* 互斥量/二进制信号量句柄 */
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;
}/*-----------------------------------------------------------*//*-----------------------------------------------------------*/
static void vLPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL ); uint32_t i;char c = 'A';printf("LPTask start\r\n");/* 无限循环 */for( ;; ){ flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);/* 耗时很久 */printf("LPTask take the Lock for long time");for (i = 0; i < 26; i++) {flagLPTaskRun = 1;flagMPTaskRun = 0;flagHPTaskRun = 0;printf("%c", c + i);}printf("\r\n");/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);printf("task_low\r\n");vTaskDelay(xTicksToWait);}
}static void vMPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL ); flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;printf("MPTask start\r\n");/* 让LPTask、HPTask先运行 */ vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){ flagLPTaskRun = 0;flagMPTaskRun = 1;flagHPTaskRun = 0;}
}static void vHPTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL ); flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask start\r\n");/* 让LPTask先运行 */ vTaskDelay(xTicksToWait);/* 无限循环 */for( ;; ){ flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;printf("HPTask wait for Lock\r\n");/* 获得互斥量/二进制信号量 */xSemaphoreTake(xLock, portMAX_DELAY);printf("task_high\r\n");flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;/* 释放互斥量/二进制信号量 */xSemaphoreGive(xLock);printf("task\r\n");}
}
唯一的改变就是:
运行时序图如下图所示:
A:HPTask执行xSemaphoreTake(xLock, portMAX_DELAY); ,它的优先级被LPTask继承
B:LPTask抢占MPTask,运行
C:LPTask执行xSemaphoreGive(xLock); ,它的优先级恢复为原来值
D:HPTask得到互斥锁,开始运行
互斥锁的"优先级继承",可以减小"优先级反转"的影响