8. FreeRTOS信号量的简易分析

FreeRTOS信号量简易分析

  • 架构:Cortex-M3
  • 版本:FreeRTOS V9.0.0
  • 前言:在多线程的系统中,通常要处理一些同步或资源竞争的问题,这时候可以使用信号量。

1.二值信号量

1.1二值信号量的创建

	#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

​ 创建后会返回一个句柄,用于以后获取和释放信号量。可以看到,创建二值信号量和创建队列调用同一个函数xQueueGenericCreate,只不过二值信号量固定是创建一个队列项为1、队列大小为0。xQueueGenericCreate之前讨论过了,这里就不浪费时间了。

​ 初始化后的结构体:

1.2 释放二值信号量

1.2.1 普通释放二值信号量

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

​ 要传入二值信号量句柄进去。可以看到这个和入队的函数xQueueGenericSend又是一样的,但是在调用prvCopyDataToQueue时并不是真正的入队,而只是uxMessagesWaiting加1就完事了,那么基本就能判断出来,二值信号量是以uxMessagesWaiting来判断是否有效。之后的流程和入队没有什么区别。

1.2.2 中断中释放二值信号量

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

1.3 获取二值信号量

1.3.1 普通获取二值信号量

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

要传入二值信号量句柄和阻塞时间进去。这个宏和出队函数xQueueGenericReceive也是一样的。如果当前uxMessagesWaiting>0,就是单纯的uxMessagesWaiting减1,然后退出。如果当前没收到,就挂载到delaylist上。

1.3.2 中断中获取二值信号量

#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )

2.计数信号量

2.1 创建计数信号量

	#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

创建计数信号量要传入两个值,最大数量和初始数量。

具体分析xQueueCreateCountingSemaphore


	QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
	{
	QueueHandle_t xHandle;

		configASSERT( uxMaxCount != 0 );
		configASSERT( uxInitialCount <= uxMaxCount );

		xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );

		if( xHandle != NULL )
		{
			( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;

			traceCREATE_COUNTING_SEMAPHORE();
		}
		else
		{
			traceCREATE_COUNTING_SEMAPHORE_FAILED();
		}

		return xHandle;
	}

很明显,表面上的创建计数信号量,背地里又是创建队列xQueueGenericCreate,创建队列的参数把uxMaxCount传入,queueSEMAPHORE_QUEUE_ITEM_LENGTH为0。

根据程序画出初始化后的结构体:

2.2获取计数信号量

2.2.1普通获取计数信号量

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

和二值是一样的,如果把初始值设置为0,最大值设置为1,那就是二值信号量了。

2.2.2 中断中获取计数信号量

#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )

2.3 释放计数信号量

2.3.1 普通释放计数信号量

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

同样和二值信号量一样,没什么好说的。

2.3.2 中断中释放计数信号量

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

3.互斥量

3.1互斥量的创建

用途:通常使用互斥量是为了同一个资源只能由一个任务访问。

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

具体看xQueueCreateMutex

#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 */

​ 可以看到又是熟悉的xQueueGenericCreate,所以当你能看懂队列部分,那信号量这一篇基本上是信手拈来。

uxMutexLength是1,uxMutexSize是0。其中和其它类型的信号量这个函数不同的是prvInitialiseMutex

具体看函数prvInitialiseMutex

	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();
		}
	}

pxMutexHolder是pcTail的宏定义,uxQueueType是pcHead的宏定义。互斥量创建的时候最大的不同是会调用xQueueGenericSend,调用了后会使得uxMessagesWaiting变为1。

3.2释放互斥量

互斥量被某个任务释放时,代表着这个资源被任务占用,其它相同优先级的任务是无法访问的。

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

​ 互斥量的释放也是调用xQueueGenericReceive,按正常流程走,很简单的把uxMessagesWaiting减1,并且把使用该互斥量的任务记录在pxMutexHolder中。当前任务pxCurrentTCB的持有互斥量个数uxMutexesHeld加1。

3.3获取互斥量

互斥量被某个任务获取时,代表着这个资源被任务解放出来,其它任务可以正常访问

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

​ 互斥量的获取调用的是xQueueGenericSend,按照正常流程走,把uxMessagesWaiting加1,然后把pxMutexHolder清空,表示当前已经没有任务使用这个资源了。同样的再把uxMutexesHeld减1。

3.4优先级反转

使用互斥量,能够降低优先级反转带来的影响,注意只是降低。

3.4.1 什么是优先级反转

比如我们创建了三个任务A、B、C,A的优先级为1B的优先级为2C的优先级为3。B、C处于一个阻塞的状态,此时A任务处于运行状态,使用互斥量来访问资源X,在访问的过程中,B、C由于外部因素从阻塞状态变为等待状态,那么调度器就会优先从A任务切换到C任务,原因是C的优先级比A和B的优先级大。那么这时,C想要去访问资源X,发现访问不了,原因是资源X已经被A任务占有了,迫于无奈,C只能进入阻塞状态并切换到B任务,等B任务执行完了,B任务就进入阻塞状态,那么CPU的使用权又到了A任务上,A任务就慢悠悠的执行直到释放资源X,释放了资源X后,C才能从阻塞状态变为执行状态,并且去访问资源X。(以上只是举个没有优先级反转处理的例子,实际上FreeRTOS的互斥量是有处理优先级反转这个问题的)。通过这个例子可以看到,明明C的优先级是最高的,却让B先执行了。

