1、互斥量的使用场景
用于保护临界资源,在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。 比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信 息混杂在一起。
在使用二进制信号量达到互斥的效果的时候,不能保证谁上锁谁解锁的问题。假如A在上锁然后去上厕所,这时B来解锁了,然后C来发现没有锁也来上厕所,那么这时A就曝光了。
虽然互斥量也不能在代码上解决谁上锁谁解锁的问题,但是它可以解决优先级反转、递归上锁/解锁的问题。
优先级反转:假设任务A、B、C的优先级分别为1、2、3,因为A先运行去获得了锁,后面C去抢占获得锁时,因为没有锁了所以被阻塞了,这时B运行,加入B运行过程中一直没有放弃CPU资源,这时候A没法执行,不能去放锁,而C此时也一直被阻塞。这就是高优先级的反而不能执行,这种现象就叫优先级反转。
解决这种可以通过优先级继承,A开始执行,执行过程中去获得这个锁,然后轮到B执行,因为B的优先级比较高,所以B可以抢占A,然后轮到C来执行,C的优先级更高,可以抢占B,C来调用lock的时候,因为锁已经被A拿了,于是C会进入阻塞状态,在C调用lock的时候还会做个优先级继承,将自身的优先级继承给A,A的优先级变成了3,这时候A就可以执行去放锁,放锁之后又轮到C来执行。
递归上锁:假设在任务A、B中都有上锁和解锁,在任务A执行过程中,去调用任务B,但是在B执行时,锁已经在之前被A获得并且没有释放,这就会造成递归上锁,这时任务A、B会进入阻塞状态,造成死锁。
解决这种可以通过递归锁,即任务A在持有这个锁的过程中还可以继续去上锁。
2、互斥量函数
2.1 创建
互斥量是一种特殊的二进制信号量。 使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。
创建互斥量的函数有2种:动态分配内存,静态分配内存。
/* 创建一个互斥量,返回它的句柄。* 此函数内部会分配互斥量结构体* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 创建一个互斥量,返回它的句柄。* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer);
要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:
#define configUSE_MUTEXES 1
2.2 其它函数
要注意的是,互斥量不能在ISR中使用。 各类操作函数,比如删除、give/take,跟一般是信号量是一样的。
/** xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );/* 释放(ISR版本) */
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken
);/* 获得 */
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait
);/* 获得(ISR版本) */
xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken
);
2.3 递归锁函数
递归锁的函数跟一般互斥量的函数名不一样,参数类型一样。
递归锁 | 一般互斥量 | |
创建 | xSemaphoreCreateRecursiveMutex | xSemaphoreCreateMutex |
获得 | xSemaphoreTakeRecursive | xSemaphoreTake |
释放 | xSemaphoreGiveRecursive | xSemaphoreGive |
/* 创建一个递归锁,返回它的句柄。* 此函数内部会分配互斥量结构体* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );/* 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );/* 获得 */
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait
);
3、示例代码
3.1 互斥
要配置一下#define configUSE_MUTEXES 1
static SemaphoreHandle_t xSemUART;void TaskGenericFunction( void * param)
{while(1){ xSemaphoreTake(xSemUART, portMAX_DELAY); //在使用串口前先take这个信号量printf("%s\r\n", (char *)param);xSemaphoreGive(xSemUART); //用完释放掉vTaskDelay(1);}
}//main函数中
xSemUART = xSemaphoreCreateMutex(); //创建互斥量,互斥量初始值是1xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
3.2 优先级反转/继承
static volatile uint8_t flagLPTaskRun = 0;
static volatile uint8_t flagMPTaskRun = 0;
static volatile uint8_t flagHPTaskRun = 0;/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;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、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);flagLPTaskRun = 0;flagMPTaskRun = 0;flagHPTaskRun = 1;/* 释放互斥量/二进制信号量 */xSemaphoreGive(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;
}
该程序实现了优先级反转的现象
只要将上述程序中的代码做出以下修改即可解决
int main( void )
{/* 创建互斥量/二进制信号量 *///xLock = xSemaphoreCreateBinary( );//xSemaphoreGive(xLock);xLock = xSemaphoreCreateMutex(); //在这里修改}
3.3 递归锁
互斥量的本意是:谁持有,就由谁释放。
但是FreeRTOS并没有实现这一点,Linux也没有,A持有,B也可以释放。
通过递归锁实现了:谁持有,就由谁释放;递归上锁/解锁。
设置一个程序,任务四和任务五交替使用串口打印,之后让任务五来偷偷解锁。
void TaskGenericFunction( void * param)
{while(1){ xSemaphoreTake(xSemUART, portMAX_DELAY); //在使用串口前先take这个信号量printf("%s\r\n", (char *)param);xSemaphoreGive(xSemUART); //用完释放掉vTaskDelay(1);}
}void Task5Function( void * param)
{vTaskDelay(10);while(1){while(1){if(xSemaphoreTake(xSemUART, 0) != pdTRUE){xSemaphoreGive(xSemUART); //放锁}else{break;}}printf("%s\r\n", (char *)param);xSemaphoreGive(xSemUART); //放锁vTaskDelay(1);}
}//main函数中
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);
通过结果发现后面串口打印出来的数据乱套了,证明一般的互斥量没有实现谁上锁就由谁来解锁。
修改上述代码通过递归锁来查看。
使用递归锁要先定义#define configUSE_RECURSIVE_MUTEXES 1
void TaskGenericFunction( void * param)
{while(1){ xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY); //在使用串口前先take这个信号量printf("%s\r\n", (char *)param);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);}
}//main函数中
xSemUART = xSemaphoreCreateRecursiveMutex(); //创建递归锁
通过结果可以看出几个任务都正常的使用串口了,完成了谁上锁就由谁来解锁的问题。