目录
1. 互斥量的理论讲解
1.1 解决优先级反转的问题
1.2 解决递归上锁/解锁的问题
2. 互斥量的常规使用
2.1 常规使用
2.2 优先级反转的例子
2.3 使用继承(互斥量)解决优先级反转
3. 互斥量的缺陷和递归锁
互斥量就是保护临界资源,大家互斥的去使用这些资源。
使用方法
跟二进制信号量的对比
能解决优先级反转的问题
能解决递归上锁/解锁的问题
在信号量中,A使用Take函数,然后printf,B发现A没有Give,所以B阻塞在哪里,等到A Give之后,B才来Take,使用二进制信号量如此完美的解决了该问题,但是你在看看下面这个场景!!!
A在Take之后,进行printf,这时候C给Give了,然后D又来Take了,这时候A、D都在使用串口进行打印,所以乱套了,还是存在风险。
本来举上面两个例子,就想说明对于临界资源应该是谁上锁,谁来解锁,但是很不幸的是,FreeRTOS的互斥锁,并没有在代码上实现这点,那后续出现上述的问题怎么办呢?答案是只能凭借程序猿的自觉性,写出完美的代码,既然没有实现这一点你讲这么多干啥呢?但是它还有如下的优势:
跟二进制信号量的对比
能解决优先级反转的问题
能解决递归上锁/解锁的问题
假设有A、B、C三个任务,优先级分别是1、2、3。
图1:假设A先运行,获得了锁lock,然后B在运行,B的优先级比A高,可以抢占A,然后在轮到C在运行,在C函数中,它也想获得那把锁,于是它调用lock函数,因为锁已经被A使用了,所以进入到阻塞状态,现在轮到了B运行,在B运行的过程中,假设它一直没有放弃cpu资源,在这个过程中,A一直没有办法执行,在这种情况下,A的优先级最低,C的优先级最高,优先级最高的C被B给抢占了,这就是优先级高的程序反而不能被执行,这种现象就是优先级反转。
那怎么解决这个问题,解决这个问题的方法就是优先级继承。
图2:假设A先运行,获得了锁lock,然后B在运行,B的优先级比A高,可以抢占A,然后在轮到C在运行,在C函数中,它也想获得那把锁,于是它调用lock函数,因为锁已经被A使用了,所以进入到阻塞状态,C在调用lock的时候它还会做优先级继承,然后A的优先级就变成了3,A继承了C的优先级,这时候运行的不是B,运行的是A,A运行完之后,它进行unlock,释放这个互斥量,这时候A的优先级又变了1,然后轮到C在执行。第一次A lock的时候没有问题,第二次C lock的时候没有办法获得互斥量,所以提升C的优先级,在这个过程中C的优先级并没有被B来反转,这就是优先级继承。
A先Take ,此时已经lock了,val也变为0了,这时候xxxlib()函数也Take了,这时候处于阻塞状态,两个人各自拿了一把锁,都在相互等待对方释放锁,此时就是死锁的状态了。
解决该问题的,就要用到递归锁了。
递归锁除了优先级继承外,还有递归的的功能。
红色lock
黑色lock
黑色unlock
红色unlock
注意好层次,应该就没有问题了。
常规使用:
源码:16_freertos_example_mutex
来自视频配套源码:15_freertos_example_semaphore
优先级反转的例子:
源码:17_freertos_example_mutex_inversion
来自文档配套的源码FreeRTOS_17_mutex_inversion
使用继承(互斥量)解决优先级反转
源码:18_freertos_example_mutex_inheritance
来自文档配套的源码FreeRTOS_18_mutex_inheritance
2.1 常规使用
二级制信号量初始值是0,创建后需要Give一次;互斥量初始值是1,创建后不需要Give一次。
static int sum = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static SemaphoreHandle_t xSemCalc;
static SemaphoreHandle_t xSemUART;
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000000; i++)
sum++;
//printf("1");
xSemaphoreGive(xSemCalc);
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
while (1)
{
//if (flagCalcEnd)
flagCalcEnd = 0;
xSemaphoreTake(xSemCalc, portMAX_DELAY);
flagCalcEnd = 1;
printf("sum = %d\r\n", sum);
}
}
void TaskGenericFunction(void * param)
{
while (1)
{
xSemaphoreTake(xSemUART, portMAX_DELAY);
printf("%s\r\n", (char *)param);
xSemaphoreGive(xSemUART);
vTaskDelay(1);
}
}
/*-----------------------------------------------------------*/
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xSemCalc = xSemaphoreCreateCounting(10, 0);
//xSemUART = xSemaphoreCreateBinary();
//xSemaphoreGive(xSemUART);
xSemUART = xSemaphoreCreateMutex();
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
Task3、Task4互斥的使用串口,他们的信息并没有掺杂在一起。
在main函数中,我们创建了(低/中/高优先级)的任务,先让高优先级的任务,打印“HPTask start”之后,故意让它vTaskDelay 10ms,然后让中等优先级的任务,打印 "MPTask start"之后,故意让它vTaskDelay 30ms,这时只剩下最低优先级的任务了,在最低优先级任务里,先是获得锁,故意耗时很久,在释放锁,在vTaskDelay 10ms,这时候到高优先级任务了,打印“HPTask wait for Lock”之后,他想获得这把锁,这个锁之前已经被低优先级的任务获得了,高优先级任务进入阻塞状态,然后又轮到低优先级的任务继续执行,这时中优先级的30ms到了,中优先级任务一直执行,没有主动放弃cpu资源,于是中优先级的任务一直在执行,这时候高优先级的任务一直没有机会执行,这就是优先级反转。
怎么解决这个问题,解决问题的关键就是优先级继承。
static volatile uint8_t flagLPTaskRun = 0;
static volatile uint8_t flagMPTaskRun = 0;
static volatile uint8_t flagHPTaskRun = 0;
static void vLPTask( void *pvParameters );
static void vMPTask( void *pvParameters );
static void vHPTask( void *pvParameters );
/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
int main( void )
{
prvSetupHardware();
/* 创建互斥量/二进制信号量/ */
xLock = xSemaphoreCreateBinary( );
xSemaphoreGive(xLock);
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 < 500; i++)
{
flagLPTaskRun = 1;
flagMPTaskRun = 0;
flagHPTaskRun = 0;
printf("%c", c + i);
}
printf("\r\n");
/* 释放互斥量/二进制信号量 */
xSemaphoreGive(xLock);
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、vHPTask先运行 */
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);
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
/* 释放互斥量/二进制信号量 */
xSemaphoreGive(xLock);
}
}
运行结果:
在main函数中,改为下面的代码,即可解决优先级反转的问题。
/* 创建互斥量/二进制信号量/ */
//xLock = xSemaphoreCreateBinary( );
//xSemaphoreGive(xLock);
xLock = xSemaphoreCreateMutex( );
运行结果:
本节源码:19_freertos_example_mutex_recursive
,源自16_freertos_example_mutex
对于互斥量,本意是:谁持有,就由谁释放
但是FreeRTOS并没有实现这点:A持有,B也可以释放
递归锁实现了
谁持有,就由谁释放
递归上锁/解锁
其实在linux中,互斥锁也没有实现这一点,谁持有,谁释放 。
以下运行结果,与任务1、任务2无关,代码中没有删除任务1、任务2的相关代码。
static int sum = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static SemaphoreHandle_t xSemCalc;
static SemaphoreHandle_t xSemUART;
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000000; i++)
sum++;
//printf("1");
xSemaphoreGive(xSemCalc);
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
while (1)
{
//if (flagCalcEnd)
flagCalcEnd = 0;
xSemaphoreTake(xSemCalc, portMAX_DELAY);
flagCalcEnd = 1;
printf("sum = %d\r\n", sum);
}
}
void TaskGenericFunction(void * param)
{
int i;
while (1)
{
xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY);
printf("%s\r\n", (char *)param);
for (i = 0; i < 10; i++)
{
xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY);
printf("%s in loop %d\r\n", (char *)param, i);
xSemaphoreGiveRecursive(xSemUART);
}
xSemaphoreGiveRecursive(xSemUART);
vTaskDelay(1);
}
}
void Task5Function(void * param)
{
vTaskDelay(10);
while (1)
{
while (1)
{
if (xSemaphoreTakeRecursive(xSemUART, 0) != pdTRUE)
{
xSemaphoreGiveRecursive(xSemUART);
}
else
{
break;
}
}
printf("%s\r\n", (char *)param);
xSemaphoreGiveRecursive(xSemUART);
vTaskDelay(1);
}
}
/*-----------------------------------------------------------*/
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
xSemCalc = xSemaphoreCreateCounting(10, 0);
//xSemUART = xSemaphoreCreateBinary();
//xSemaphoreGive(xSemUART);
//xSemUART = xSemaphoreCreateMutex();
xSemUART = xSemaphoreCreateRecursiveMutex();
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
xTaskCreate(Task5Function, "Task5", 100, "Task 5 is running", 1, NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
运行结果: