FreeRTOS--任务通知

发布时间 2024-01-04 15:05:26作者: 流翎

示例源码基于FreeRTOS V9.0.0

任务通知

1. 概述

任务通知是也是RTOS中任务通信的一种方式,区别于队列,信号量,事件组等,它不使用额外的结构体作为通信内容;

基于任务通知,可以实现轻量级的队列(长度为1)、邮箱(覆盖的队列)、计数型和二值信号量、事件组;

2. 特性

2.1 劣势

  • 仅能发送给任务,不能发送给ISR。发送方可以为任务和ISR,接收方只能为任务;
  • 仅能发送给单个任务,不能广播,而事件组可以实现广播通知;
  • 无法缓存数据,而队列可以缓存;
  • 数据只能任务独享,即只能通知特定任务,其他任务无法访问通知值;
  • 发送受阻时无法等待,只能失败退出;

2.2 优势

  • 相比于队列、信号量、事件组,其效率更高;
  • 占用内存更小,它不创建单独的结构体或内存块去缓存数据,而是仅使用任务控制块TCB中的通知值和通知状态;

3. 代码实现

3.1 通知值和通知状态

typedef struct tskTaskControlBlock{
	...
	#if( configUSE_TASK_NOTIFICATIONS == 1 )
		volatile uint32_t ulNotifiedValue;			// 通知值
		volatile uint8_t ucNotifyState;			// 通知状态
	#endif
	...
}tskTCB;

typedef tskTCB TCB_t;

任务通知的实现基于TCB中的ulNotifiedValue和ucNotifyState,使用任务通知,需要启用宏configUSE_TASK_NOTIFICATIONS。通知状态包括以下几种:

/* Values that can be assigned to the ucNotifyState member of the TCB. */
#define taskNOT_WAITING_NOTIFICATION	( ( uint8_t ) 0 )		// 初始状态,非等待
#define taskWAITING_NOTIFICATION		( ( uint8_t ) 1 )		// 等待通知,阻塞态
#define taskNOTIFICATION_RECEIVED		( ( uint8_t ) 2 )		// 收到通知

3.2 接口

任务通知有两套函数,简化版和专业版,简化版也是基于专业版实现的。

简化版 专业版
发出通知 xTaskNotifyGive vTaskNotifyGiveFromISR xTaskNotify xTaskNotifyAndQuery xTaskNotifyFromISR xTaskNotifyAndQueryFromISR
取出通知 ulTaskNotifyTake xTaskNotifyWait

3.2.1 xTaskNotify

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) PRIVILEGED_FUNCTION;
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )

xTaskNotify和xTaskNotifyAndQuery原型为xTaskGenericNotify,区别在于参数pulPreviousNotificationValue,xTaskNotify为NULL,xTaskNotifyAndQuery则提供了出参。

xTaskNotify和xTaskNotifyAndQuery在任务中使用,用于发送通知。

