FreeRTOS--互斥量

发布时间 2023-12-17 16:42:20作者: 流翎

示例源码基于FreeRTOS V9.0.0

互斥量

1. 概述

互斥量用于临界资源的保护,通过互斥量,多个任务对相同资源进行的访问操作是互斥的;

互斥量的核心在于谁上锁,就由谁解锁,这只是约定,FreeRTOS并没有在代码上实现这一点;

互斥量是一种特殊的信号量,也是一种特殊的队列;

使用互斥量,需要开启宏configUSE_MUTEXES;

2. 接口API

2.1 创建互斥量

2.1.1 动态创建

#define pxMutexHolder					pcTail
#define uxQueueType						pcHead
#define queueQUEUE_IS_MUTEX				NULL

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif
#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

    QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
    {
    Queue_t *pxNewQueue;
    const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

        pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
        prvInitialiseMutex( pxNewQueue );

        return pxNewQueue;
    }

#endif /* configUSE_MUTEXES */
#if( configUSE_MUTEXES == 1 )

    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->pxMutexHolder = NULL;
            pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;

            /* In case this is a recursive mutex. */
            pxNewQueue->u.uxRecursiveCallCount = 0;

            traceCREATE_MUTEX( pxNewQueue );

            /* Start with the semaphore in the expected state. */
            ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
        }
        else
        {
            traceCREATE_MUTEX_FAILED();
        }
    }

#endif /* configUSE_MUTEXES */
  • 通过使用xSemaphoreCreateMutex()来创建互斥量,使用互斥量,必须开启宏configUSE_MUTEXES;

  • 创建互斥量,本质也是创建队列。它基于队列实现,是一种特殊的队列,队列深度为1,数据项大小为0;

  • 在队列创建之后,需要针对互斥量做一些专用的初始化,它复用pcTail指针作为pxMutexHolder,用来指向当前持有互斥量的任务块(TCB),初始为NULL。复用pcHead指针作为uxQueueType,用来表示队列类型,赋值为queueQUEUE_IS_MUTEX。

  • 初始化后,通过使用xQueueGenericSend(),将队列当前大小设为1,队列满;

2.1.2 静态创建

 #if( configSUPPORT_STATIC_ALLOCATION == 1 )
	#define xSemaphoreCreateMutexStatic( pxMutexBuffer ) xQueueCreateMutexStatic( queueQUEUE_TYPE_MUTEX, ( pxMutexBuffer ) )
#endif /* configSUPPORT_STATIC_ALLOCATION */
#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )

    QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType, StaticQueue_t *pxStaticQueue )
    {
    Queue_t *pxNewQueue;
    const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

        /* Prevent compiler warnings about unused parameters if
        configUSE_TRACE_FACILITY does not equal 1. */
        ( void ) ucQueueType;

        pxNewQueue = ( Queue_t * ) xQueueGenericCreateStatic( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType );
        prvInitialiseMutex( pxNewQueue );

        return pxNewQueue;
    }

#endif /* configUSE_MUTEXES */
  • 通过使用xQueueCreateMutexStatic静态创建互斥量,需要开启宏configUSE_MUTEXES和configSUPPORT_STATIC_ALLOCATION;

  • 创建队列深度为1,数据项大小为0,初始化同动态创建,区别在于队列的创建接口使用xQueueGenericCreateStatic,队列空间pxStaticQueue需要在外部自行创建;

2.2 删除互斥量

#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )

需注意,不能在任务持有信号量的时候使用vSemaphoreDelete接口删除信号量!!!

2.3 获取互斥量

#define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )

使用xSemaphoreTake获取互斥量,实际使用的是队列的接收接口xQueueGenericReceive;

获取互斥量时,如果互斥量未被其他任务持有(pxQueue->uxMessagesWaiting > 0),则更新pxMutexHolder,指向当前任务块。代码如下:

img

pvTaskIncrementMutexHeldCount返回当前任务块指针,其实现定义在task.c