没有优先级反转处理的任务执行流程:

A(被高优先级抢占)->C(无法访问资源X而阻塞)->B(正常执行并切换任务)->A(释放了资源X,而被高优先级抢占)->C(正常执行)

3.4.2 FreeRTOS对优先级反转的处理

依旧举上面这个例子创建三个任务A、B、C,**A的优先级为1**,**B的优先级为2**,**C的优先级为3**。B、C处于一个阻塞的状态,此时A任务处于运行状态,使用互斥量来访问资源X,结合FreeRTOS互斥量的代码来分析A在使用互斥量时还做了什么:

	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;
	}

它把A这个任务记录了下来。那么此时外部因素使得C任务先执行,C也要访问这个资源X,结合代码看看发生了什么:

#if ( configUSE_MUTEXES == 1 )
{
    if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
    {
        taskENTER_CRITICAL();
        {
            vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
        }
        taskEXIT_CRITICAL();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
#endif

进入临界区并调用vTaskPriorityInherit

	void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
	{
	TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;

		/* If the mutex was given back by an interrupt while the queue was
		locked then the mutex holder might now be NULL. */
		if( pxMutexHolder != NULL )
		{
			/* If the holder of the mutex has a priority below the priority of
			the task attempting to obtain the mutex then it will temporarily
			inherit the priority of the task attempting to obtain the mutex. */
			if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
			{
				/* Adjust the mutex holder state to account for its new
				priority.  Only reset the event list item value if the value is
				not	being used for anything else. */
				if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
				{
					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				/* If the task being modified is in the ready state it will need
				to be moved into a new list. */
				if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxTCB->uxPriority ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
				{
					if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{
						taskRESET_READY_PRIORITY( pxTCB->uxPriority );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* Inherit the priority before being moved into the new list. */
					pxTCB->uxPriority = pxCurrentTCB->uxPriority;
					prvAddTaskToReadyList( pxTCB );
				}
				else
				{
					/* Just inherit the priority. */
					pxTCB->uxPriority = pxCurrentTCB->uxPriority;
				}

				traceTASK_PRIORITY_INHERIT( pxTCB, pxCurrentTCB->uxPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

首先会判断C任务pxCurrentTCB->uxPriority的优先级是不是大于A优先级pxTCB->uxPriority,很明显是大于的,把A任务从旧的优先级List(优先级:1)上移除,并挂载到新的优先级List(优先级:3)上,然后把A优先级变得和C优先级一样为3,A从阻塞状态变为等待状态。那么此时就会去执行A任务,A就会执行到xSemaphoreGive把互斥量归还,具体看xSemaphoreGive做了什么:

#if ( configUSE_MUTEXES == 1 )
{
    if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
    {
        /* The mutex is no longer being held. */
        xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );
        pxQueue->pxMutexHolder = NULL;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
#endif /* configUSE_MUTEXES */

可以看到真正处理优先级问题的是xTaskPriorityDisinherit

接着看xTaskPriorityDisinherit

#if ( configUSE_MUTEXES == 1 )

	BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
	{
	TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
	BaseType_t xReturn = pdFALSE;

		if( pxMutexHolder != NULL )
		{
			/* A task can only have an inherited priority if it holds the mutex.
			If the mutex is held by a task then it cannot be given from an
			interrupt, and if a mutex is given by the holding task then it must
			be the running state task. */
			configASSERT( pxTCB == pxCurrentTCB );

			configASSERT( pxTCB->uxMutexesHeld );
			( pxTCB->uxMutexesHeld )--;

			/* Has the holder of the mutex inherited the priority of another
			task? */
			if( pxTCB->uxPriority != pxTCB->uxBasePriority )
			{
				/* Only disinherit if no other mutexes are held. */
				if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
				{
					/* A task can only have an inherited priority if it holds
					the mutex.  If the mutex is held by a task then it cannot be
					given from an interrupt, and if a mutex is given by the
					holding	task then it must be the running state task.  Remove
					the	holding task from the ready	list. */
					if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{
						taskRESET_READY_PRIORITY( pxTCB->uxPriority );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* Disinherit the priority before adding the task into the
					new	ready list. */
					traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
					pxTCB->uxPriority = pxTCB->uxBasePriority;

					/* Reset the event list item value.  It cannot be in use for
					any other purpose if this task is running, and it must be
					running to give back the mutex. */
					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
					prvAddTaskToReadyList( pxTCB );

					/* Return true to indicate that a context switch is required.
					This is only actually required in the corner case whereby
					multiple mutexes were held and the mutexes were given back
					in an order different to that in which they were taken.
					If a context switch did not occur when the first mutex was
					returned, even if a task was waiting on it, then a context
					switch should occur when the last mutex is returned whether
					a task is waiting on it or not. */
					xReturn = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		return xReturn;
	}

#endif /* configUSE_MUTEXES */

​ 任务A的uxMutexesHeld减1,在创建A任务时,uxBasePriority里面是A最开始的优先级,对比现在的优先级,发现变了,就会把A从优先级List(优先级:3)上移除,并初始化uxTopReadyPriority,又把A的优先级从3变回了1,把A又挂载到了优先级List(优先级:1)上,返回退出。此时就会去执行C任务了,C任务就会发现资源X没人占用,便继续执行下去,执行完了C程序就自然地进入阻塞状态,B任务就会得到执行。

有优先级反转处理的任务执行流程:

​ A(被高优先级抢占)->C(无法访问资源X而修改A的优先级)->A(优先级被修改而执行,释放资源X,并改回原来的优先级,被高优先级抢占)->C(正常访问资源X,正常阻塞)->B(正常执行)

​ 这两者对比,有优先级反转处理的高优先级任务等待的时间是减少了,并且也符合抢占优先级这个机制。

4.递归互斥量

4.1 创建递归互斥量

	#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )

故名思意,就是在递归的时候使用的互斥量。

具体看创建函数:

	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;
	}

它和互斥量调用的是同一个函数,但传进的参数是queueQUEUE_TYPE_RECURSIVE_MUTEX。它和创建互斥量的初始化过程是一样的。

4.2 释放递归互斥量

	#define xSemaphoreGiveRecursive( xMutex )	xQueueGiveMutexRecursive( ( xMutex ) )

具体看xQueueGiveMutexRecursive


	BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
	{
	BaseType_t xReturn;
	Queue_t * const pxMutex = ( Queue_t * ) xMutex;

		configASSERT( pxMutex );

		/* If this is the task that holds the mutex then pxMutexHolder will not
		change outside of this task.  If this task does not hold the mutex then
		pxMutexHolder can never coincidentally equal the tasks handle, and as
		this is the only condition we are interested in it does not matter if
		pxMutexHolder is accessed simultaneously by another task.  Therefore no
		mutual exclusion is required to test the pxMutexHolder variable. */
		if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) /*lint !e961 Not a redundant cast as TaskHandle_t is a typedef. */
		{
			traceGIVE_MUTEX_RECURSIVE( pxMutex );

			/* uxRecursiveCallCount cannot be zero if pxMutexHolder is equal to
			the task handle, therefore no underflow check is required.  Also,
			uxRecursiveCallCount is only modified by the mutex holder, and as
			there can only be one, no mutual exclusion is required to modify the
			uxRecursiveCallCount member. */
			( pxMutex->u.uxRecursiveCallCount )--;

			/* Has the recursive call count unwound to 0? */
			if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
			{
				/* Return the mutex.  This will automatically unblock any other
				task that might be waiting to access the mutex. */
				( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			xReturn = pdPASS;
		}
		else
		{
			/* The mutex cannot be given because the calling task is not the
			holder. */
			xReturn = pdFAIL;

			traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
		}

		return xReturn;
	}

​ 如果当前任务不是递归互斥量的所有者,那么就会退出。然后对uxRecursiveCallCount减1。如果减到为0的时候,uxMessagesWaiting加1。

4.3 获取递归互斥量

	#define xSemaphoreTakeRecursive( xMutex, xBlockTime )	xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )

​ 具体调用xQueueTakeMutexRecursive

BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
	{
	BaseType_t xReturn;
	Queue_t * const pxMutex = ( Queue_t * ) xMutex;

		configASSERT( pxMutex );

		/* Comments regarding mutual exclusion as per those within
		xQueueGiveMutexRecursive(). */

		traceTAKE_MUTEX_RECURSIVE( pxMutex );

		if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */
		{
			( pxMutex->u.uxRecursiveCallCount )++;
			xReturn = pdPASS;
		}
		else
		{
			xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );

			/* pdPASS will only be returned if the mutex was successfully
			obtained.  The calling task may have entered the Blocked state
			before reaching here. */
			if( xReturn != pdFAIL )
			{
				( pxMutex->u.uxRecursiveCallCount )++;
			}
			else
			{
				traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
			}
		}

		return xReturn;
	}

​ 先判断当前是不是任务获取了这个递归互斥量,如果是,uxRecursiveCallCount加1,如果不是,那么可能是当前递归互斥量还不属于任何一个任务的,这种情况就会调用xQueueGenericReceive,然后uxMessagesWaiting会减1,uxRecursiveCallCount加1。

总结:

​ 也就是说当第一个获取递归互斥量时,uxMessagesWaiting会减一,当最后一次释放递归互斥量时,uxMessagesWaiting加1。真正记录递归次数的是uxRecursiveCallCount,获取一次就会加1,释放一次就减1


原文地址:https://www.cnblogs.com/r1chie/p/14158914.html