#if( configUSE_TASK_NOTIFICATIONS == 1 )

	BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
	{
	TCB_t * pxTCB;
	BaseType_t xReturn = pdPASS;
	uint8_t ucOriginalNotifyState;

		configASSERT( xTaskToNotify );
		pxTCB = ( TCB_t * ) xTaskToNotify;

		taskENTER_CRITICAL();
		{
			if( pulPreviousNotificationValue != NULL )
			{
				*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
			}

			ucOriginalNotifyState = pxTCB->ucNotifyState;

			pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;

			switch( eAction )
			{
				case eSetBits	:
					pxTCB->ulNotifiedValue |= ulValue;
					break;

				case eIncrement	:
					( pxTCB->ulNotifiedValue )++;
					break;

				case eSetValueWithOverwrite	:
					pxTCB->ulNotifiedValue = ulValue;
					break;

				case eSetValueWithoutOverwrite :
					if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
					{
						pxTCB->ulNotifiedValue = ulValue;
					}
					else
					{
						/* The value could not be written to the task. */
						xReturn = pdFAIL;
					}
					break;

				case eNoAction:
					/* The task is being notified without its notify value being
					updated. */
					break;
			}

			traceTASK_NOTIFY();

			/* If the task is in the blocked state specifically to wait for a
			notification then unblock it now. */
			if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
			{
				( void ) uxListRemove( &( pxTCB->xStateListItem ) );
				prvAddTaskToReadyList( pxTCB );

				/* The task should not have been on an event list. */
				configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );

				#if( configUSE_TICKLESS_IDLE != 0 )
				{
					/* If a task is blocked waiting for a notification then
					xNextTaskUnblockTime might be set to the blocked task's time
					out time.  If the task is unblocked for a reason other than
					a timeout xNextTaskUnblockTime is normally left unchanged,
					because it will automatically get reset to a new value when
					the tick count equals xNextTaskUnblockTime.  However if
					tickless idling is used it might be more important to enter
					sleep mode at the earliest possible time - so reset
					xNextTaskUnblockTime here to ensure it is updated at the
					earliest possible time. */
					prvResetNextTaskUnblockTime();
				}
				#endif

				if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
				{
					/* The notified task has a priority above the currently
					executing task so a yield is required. */
					taskYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		taskEXIT_CRITICAL();

		return xReturn;
	}

#endif /* configUSE_TASK_NOTIFICATIONS */

代码分析

  • 进入临界区,关闭中断;
  • 如果pulPreviousNotificationValue != NULL,则将通知值pxTCB->ulNotifiedValue作为赋给*pulPreviousNotificationValue,作为出参,此时的通知值是上一次的,修改前的。
  • 先备份通知状态,再修改通知状态为taskNOTIFICATION_RECEIVED,标识已经接收到通知了;
  • 根据参数eAction对通知值做如下修改:
    • eSetBits:用于设置对应比特位,设置值为ulValue,基于此实现轻量级事件组;
    • eIncrement:累加通知值,基于此实现轻量级信号量;
    • eSetValueWithOverwrite:覆盖通知值,将其设置为ulValue。基于此实现轻量级邮箱;
    • eSetValueWithoutOverwrite:仅在通知值被读取后才设置,设置值为ulValue,否则不修改通知值,标记返回失败,基于此实现轻量级的长度为1的队列;
    • eNoAction:什么都不做;
  • 如果任务原先是在等待通知的,即阻塞态,那么将其加入就绪列表,待唤醒执行。
  • 出临界区,开中断;

参数ulValue依赖于参数eAction,仅当其为eSetBits、eSetValueWithOverwrite和eSetValueWithoutOverwrite才有效;

函数xTaskGenericNotify仅在eAction为eSetValueWithoutOverwrite且通知值尚未读出时才返回失败pdFAIL,其他情况均返回成功pdPASS;

3.2.2 xTaskNotifyFromISR

BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )

xTaskNotifyFromISR和xTaskNotifyAndQueryFromISR原型为xTaskGenericNotifyFromISR,区别在于参数pulPreviousNotificationValue,xTaskNotifyFromISR为NULL,xTaskNotifyAndQueryFromISR则提供了出参。

xTaskNotifyFromISR和xTaskNotifyAndQueryFromISR在中断上下文中使用,用于发送通知。

#if( configUSE_TASK_NOTIFICATIONS == 1 )

	BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )
	{
	TCB_t * pxTCB;
	uint8_t ucOriginalNotifyState;
	BaseType_t xReturn = pdPASS;
	UBaseType_t uxSavedInterruptStatus;

		configASSERT( xTaskToNotify );

		/* RTOS ports that support interrupt nesting have the concept of a
		maximum	system call (or maximum API call) interrupt priority.
		Interrupts that are	above the maximum system call priority are keep
		permanently enabled, even when the RTOS kernel is in a critical section,
		but cannot make any calls to FreeRTOS API functions.  If configASSERT()
		is defined in FreeRTOSConfig.h then
		portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
		failure if a FreeRTOS API function is called from an interrupt that has
		been assigned a priority above the configured maximum system call
		priority.  Only FreeRTOS functions that end in FromISR can be called
		from interrupts	that have been assigned a priority at or (logically)
		below the maximum system call interrupt priority.  FreeRTOS maintains a
		separate interrupt safe API to ensure interrupt entry is as fast and as
		simple as possible.  More information (albeit Cortex-M specific) is
		provided on the following link:
		http://www.freertos.org/RTOS-Cortex-M3-M4.html */
		portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

		pxTCB = ( TCB_t * ) xTaskToNotify;

		uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
		{
			if( pulPreviousNotificationValue != NULL )
			{
				*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
			}

			ucOriginalNotifyState = pxTCB->ucNotifyState;
			pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;

			switch( eAction )
			{
				case eSetBits	:
					pxTCB->ulNotifiedValue |= ulValue;
					break;

				case eIncrement	:
					( pxTCB->ulNotifiedValue )++;
					break;

				case eSetValueWithOverwrite	:
					pxTCB->ulNotifiedValue = ulValue;
					break;

				case eSetValueWithoutOverwrite :
					if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
					{
						pxTCB->ulNotifiedValue = ulValue;
					}
					else
					{
						/* The value could not be written to the task. */
						xReturn = pdFAIL;
					}
					break;

				case eNoAction :
					/* The task is being notified without its notify value being
					updated. */
					break;
			}

			traceTASK_NOTIFY_FROM_ISR();

			/* If the task is in the blocked state specifically to wait for a
			notification then unblock it now. */
			if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
			{
				/* The task should not have been on an event list. */
				configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );

				if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
				{
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					prvAddTaskToReadyList( pxTCB );
				}
				else
				{
					/* The delayed and ready lists cannot be accessed, so hold
					this task pending until the scheduler is resumed. */
					vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
				}

				if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
				{
					/* The notified task has a priority above the currently
					executing task so a yield is required. */
					if( pxHigherPriorityTaskWoken != NULL )
					{
						*pxHigherPriorityTaskWoken = pdTRUE;
					}
					else
					{
						/* Mark that a yield is pending in case the user is not
						using the "xHigherPriorityTaskWoken" parameter to an ISR
						safe FreeRTOS function. */
						xYieldPending = pdTRUE;
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

		return xReturn;
	}

#endif /* configUSE_TASK_NOTIFICATIONS */

代码分析

  • 如果pulPreviousNotificationValue != NULL,则将通知值pxTCB->ulNotifiedValue作为赋给*pulPreviousNotificationValue,作为出参,此时的通知值是上一次的,修改前的。
  • 先备份通知状态,再修改通知状态为taskNOTIFICATION_RECEIVED,标识已经接收到通知了;
  • 根据参数eAction对通知值做相应修改,同xTaskGenericNotify;
  • 如果任务原先是在等待通知的,即阻塞态,那么:
    • 调度器没有挂起,则将任务加入就绪列表(ready list),等待唤醒执行;
    • 调度器挂起,则将任务加入待就绪列表(pending ready list);
  • 如果任务优先级高于当前任务,当pxHigherPriorityTaskWoken非空时,置其为pdTRUE,标识需要任务切换,否则将全局变量xYieldPending置为pdTRUE;

xTaskGenericNotifyFromISR和xTaskGenericNotify的处理大致是相同的,区别在于ISR的接口,在设置完通知值准备唤醒任务时,需要考虑调度器是否挂起,以决定是加入ready list还是pending ready list。同时,高优先级任务获得通知时,ISR不直接让步切换,而是设置标志位;

3.2.3 xTaskNotifyWait

#if( configUSE_TASK_NOTIFICATIONS == 1 )

	BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
	{
	BaseType_t xReturn;

		taskENTER_CRITICAL();
		{
			/* Only block if a notification is not already pending. */
			if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED )
			{
				/* Clear bits in the task's notification value as bits may get
				set	by the notifying task or interrupt.  This can be used to
				clear the value to zero. */
				pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;

				/* Mark this task as waiting for a notification. */
				pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;

				if( xTicksToWait > ( TickType_t ) 0 )
				{
					prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
					traceTASK_NOTIFY_WAIT_BLOCK();

					/* All ports are written to allow a yield in a critical
					section (some will yield immediately, others wait until the
					critical section exits) - but it is not something that
					application code should ever do. */
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		taskEXIT_CRITICAL();

		taskENTER_CRITICAL();
		{
			traceTASK_NOTIFY_WAIT();

			if( pulNotificationValue != NULL )
			{
				/* Output the current notification value, which may or may not
				have changed. */
				*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
			}

			/* If ucNotifyValue is set then either the task never entered the
			blocked state (because a notification was already pending) or the
			task unblocked because of a notification.  Otherwise the task
			unblocked because of a timeout. */
			if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION )
			{
				/* A notification was not received. */
				xReturn = pdFALSE;
			}
			else
			{
				/* A notification was already pending or a notification was
				received while the task was waiting. */
				pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;
				xReturn = pdTRUE;
			}

			pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
		}
		taskEXIT_CRITICAL();

		return xReturn;
	}

#endif /* configUSE_TASK_NOTIFICATIONS */

xTaskNotifyWait用于任务取出通知。

代码分析

  • 进临界区,关闭中断;
  • 判断通知状态,如果不是等待通知,则在切换为等待通知状态前需要做如下工作:
    • 根据参数ulBitsToClearOnEntry将通知值相应比特位清0;
    • 设置通知状态为taskWAITING_NOTIFICATION,标识正在等待通知;
    • 如果参数xTicksToWait>0,代表要阻塞等待,则将调用prvAddCurrentTaskToDelayedList将任务加入delay列表,并主动让步切换任务,任务进入阻塞态;
  • 出临界区,开中断;
  • 进临界去,关闭中断(到这里,要么任务收到通知被唤醒,要么阻塞时间超时唤醒,要么是非阻塞的接口调用);
  • 如果出参pulNotificationValue非空,则将通知值赋给*pulNotificationValue;
  • 判断通知状态:
    • 如果通知状态为taskWAITING_NOTIFICATION,表示任务没有收到通知(阻塞时间到超时退出,或者非阻塞),标记返回值为pdFALSE;
    • 否则(收到了通知,或者任务已经pending?),根据参数ulBitsToClearOnExit将通知值的相应比特位清0;
  • 设置通知状态为taskNOT_WAITING_NOTIFICATION;
  • 退出临界区,开中断;

xTaskNotifyWait可以根据参数ulBitsToClearOnEntry在等待通知前重置某些比特位,也可以根据参数ulBitsToClearOnExit在收到通知后重置某些比特位。通知值根据出参pulNotificationValue返回。

3.2.4 xTaskNotifyGive

#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )

xTaskNotifyGive的原型也是xTaskGenericNotify,同xTaskNotify。它也是用于发送通知。

xTaskNotifyGive与xTaskNotify区别在于,xTaskNotifyGive限定了eAction为eIncrement,ulValue为0(当eAction为eIncrement时,该值无用)。xTaskNotifyGive是xTaskNotify的轻量型信号量简化版接口。

3.2.5 vTaskNotifyGiveFromISR

#if( configUSE_TASK_NOTIFICATIONS == 1 )

	void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
	{
	TCB_t * pxTCB;
	uint8_t ucOriginalNotifyState;
	UBaseType_t uxSavedInterruptStatus;

		configASSERT( xTaskToNotify );

		/* RTOS ports that support interrupt nesting have the concept of a
		maximum	system call (or maximum API call) interrupt priority.
		Interrupts that are	above the maximum system call priority are keep
		permanently enabled, even when the RTOS kernel is in a critical section,
		but cannot make any calls to FreeRTOS API functions.  If configASSERT()
		is defined in FreeRTOSConfig.h then
		portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
		failure if a FreeRTOS API function is called from an interrupt that has
		been assigned a priority above the configured maximum system call
		priority.  Only FreeRTOS functions that end in FromISR can be called
		from interrupts	that have been assigned a priority at or (logically)
		below the maximum system call interrupt priority.  FreeRTOS maintains a
		separate interrupt safe API to ensure interrupt entry is as fast and as
		simple as possible.  More information (albeit Cortex-M specific) is
		provided on the following link:
		http://www.freertos.org/RTOS-Cortex-M3-M4.html */
		portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

		pxTCB = ( TCB_t * ) xTaskToNotify;

		uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
		{
			ucOriginalNotifyState = pxTCB->ucNotifyState;
			pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED;

			/* 'Giving' is equivalent to incrementing a count in a counting
			semaphore. */
			( pxTCB->ulNotifiedValue )++;

			traceTASK_NOTIFY_GIVE_FROM_ISR();

			/* If the task is in the blocked state specifically to wait for a
			notification then unblock it now. */
			if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
			{
				/* The task should not have been on an event list. */
				configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );

				if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
				{
					( void ) uxListRemove( &( pxTCB->xStateListItem ) );
					prvAddTaskToReadyList( pxTCB );
				}
				else
				{
					/* The delayed and ready lists cannot be accessed, so hold
					this task pending until the scheduler is resumed. */
					vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
				}

				if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
				{
					/* The notified task has a priority above the currently
					executing task so a yield is required. */
					if( pxHigherPriorityTaskWoken != NULL )
					{
						*pxHigherPriorityTaskWoken = pdTRUE;
					}
					else
					{
						/* Mark that a yield is pending in case the user is not
						using the "xHigherPriorityTaskWoken" parameter in an ISR
						safe FreeRTOS function. */
						xYieldPending = pdTRUE;
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
	}

#endif /* configUSE_TASK_NOTIFICATIONS */

vTaskNotifyGiveFromISR与xTaskGenericNotifyFromISR类似,是其轻量型信号量简化实现版。用于在ISR中发送任务通知。

vTaskNotifyGiveFromISR对于通知值的处理是累加(该接口默认了对通知值的处理是eIncrement,专用于信号量)。

代码分析见xTaskGenericNotifyFromISR,不再赘述。

3.2.6 ulTaskNotifyTake

#if( configUSE_TASK_NOTIFICATIONS == 1 )

	uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
	{
	uint32_t ulReturn;

		taskENTER_CRITICAL();
		{
			/* Only block if the notification count is not already non-zero. */
			if( pxCurrentTCB->ulNotifiedValue == 0UL )
			{
				/* Mark this task as waiting for a notification. */
				pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION;

				if( xTicksToWait > ( TickType_t ) 0 )
				{
					prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
					traceTASK_NOTIFY_TAKE_BLOCK();

					/* All ports are written to allow a yield in a critical
					section (some will yield immediately, others wait until the
					critical section exits) - but it is not something that
					application code should ever do. */
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		taskEXIT_CRITICAL();

		taskENTER_CRITICAL();
		{
			traceTASK_NOTIFY_TAKE();
			ulReturn = pxCurrentTCB->ulNotifiedValue;

			if( ulReturn != 0UL )
			{
				if( xClearCountOnExit != pdFALSE )
				{
					pxCurrentTCB->ulNotifiedValue = 0UL;
				}
				else
				{
					pxCurrentTCB->ulNotifiedValue = ulReturn - 1;
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
		}
		taskEXIT_CRITICAL();

		return ulReturn;
	}

#endif /* configUSE_TASK_NOTIFICATIONS */

ulTaskNotifyTake用于取出通知值,与xTaskNotifyGive或vTaskNotifyGiveFromISR搭配使用,专用于轻量型信号量;

代码分析

  • 进临界区,关闭中断;
  • 判断通知值是否为0(信号量为0),如果是,则在切换为等待通知状态前需要做如下工作:
    • 将通知状态设置为taskWAITING_NOTIFICATION,标识等待通知(等待信号量);
    • 参数xTicksToWait>0,表示阻塞等待通知信号量,调用prvAddCurrentTaskToDelayedList将任务加入delay list,并主动让步,任务进入阻塞态;
  • 出临界区,开中断;
  • 进临界区,关闭中断(到这里,要么任务收到通知被唤醒,要么阻塞时间超时唤醒,要么是非阻塞的接口调用);
  • 标记ulReturn为通知值;
  • 如果通知值不为0,说明已经收到通知(其他任务或ISR释放了信号量)。根据参数xClearCountOnExit执行:
    • 如果xClearCountOnExit为pdTRUE,则将通知值清0;
    • 否则,将通知值减一;
  • 设置通知状态为taskNOT_WAITING_NOTIFICATION;
  • 出临界区,开中断;
  • 返回ulReturn(通知值/信号量);

4. 总结

专业版接口

对于xTaskNotify/xTaskNotifyFromISR,根据参数eAction,任务通知即为轻量型的事件组、信号量、队列(深度为1)、邮箱。

eAction 说明
eSetBits 用于设置对应比特位,设置值为ulValue,基于此实现轻量级事件组
eIncrement 累加通知值,基于此实现轻量级信号量
eSetValueWithOverwrite 覆盖通知值,将其设置为ulValue。基于此实现轻量级邮箱
eSetValueWithoutOverwrite 仅在通知值被读取后才设置,设置值为ulValue,否则不修改通知值,标记返回失败,基于此实现轻量级的长度为1的队列
eNoAction 什么都不做

简化版接口

对于xTaskNotifyGive/vTaskNotifyGiveFromISR/ulTaskNotifyTake,其是轻量级的信号量实现。

参考链接

百问网-FreeRTOS入门与工程实践-基于STM32F103-第15章任务通知