#if ( configUSE_MUTEXES == 1 )

	void *pvTaskIncrementMutexHeldCount( void )
	{
		/* If xSemaphoreCreateMutex() is called before any tasks have been created
		then pxCurrentTCB will be NULL. */
		if( pxCurrentTCB != NULL )
		{
			( pxCurrentTCB->uxMutexesHeld )++;
		}

		return pxCurrentTCB;
	}

#endif /* configUSE_MUTEXES */

获取互斥量,如果互斥量被其他任务持有(pxQueue->uxMessagesWaiting == 0,队列为空),此时pxQueue->pxMutexHolder != NULL,让持有互斥量的任务继承当前任务的优先级(当前任务优先级高于持有互斥量的任务)。代码如下:

img

vTaskPriorityInherit实现优先级继承功能,定义在task.c

2.4 释放互斥量

#define semGIVE_BLOCK_TIME					( ( TickType_t ) 0U )
#define xSemaphoreGive( xSemaphore )		xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

使用xSemaphoreGive释放互斥量,实际使用的是队列的发送接口xQueueGenericSend,阻塞时间为0,即接口不会阻塞;

xQueueGenericSend调用了prvCopyDataToQueue,接口内对于互斥量,会取消优先级继承,恢复原优先级。代码如下:

img

xTaskPriorityDisinherit实现优先级恢复功能,定义在task.c

3. 优先级反转

优先级反转,即高优先级任务运行起来的效果如同低优先级,低优先级比高优先级先运行。

出现优先级反转,可能是如下场景:

img

  1. 任务H和任务M处于挂起状态,等待某一事件发生,任务L正在运行;
  2. 某时刻任务L想要访问共享资源,因此它先获得资源信号量;
  3. 任务L获得信号量后开始使用共享资源;
  4. 任务H比任务L优先级高,H等待的事件发生后抢占了L的CPU;
  5. 任务H开始运行;
  6. 任务H运行过程中需要使用任务L正在使用的资源,由于资源正在被L占用,H只能挂起,等待L释放信号量
  7. 任务L继续运行;
  8. 任务M比任务L优先级高,M等待的事件发生后抢占了L的CPU;
  9. 任务M运行,运行结束后,将CPU使用权归还L;
    10.任务L继续运行;
    11.任务L完成工作释放信号量,而高优先级任务H正在等待这个信号量,因此任务切换;
    12.任务H得到信号量并继续运行;

上述场景,任务H的优先级实际降到了任务L的优先级水平,任务H一直等待直到任务L释放信号量,而任务M抢占了任务L,使得任务H的情况更加恶化,这样相当于任务M的优先级高于了任务H,导致优先级反转。

4. 优先级继承

上述任务L由于优先级太低,而导致被其他任务抢占无法运行,即使它持有了信号量,也依旧会被抢占。如果能把L的优先级提高到H一样,让它能更快运行,更快地释放信号量,优先级反转的问题即可得到解决;

优先级继承,即当持有互斥量的任务由于优先级低而无法运行,高优先级任务试图获取互斥量而阻塞时,它会将自身优先级赋予低优先级的任务(将优先级继承下去),以使其能尽快得到运行,当其运行结束释放互斥量后,恢复原有优先级。

互斥量的内部实现了优先级的提升和恢复,见xSemaphoreTake和xSemaphoreGive的分析;

优先级继承不能完全消除优先级反转的问题,只是尽可能地降低优先级反转的影响;

5. 与信号量的区别

信号量存在优先级反转问题,互斥量有优先级继承,而信号量没有;

互斥量不能在中断内使用,信号量可以(因为优先级继承的特性,不能在中断内使用,只能在任务中);

使用场景的差异,信号量用于解决同步,互斥量用于解决竞争。信号量用于同步,主要任务间和中断间同步;互斥量用于互锁,用于保护同时只能有一个任务访问的资源,为资源上一把锁。

参考链接

优先级翻转与互斥信号量
FreeRTOS的信号量和互斥量