互斥量介绍
互斥量就是二进制的信号量,都是用来传递状态。与二进制信号量不同的是,互斥量的初始值是1。官方源码如下,由源码可知初始值为1(通过xQueueGenericSend函数向队列中写入一个数据)。
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
QueueHandle_t xNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
prvInitialiseMutex( ( Queue_t * ) xNewQueue );
return xNewQueue;
}
static void prvInitialiseMutex( Queue_t * pxNewQueue )
{
if( pxNewQueue != NULL )
{
/* The queue create function will set all the queue structure members
* correctly for a generic queue, but this function is creating a
* mutex. Overwrite those members that need to be set differently -
* in particular the information required for priority inheritance. */
pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
/* In case this is a recursive mutex. */
pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;
traceCREATE_MUTEX( pxNewQueue );
/* Start with the semaphore in the expected state. */
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK ); // 计数值初始值为1
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
除此之外,互斥量还有其他的特性:
- 可以优先级继承,解决优先级反转问题;
- 普通的互斥量不具备谁上锁就谁解锁的功能,但递归锁解决了这个问题。
互斥量函数
如果需要使用互斥量,需要在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 );
/* 获得 */
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
互斥量基本使用
互斥量基本使用的步骤如下:
- 在FreeRTOSConfig.h文件中添加#define configUSE_MUTEXES 1;
- 使用xSemaphoreCreateMutex函数创建互斥量;
- 将句柄传递给需要的任务;
- 进入临界区前调用xSemaphoreTake,退出临界区后调用xSemaphoreGive。
示例程序的功能是串口交替输出任务3和任务4的内容。
/*-----------------------------------------------------------*/
/*
* Configure the clocks, GPIO and other peripherals as required by the demo.
*/
static void prvSetupHardware( void );
/*-----------------------------------------------------------*/
void vmainTask3Function( void * param);
void vmainTask4Function( void * param);
/*-----------------------------------------------------------*/
int main( void )
{
SemaphoreHandle_t semaphoreHandleMutex;
prvSetupHardware();
printf("Hello world!\r\n");
// 创建互斥量,计数值初始值为1
semaphoreHandleMutex = xSemaphoreCreateMutex();
if(semaphoreHandleMutex != NULL) {
xTaskCreate(vmainTask3Function, "task3", 100, (void *)semaphoreHandleMutex, 1, NULL); // 动态创建任务
xTaskCreate(vmainTask4Function, "task4", 100, (void *)semaphoreHandleMutex, 1, NULL); // 动态创建任务
/* Start the scheduler. */
vTaskStartScheduler();
}
else {
}
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/
static void prvSetupHardware( void )
{
/* Start with the clocks in their expected state. */
RCC_DeInit();
/* Enable HSE (high speed external clock). */
RCC_HSEConfig( RCC_HSE_ON );
/* Wait till HSE is ready. */
while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
{
}
/* 2 wait states required on the flash. */
*( ( unsigned long * ) 0x40022000 ) = 0x02;
/* HCLK = SYSCLK */
RCC_HCLKConfig( RCC_SYSCLK_Div1 );
/* PCLK2 = HCLK */
RCC_PCLK2Config( RCC_HCLK_Div1 );
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config( RCC_HCLK_Div2 );
/* PLLCLK = 8MHz * 9 = 72 MHz. */
RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );
/* Enable PLL. */
RCC_PLLCmd( ENABLE );
/* Wait till PLL is ready. */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source. */
RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );
/* Wait till PLL is used as system clock source. */
while( RCC_GetSYSCLKSource() != 0x08 )
{
}
/* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );
/* SPI2 Periph clock enable */
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );
/* Set the Vector Table base address at 0x08000000 */
NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* Configure HCLK clock as SysTick clock source. */
SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
serialPortInit();
}
/*-----------------------------------------------------------*/
void vmainTask3Function( void * param)
{
while(1) {
xSemaphoreTake((SemaphoreHandle_t)param, portMAX_DELAY);
printf("task3\r\n");
xSemaphoreGive((SemaphoreHandle_t)param);
taskYIELD();
}
}
void vmainTask4Function( void * param)
{
while(1) {
xSemaphoreTake((SemaphoreHandle_t)param, portMAX_DELAY);
printf("task4\r\n");
xSemaphoreGive((SemaphoreHandle_t)param);
taskYIELD();
}
}
实验现象如下图所示:
优先级反转
优先级反转指低优先级的任务可以执行但高优先级的任务无法得到执行。下面是一个优先级反转的例子及现象。
/*
* Configure the clocks, GPIO and other peripherals as required by the demo.
*/
static void prvSetupHardware( void );
/*-----------------------------------------------------------*/
void vmainTask1Function( void * param);
void vmainTask2Function( void * param);
void vmainTask3Function( void * param);
BaseType_t flagLPTaskRun, flagMPTaskRun, flagHPTaskRun;
/*-----------------------------------------------------------*/
int main( void )
{
SemaphoreHandle_t semaphoreHandleBinary;
prvSetupHardware();
printf("Hello world!\r\n");
// 创建二进制信号量,计数值初始值为1
semaphoreHandleBinary = xSemaphoreCreateBinary();
xSemaphoreGive(semaphoreHandleBinary);
if(semaphoreHandleBinary != NULL) {
xTaskCreate(vmainTask1Function, "LPTask", 100, (void *)semaphoreHandleBinary, 1, NULL); // 动态创建任务
xTaskCreate(vmainTask2Function, "MPTask", 100, NULL, 2, NULL); // 动态创建任务
xTaskCreate(vmainTask3Function, "HPTask", 100, (void *)semaphoreHandleBinary, 3, NULL); // 动态创建任务
/* Start the scheduler. */
vTaskStartScheduler();
}
else {
}
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/
static void prvSetupHardware( void )
{
/* Start with the clocks in their expected state. */
RCC_DeInit();
/* Enable HSE (high speed external clock). */
RCC_HSEConfig( RCC_HSE_ON );
/* Wait till HSE is ready. */
while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
{
}
/* 2 wait states required on the flash. */
*( ( unsigned long * ) 0x40022000 ) = 0x02;
/* HCLK = SYSCLK */
RCC_HCLKConfig( RCC_SYSCLK_Div1 );
/* PCLK2 = HCLK */
RCC_PCLK2Config( RCC_HCLK_Div1 );
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config( RCC_HCLK_Div2 );
/* PLLCLK = 8MHz * 9 = 72 MHz. */
RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );
/* Enable PLL. */
RCC_PLLCmd( ENABLE );
/* Wait till PLL is ready. */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source. */
RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );
/* Wait till PLL is used as system clock source. */
while( RCC_GetSYSCLKSource() != 0x08 )
{
}
/* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );
/* SPI2 Periph clock enable */
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );
/* Set the Vector Table base address at 0x08000000 */
NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* Configure HCLK clock as SysTick clock source. */
SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
serialPortInit();
}
/*-----------------------------------------------------------*/
void vmainTask1Function( void * param)
{
int i;
while(1) {
xSemaphoreTake((SemaphoreHandle_t)param, portMAX_DELAY);
printf("LPTask take the lock for long time\r\n");
for(i = 0; i < 100000; i++) {
flagLPTaskRun = 1;
flagMPTaskRun = 0;
flagHPTaskRun = 0;
}
xSemaphoreGive((SemaphoreHandle_t)param);
}
}
void vmainTask2Function( void * param)
{
const TickType_t tickToWait = pdMS_TO_TICKS(30);
flagLPTaskRun = 0;
flagMPTaskRun = 1;
flagHPTaskRun = 0;
vTaskDelay(tickToWait);
while(1) {
flagLPTaskRun = 0;
flagMPTaskRun = 1;
flagHPTaskRun = 0;
}
}
void vmainTask3Function( void * param)
{
const TickType_t tickToWait = pdMS_TO_TICKS(10);
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
vTaskDelay(tickToWait);
while(1) {
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
printf("HPTask take the lock\r\n");
xSemaphoreTake((SemaphoreHandle_t)param, portMAX_DELAY);
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
xSemaphoreGive((SemaphoreHandle_t)param);
}
}
- HPTask优先级最高先执行,由于调用vTaskDelay进入阻塞态,阻塞10ms;
- 然后MPTaskz执行,由于调用vTaskDelay进入阻塞态,阻塞30ms;
- 然后LPTask执行,使信号量计数值变成0并长时间持有信号量;
- 10ms后,HPTask由阻塞态进入就绪态,然后执行,此时获取信号量则阻塞;
- 再经过20ms,MPTask进入就绪态,由于优先级高,打断LPTask的执行(仍持有信号量);
- 信号量得不到释放,HPTask永远无法执行。
可以通过优先级继承解决此问题,互斥量具有此特性。 官方源码如下所示:
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
TickType_t xTicksToWait )
{
..............
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* if ( configUSE_MUTEXES == 1 ) */
..............
}
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
{
.................
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
................
}
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition )
{
...............
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
pxQueue->u.xSemaphore.xMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
................
}
示例代码如下:
/*
* Configure the clocks, GPIO and other peripherals as required by the demo.
*/
static void prvSetupHardware( void );
/*-----------------------------------------------------------*/
void vmainTask1Function( void * param);
void vmainTask2Function( void * param);
void vmainTask3Function( void * param);
BaseType_t flagLPTaskRun, flagMPTaskRun, flagHPTaskRun;
/*-----------------------------------------------------------*/
int main( void )
{
SemaphoreHandle_t semaphoreHandleMutex;
prvSetupHardware();
printf("Hello world!\r\n");
// 创建互斥量,计数值初始值为1
semaphoreHandleMutex = xSemaphoreCreateMutex();
if(semaphoreHandleMutex != NULL) {
xTaskCreate(vmainTask1Function, "LPTask", 100, (void *)semaphoreHandleMutex, 1, NULL); // 动态创建任务
xTaskCreate(vmainTask2Function, "MPTask", 100, NULL, 2, NULL); // 动态创建任务
xTaskCreate(vmainTask3Function, "HPTask", 100, (void *)semaphoreHandleMutex, 3, NULL); // 动态创建任务
/* Start the scheduler. */
vTaskStartScheduler();
}
else {
}
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/
static void prvSetupHardware( void )
{
/* Start with the clocks in their expected state. */
RCC_DeInit();
/* Enable HSE (high speed external clock). */
RCC_HSEConfig( RCC_HSE_ON );
/* Wait till HSE is ready. */
while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
{
}
/* 2 wait states required on the flash. */
*( ( unsigned long * ) 0x40022000 ) = 0x02;
/* HCLK = SYSCLK */
RCC_HCLKConfig( RCC_SYSCLK_Div1 );
/* PCLK2 = HCLK */
RCC_PCLK2Config( RCC_HCLK_Div1 );
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config( RCC_HCLK_Div2 );
/* PLLCLK = 8MHz * 9 = 72 MHz. */
RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );
/* Enable PLL. */
RCC_PLLCmd( ENABLE );
/* Wait till PLL is ready. */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source. */
RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );
/* Wait till PLL is used as system clock source. */
while( RCC_GetSYSCLKSource() != 0x08 )
{
}
/* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );
/* SPI2 Periph clock enable */
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );
/* Set the Vector Table base address at 0x08000000 */
NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* Configure HCLK clock as SysTick clock source. */
SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
serialPortInit();
}
/*-----------------------------------------------------------*/
void vmainTask1Function( void * param)
{
int i;
while(1) {
xSemaphoreTake((SemaphoreHandle_t)param, portMAX_DELAY);
printf("LPTask take the lock for long time\r\n");
for(i = 0; i < 100000; i++) {
flagLPTaskRun = 1;
flagMPTaskRun = 0;
flagHPTaskRun = 0;
}
xSemaphoreGive((SemaphoreHandle_t)param);
}
}
void vmainTask2Function( void * param)
{
const TickType_t tickToWait = pdMS_TO_TICKS(30);
flagLPTaskRun = 0;
flagMPTaskRun = 1;
flagHPTaskRun = 0;
vTaskDelay(tickToWait);
while(1) {
flagLPTaskRun = 0;
flagMPTaskRun = 1;
flagHPTaskRun = 0;
}
}
void vmainTask3Function( void * param)
{
const TickType_t tickToWait = pdMS_TO_TICKS(10);
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
vTaskDelay(tickToWait);
while(1) {
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
printf("HPTask take the lock\r\n");
xSemaphoreTake((SemaphoreHandle_t)param, portMAX_DELAY);
flagLPTaskRun = 0;
flagMPTaskRun = 0;
flagHPTaskRun = 1;
xSemaphoreGive((SemaphoreHandle_t)param);
}
}
递归锁
死锁的情况可分为:
- 任务1获取互斥量M1,任务2获取互斥量M2。此时任务2去获取M1,任务1获取M2,结果都阻塞同时互斥量无法释放。
- 任务获取连续获取互斥量,进入阻塞互斥量无法释放。
递归锁的特性是:
- 同一个互斥量可以获取多次;
- 获取多少次就要释放多少次,同时记录谁获取的锁。
- 谁获取就谁释放。