一、谁上锁就由谁解锁?
互斥量、互斥锁,本来的概念确实是:谁上锁就得由谁解锁。
但是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得到互斥锁,开始运行
互斥锁的"优先级继承",可以减小"优先级反转"的影响