目录
信号量
创建信号量
删除信号量
give/take
应用场景:使用二进制信号量来同步
应用场景:防止数据丢失
应用场景:使用计数型信号量
互斥量
创建互斥量
删除互斥量
give释放/take获取
应用场景:互斥量基本使用
应用场景:谁上锁就由谁解锁?
应用场景:优先级反转
应用场景:优先级继承
递归锁
信号:起通知作用。
量:表示资源的数量。
当量没有限制时,就是计数型信号量。
当量只有0、1时,就是二进制信号量。
支持的动作:give给出资源,计数值+1;take获得资源,计数值-1。
队列 | 信号量 |
可以容纳多个数据, 创建队列时有2部分内存:队列结构体、存储数据的空间 |
只有计数值,无法容纳其他数据。 创建信号量时,只需要分配信号量结构体 |
生产者:没有空间存入数据时可以阻塞 | 生产者:用于不阻塞,计数值已经达到最大时返回失败 |
消费者:没有数据时可以阻塞 | 消费者:没有数据时可以阻塞 |
二进制信号量 | 计数型信号量 |
被创建时初始值为0 | 被初始化时初始值可以设定 |
其他操作是一样的 | 其他操作是一样的 |
二进制信号量 | 计数型信号量 | |
动态创建 | xSemaphoreCreateBinary计数值初始值为0 | xSemaphoreCreateCounting |
vSemaphoreCreateBinary计数值初始值为1(过时了) | ||
静态创建 | xSemaphoreCreateBinaryStatic | xSemaphoreCreateCountingStatic |
/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
/* 创建一个二进制信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
/* 创建一个计数型信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
/* 创建一个计数型信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* pxSemaphoreBuffer: StaticSemaphore_t结构体指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete可以用来删除二进制信号量、计数型信号量。
/* xSemaphore: 信号量句柄,你要删除哪个信号量 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
二进制信号量、计数型信号量的give、take操作函数是一样的。也分两个版本:给任务使用、给ISR使用。
在任务中使用 | 在ISR中使用 | |
give | xSemaphoreGive | xSemaphoreGiveFromISR |
take | xSemaphoreTake | xSemaphoreTakeFromISR |
// xSemaphore:信号量句柄
// 返回值:
// pdTRUE:成功
// 失败:如果二进制信号量的计数值已经是1,再次调用此函数时返回失败
// 失败:如果计数型信号量的计数值已经是最大值,再次调用此函数时返回失败
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
// xSemaphore:信号量句柄
// pxHigherPriorityTaskWoken:如果释放信号量导致更高优先级的任务变成了就绪态,则*pxHigherPriorityTaskWoken = pdTRUE
// 返回值:
// pdTRUE:成功
// 失败:如果二进制信号量的计数值已经是1,再次调用此函数时返回失败
// 失败:如果计数型信号量的计数值已经是最大值,再次调用此函数时返回失败
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
// xSemaphore:信号量句柄
// xTicksToWait:如果无法马上获得信号量,则阻塞一会。0表示不阻塞,马上返回。portMAX_DELAY表示一直阻塞直到成功。
// 返回值:
// pdTRUE:成功
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
// xSemaphore:信号量句柄
// pxHigherPriorityTaskWoken:如果获取信号量导致更高优先级的任务变成了就绪态,则*pxHigherPriorityTaskWoken = pdTRUE
// 返回值:
// pdTRUE:成功
BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
创建一个二进制信号量,创建两个任务(一个用于释放信号量、另一个用于获取信号量)。
/* 二进制信号量句柄 */
SemaphoreHandle_t xBinarySemaphore;
int main( void )
{
prvSetupHardware();
/* 创建二进制信号量 */
xBinarySemaphore = xSemaphoreCreateBinary( );
if( xBinarySemaphore != NULL )
{
/* 创建1个任务用于释放信号量,优先级为2 */
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建1个任务用于获取信号量,优先级为1 */
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
} else
{
/* 无法创建二进制信号量 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
static void vSenderTask( void *pvParameters )
{
int i;
int cnt_ok = 0;
int cnt_err = 0;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
/* 无限循环 */
for( ;; )
{
for (i = 0; i < 3: i++)
{
// 给出资源,计数值+1
if (xSemaphoreGive(xBinarySemaphore) == pdTRUE)
printf("Give BinarySemaphore %d time: OK\r\n", cnt_ok++);
else
printf("Give Binarysemaphore %d time: ERR\r\n", cnt_err++);
}
vTaskDelay(xTicksToWait);
}
}
static void vReceiverTask( void *pvParameters )
{
int cnt_ok = 0;
int cnt_err = 0;
/* 无限循环 */
for( ;; )
{
// 获得资源,计数值-1
if( xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE )
{
/* 得到二进制信号量 */
printf("Get BinarySemaphore OK: %d\r\n", cnt_ok++);
}else
{
/* 没有得到二进制信号量 */
printf("Get BinarySemaphore ERR: %d\r\n", cnt_err++);
}
}
}
实验现象:
Give BinarySemaphore 0 time: OK
Give BinarySemaphore 0 time: ERR
Give BinarySemaphore 1 time: ERR
Get BinarySemaphore OK: 0
Give BinarySemaphore 1 time: OK
Give BinarySemaphore 2 time: ERR
Give BinarySemaphore 3 time: ERR
Get BinarySemaphore OK: 1
Give BinarySemaphore 2 time: OK
Give BinarySemaphore 4 time: ERR
Give BinarySemaphore 5 time: ERR
...
上个应用场景中,发送任务发出3次提醒,但是接收任务只接收到1次提醒,其中2次提醒丢失了。这种情况很常见,如每接收到一个串口字符,串口中断程序就给任务一次提醒,假设收到多个字符、发出多次提醒。当任务来处理时,它只能得到1次提醒。
这时可以使用其它方法来防止数据丢失,如:
在串口中断中把数据放入缓冲区
在任务中一次性把缓冲区的数据都读出
创建一个二进制信号量,然后创建2个任务(一个释放信号量,另一个获取信号量)。
/* 二进制信号量句柄 */
SemaphoreHandle_t xBinarySemaphore;
int main( void )
{
prvSetupHardware();
/* 创建二进制信号量 */
xBinarySemaphore = xSemaphoreCreateBinary( );
if( xBinarySemaphore != NULL )
{
/* 创建1个任务用于释放信号量,优先级为2 */
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建1个任务用于获取信号量,优先级为1 */
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
} else
{
/* 无法创建二进制信号量 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
static void vSenderTask( void *pvParameters )
{
int i;
int cnt_tx = 0;
int cnt_ok = 0;
int cnt_err = 0;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
/* 无限循环 */
for( ;; )
{
for (i = 0; i < 3: i++)
{
txbuf_put('a' + cnt_tx);
cnt_tx++;
// 给出资源,计数值+1
if (xSemaphoreGive(xBinarySemaphore) == pdTRUE)
printf("Give BinarySemaphore %d time: OK\r\n", cnt_ok++);
else
printf("Give Binarysemaphore %d time: ERR\r\n", cnt_err++);
}
vTaskDelay(xTicksToWait);
}
}
static void vReceiverTask( void *pvParameters )
{
int cnt_ok = 0;
int cnt_err = 0;
uint8_t c;
/* 无限循环 */
for( ;; )
{
// 获得资源,计数值-1
if( xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE )
{
/* 得到二进制信号量 */
printf("Get BinarySemaphore OK: %d, data: ", cnt_ok++);
while(txbuf_get(&c) == 0)
{
printf("%c", c);
}
printf("\r\n");
}else
{
/* 没有得到二进制信号量 */
printf("Get BinarySemaphore ERR: %d\r\n", cnt_err++);
}
}
}
实验现象:
Give BinarySemaphore 0 time: OK
Give BinarySemaphore 0 time: ERR
Give BinarySemaphore 1 time: ERR
Get BinarySemaphore OK: 0, data:abc
Give BinarySemaphore 1 time: OK
Give BinarySemaphore 2 time: ERR
Give BinarySemaphore 3 time: ERR
Get BinarySemaphore OK: 1, data:def
Give BinarySemaphore 2 time: OK, data:ghi
Give BinarySemaphore 4 time: ERR
Give BinarySemaphore 5 time: ERR
...
使用计数型信号量时,可以多次释放信号量。当信号量的计数值达到最大时再次释放信号量就会出错。
如果信号量计数值为n,就可以连续n次获取信号量,第n+1次获取信号量就会阻塞或失败。
创建了一个计数型信号量,最大计数值为3,初始值计数值为0。然后创建2个任务(一个用于释放信号量,另一个用于获取信号量)。
/* 计数型信号量句柄 */
SemaphoreHandle_t xCountingSemaphore;
int main( void )
{
prvSetupHardware();
/* 创建计数型信号量 */
xCountingSemaphore = xSemaphoreCreateCounting(3, 0);
if( xCountingSemaphore != NULL )
{
/* 创建1个任务用于释放信号量,优先级为2 */
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建1个任务用于获取信号量,优先级为1 */
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
} else
{
/* 无法创建信号量 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
static void vSenderTask( void *pvParameters )
{
int i;
int cnt_ok = 0;
int cnt_err = 0;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
/* 无限循环 */
for( ;; )
{
for (i = 0; i < 4: i++)
{
// 给出资源,计数值+1
if (xSemaphoreGive(xCountingSemaphore) == pdTRUE)
printf("Give xCountingSemaphore %d time: OK\r\n", cnt_ok++);
else
printf("Give xCountingSemaphore %d time: ERR\r\n", cnt_err++);
}
vTaskDelay(xTicksToWait);
}
}
static void vReceiverTask( void *pvParameters )
{
int cnt_ok = 0;
int cnt_err = 0;
/* 无限循环 */
for( ;; )
{
// 获得资源,计数值-1
if( xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE )
{
/* 得到二进制信号量 */
printf("Get xCountingSemaphore OK: %d\r\n", cnt_ok++);
}else
{
/* 没有得到二进制信号量 */
printf("Get xCountingSemaphore ERR: %d\r\n", cnt_err++);
}
}
}
实验现象:
Give xCountingSemaphore 0 time: OK
Give xCountingSemaphore 1 time: OK
Give xCountingSemaphore 2 time: OK
Give xCountingSemaphore 0 time: ERR
Get xCountingSemaphore OK: 0
Get xCountingSemaphore OK: 1
Get xCountingSemaphore OK: 2
Give xCountingSemaphore 3 time: OK
Give xCountingSemaphore 4 time: OK
Give xCountingSemaphore 5 time: OK
Give xCountingSemaphore 1 time: ERR
Get xCountingSemaphore OK: 3
Get xCountingSemaphore OK: 4
Get xCountingSemaphore OK: 5
...
在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话就有可能导致问题的出现。比如串口,任务A正使用它来打印,在打印过程中任务B也来打印,结果就是A、B的信息混在了一起。
互斥量(互斥锁)使用过程如下:
互斥量初始值为1
任务A想访问临界资源,先获得并占有互斥量,然后开始访问。
任务B也想访问临界资源,也要先获得,但被别人占有互斥量,于是阻塞。
任务A使用完毕,释放互斥量;任务B被唤醒,得到并占有互斥量,然后开始访问。
任务B使用完毕,释放互斥量。
正常来说:在任务A占有互斥量的过程中,任务B、任务C等都无法释放互斥量。但在FreeRTOS中并未实现这点,即任务A占有互斥量的情况下,任务B也可释放互斥量。
互斥量是一种特殊的二进制信号量。也分动态分配内存和静态分配内存。互斥量不能使用在ISR。
使用互斥量的过程:开始先创建、然后去获得、最后释放它。
想使用互斥量的前提是:需要在配置文件FreeRTOSConfig.h定义:#define configUSE_MUTEXES 1
/* 创建一个互斥量,返回它的句柄。
* 此函数内部会分配互斥量结构体。
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 创建一个互斥量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
/* 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 );
刚创建的互斥量可以被成功take。
take互斥量成功的任务被称为holder,只能由它give互斥量,别的任务give不成功。
在ISR中不能使用互斥量。
创建两个发送任务(故意发送大量的字符)。
两个实验:
使用互斥量:可以看到任务1、任务2打印的字符串没有混杂在一起。
不适应互斥量:任务1、任务2打印的字符串混杂在一起。
/* 互斥量句柄 */
SemaphoreHandle_t xMutex;
int main( void )
{
prvSetupHardware();
/* 创建互斥量 */
xMutex = xSemaphoreCreateMutex( );
if( xMutex != NULL )
{
/* 创建2个任务: 都是打印,优先级相同 */
xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
} else
{
/* 无法创建互斥量 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
static void vSenderTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
int cnt = 0;
int task = (int)pvParameters;
int i;
char c;
/* 无限循环 */
for( ;; )
{
/* 获得互斥量: 上锁 */
xSemaphoreTake(xMutex, portMAX_DELAY);
printf("Task %d use UART count: %d, ", task, cnt++);
c = (task == 1 ) ? 'a' : 'A';
for (i = 0; i < 26; i++)
printf("%c", c + i);
printf("\r\n");
/* 释放互斥量: 开锁 */
xSemaphoreGive(xMutex);
vTaskDelay(xTicksToWait);
}
}
实验现象:
使用take/give时,有序打印输出。
不使用take/give时,无序打印输出。
创建2个任务:
任务1:高优先级,先获得互斥锁,永远不释放。
任务2:任务1阻塞后执行,先尝试获得互斥量,失败的话就释放互斥锁,然后再上锁。
/* 互斥量句柄 */
SemaphoreHandle_t xMutex;
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;
}
static void vTakeTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
BaseType_t xStatus;
/* 获得互斥量: 上锁 */
xStatus = xSemaphoreTake(xMutex, portMAX_DELAY);
printf("Task1 take the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
/* 无限循环 */
for( ;; )
{
vTaskDelay(xTicksToWait);
}
}
static void vGiveAndTakeTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
BaseType_t xStatus;
/* 获得互斥量: 上锁 */
xStatus = xSemaphoreTake(xMutex, 0);
printf("Task2 take the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
if(xStatus != pdTRUE)
{
/* 释放互斥量: 开锁 */
xStatus = xSemaphoreGive(xMutex);
printf("Task2 give the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
}
/* 获得互斥量: 上锁 */
xStatus = xSemaphoreTake(xMutex, 0);
printf("Task2 take the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
/* 无限循环 */
for( ;; )
{
vTaskDelay(xTicksToWait);
}
}
实验现象:
Task1 take the Mutex Success
Task2 take the Mutex Failed
Task2 give the Mutex Success
Task2 take the Mutex Success
优先级反转现象:
任务A优先级较低,但获得了临界资源的互斥量。
任务B也想使用临界资源,它将会阻塞去等待任务A释放互斥量。
所以现象为高优先级的任务,被低优先级的任务延迟。
互斥量可以通过优先级继承,可以很大程度解决优先级反转的问题,这也是FreeRTOS中互斥量和二进制信号链的区别。
/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
uint8_t flagLPTaskRun = 0;
uint8_t flagMPTaskRun = 0;
uint8_t flagHPTaskRun = 0;
int main( void )
{
prvSetupHardware();
/* 创建互斥量 */
xLock = xSemaphoreCreateBinary( );
if( xLock != NULL )
{
/* 创建3个任务: 低/中/高优先级 */
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\r\n");
for(i = 0 ; i < 26 ; 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");
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");
vTaskDelay(xTicksToWait);
/* 无限循环 */
for( ;; )
{
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
printf("HPTask wait for the Lock\r\n");
/* 获得互斥量/二进制信号量 */
xSemaphoreTake(xLock, portMAX_DELAY);
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
/* 释放互斥量/二进制信号量 */
xSemaphoreGive(xLock);
}
}
上个应用场景问题在于:LPTask低优先级任务获得了锁,但是它优先级太低而无法运行。如果能提升LPTask任务的优先级,让它能尽快运行、释放锁,优先级反转的问题就可以解决。这种做法称为优先级继承。
优先级继承:
假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁,于是任务A就继承了任务B的优先级。
等任务A释放互斥锁时,就恢复为原来的优先级。
互斥锁内部就实现了优先级的提升、恢复。
在上个应用场景中把main函数的xLock = xSemaphoreCreateBinary( );替换为xLock = xSemaphoreCreateMutex( );就可。
死锁情况一(双方):
任务A获得了互斥量M1,任务B获得了互斥量M2。
任务A还想要获得互斥量M2,任务B还想要获得互斥量M1。于是任务A、任务B都阻塞,都无法释放本身持有的互斥量。
死锁情况二(自身):
任务获得了互斥量M。任务调用了一个库函数,库函数想要获得同一个互斥量M,于是任务阻塞,无法释放本身持有的互斥量。
递归锁可解决死锁问题。特性如下:
任务A获得递归锁M后,还可以多次去获得这个锁。
take了N次,要giveN次,这个锁才会被释放。
递归锁 | 互斥量 | |
创建 | 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 );
应用场景:递归锁,谁上锁就由谁解锁
创建2个任务:
任务1:高优先级,一开始就获得递归锁,然后故意等待很长时间,让任务2运行。
任务2:低优先级,看看能否操作其它任务持有的锁。
/* 递归锁句柄 */ SemaphoreHandle_t xMutex; int main( void ) { prvSetupHardware(); /* 创建递归锁 */ xMutex = xSemaphoreCreateRecursiveMutex( ); if( xMutex != NULL ) { /* 创建2个任务: 一个上锁, 另一个自己监守自盗(看看能否开别人的锁自己用) */ xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL ); xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL ); /* 启动调度器 */ vTaskStartScheduler(); } else { /* 无法创建递归锁 */ } /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */ return 0; } static void vTakeTask( void *pvParameters ) { const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL ); BaseType_t xStatus; int i; /* 无限循环 */ for( ;; ) { /* 获得互斥量: 上锁 */ xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY); printf("Task1 take the Mutex in main loop %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed"); vTaskDelay(xTicksToWait); for(i = 0 ; i < 10 ; i++) { /* 获得互斥量: 上锁 */ xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY); printf("Task1 take the Mutex in main loop %s,for time %d\r\n", (xStatus == pdTRUE)? "Success" : "Failed", i); /* 释放互斥量: 开锁 */ xStatus = xSemaphoreGiveRecursive(xMutex); } /* 释放互斥量: 开锁 */ xStatus = xSemaphoreGiveRecursive(xMutex); } } static void vGiveAndTakeTask( void *pvParameters ) { const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL ); BaseType_t xStatus; /* 获得互斥量: 上锁 */ xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY); printf("Task2 take the Mutex in main loop %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed"); if(xStatus != pdTRUE) { /* 释放互斥量: 开锁 */ xStatus = xSemaphoreGiveRecursive(xMutex); printf("Task2 give the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed"); } /* 获得互斥量: 上锁 */ xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY); printf("Task2 take the Mutex in main loop %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed"); /* 无限循环 */ for( ;; ) { vTaskDelay(xTicksToWait); } }
实验现象:
Task1 take the Mutex in main loop Success
Task2 take the Mutex in main loop Failed
Task2 give the Mutex Failed // 进入阻塞
Task1 take the Mutex in main loop Success,for time 0
...
Task1 take the Mutex in main loop Success,for time 9
Task1 take the Mutex in main loop Success
Task1 take the Mutex in main loop Success,for time 0
...
Task1 take the Mutex in main loop Success,for time 9
...