FreeRTOS学习笔记(四)——应用开发(二)

news2024/11/30 12:40:21

文章目录

    • 0x01 互斥量
      • 互斥量的优先级继承机制
      • 互斥量应用场景
      • 互斥量运作机制
      • 互斥量控制块
      • 互斥量接口函数
      • xSemaphoreCreateMutex()
      • xQueueCreateMutex()
      • prvInitialiseMutex()
      • xSemaphoreCreateRecursiveMutex()
      • vSemaphoreDelete()
      • xSemaphoreTake()
      • xQueueSemaphoreTake()中用于互斥量的部分
      • xTaskPriorityInherit()
      • xSemaphoreTakeRecursive()
      • xQueueTakeMutexRecursive()
      • xSemaphoreGive()
      • xSemaphoreGiveRecursive()
      • 程序实现
    • 0x02 事件
      • 应用场景
      • 运作机制
      • 事件控制块
      • 事件函数接口
      • xEventGroupCreate()
      • vEventGroupDelete()
      • xEventGroupSetBits()
      • xEventGroupSetBitsFromISR()
      • xEventGroupWaitBits()
      • xEventGroupClearBits()、xEventGroupClearBitsFromISR()
      • 程序实现

0x01 互斥量

互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。状态只有开锁以及闭锁

  • 开锁:当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。
  • 闭锁:当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。

当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有

持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。

  • 用于实现同步(任务之间或者任务与中断之间)——二值信号量
  • 用于保护资源的互锁(临界区资源保护)——互斥量

虽然二值信号量可以实现互斥量的保护功能,但是会产生任务优先级翻转的问题,使用互斥量可以通过优先级继承算法,可以降低优先级翻转问题产生的影响。

互斥量的优先级继承机制

在RTOS中为了降低优先级翻转的问题使用了优先级继承算法,优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此可以避免了系统资源被任何中间优先级的任务抢占。

互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有。也就是说,某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级任务使用,那么此时的互斥量是闭锁状态,也代表了没有任务能申请到这个互斥量,如果此时一个高优先级任务想要对这个资源进行访问,去申请这个互斥量,那么高优先级任务会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同,这个优先级提升的过程叫做优先级继承。这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最小。

使用没有优先级继承机制的信号量:

在这里插入图片描述
H任务需要阻塞L和M的时间。

使用互斥量:

在这里插入图片描述

只需等待L的时间。

互斥量应用场景

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁的状态。

  • 互斥量适用于:可能会引起优先级翻转的情况。
  • 递归互斥量适用于:任务可能会多次获取互斥量的情况下。这样可以避免同一任务多次递归持有而造成死锁的问题。

多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥量可被用于对临界资源的保护从而实现独占式访问。另外,互斥量可以降低信号量存在的优先级翻转问题带来的影响。

另外需要注意的是互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上下文环境毫无意义。

互斥量运作机制

用互斥量处理不同任务对临界资源的同步访问时,任务想要获得互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源,任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得以访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性。

互斥量控制块

互斥量的 API 函数实际上都是宏,它使用现有的队列机制,这些宏定义在 semphr.h 文件中,如果使用互斥量,需要包含 semphr.h 头文件。

typedef struct QueueDefinition
{
	int8_t *pcHead;					
	int8_t *pcTail;					
	int8_t *pcWriteTo;				
	
    // 一对互斥变量,使用联合体来确保两个互斥的结构体成员不会同时出现。
	union							
	{
		int8_t *pcReadFrom;						// 用于队列时pcReadFrom 指向出队消息空间的最后一个	
		UBaseType_t uxRecursiveCallCount;		// 用于互斥量时,用于计数,记录递归互斥量被调用的次数
	} u;

	List_t xTasksWaitingToSend;		
	List_t xTasksWaitingToReceive;	

    //用于队列时uxMessagesWaiting用来记录当前消息队列的消息个数 
    //用于互斥量uxMessagesWaiting表示有效互斥量个数,这个值是 1则表示互斥量有效,如果是 0 则表示互斥量无效
	volatile UBaseType_t uxMessagesWaiting;	
    //若该控制块结构体用于队列则为队列长度,能存放多少消息
    //若用于互斥量,uxLength 表示最大的信号量可用个数,uxLength 最大为 1,因为信号量要么是有效的,要么是无效的。
	UBaseType_t uxLength;
    //若用于消息队列,则表示为单个消息的大小。
    //若用于互斥量则无需存储空间,为 0 即可。
	UBaseType_t uxItemSize;			

	volatile int8_t cRxLock;		
	volatile int8_t cTxLock;		

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	
	#endif

	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;

互斥量接口函数

xSemaphoreCreateMutex()

xSemaphoreCreateMutex()用于创建一个互斥量,并返回一个互斥量句柄。该句柄的原型是一个 void 型的指针,在使用之前必须先由用户定义一个互斥量句柄。要想使用该函数必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配,其实该宏在 FreeRTOS.h 中默认定义为 1,即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案,同时还需在 FreeRTOSConfig.h 中把configUSE_MUTEXES 宏定义打开,表示使用互斥量。

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

xQueueCreateMutex()

#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )

	QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
	{
        Queue_t *pxNewQueue;
        //互斥量长度为1,即只有开锁、闭锁
        //消息空间为0,并非用于存储信息
        const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
		//调用xQueueGenericCreate进行创建,并且定义创建类型为queueQUEUE_TYPE_MUTEX
		pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
        //初始化互斥量
		prvInitialiseMutex( pxNewQueue );

		return pxNewQueue;
	}

#endif /* configUSE_MUTEXES */

prvInitialiseMutex()

#define pxMutexHolder					pcTail
#define uxQueueType						pcHead
#define queueQUEUE_IS_MUTEX				NULL
#if( configUSE_MUTEXES == 1 )

	static void prvInitialiseMutex( Queue_t *pxNewQueue )
	{
		if( pxNewQueue != NULL )
		{
			//使用宏定义重新定义了结构体中的pcTail 与 pcHead 成员变量
            //使用互斥量时无需理会消息存储区域
			pxNewQueue->pxMutexHolder = NULL;
			pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;

			/*递归互斥量需要联合体成员变量初始化 */
			pxNewQueue->u.uxRecursiveCallCount = 0;

			traceCREATE_MUTEX( pxNewQueue );

			/*释放互斥量,在创建成功时默认是有效的 */
			( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
		}
		else
		{
			traceCREATE_MUTEX_FAILED();
		}
	}

#endif /* configUSE_MUTEXES */

pxMutexHolderuxQueueType重新定义了pcTailpcHead,pcTail 与 pcHead 用于指向消息存储区域的,但是如果队列用作互斥量,那么我们就无需理会消息存储区域了。对于消息存储区域,互斥量具有一个重要的优先级继承机制,

我们要知道持有互斥量的任务是哪一个,因为只有持有互斥量的任务才能得到互斥量的所有权,所以,pxMutexHolder就被用于指向持有互斥量的任务控制块,现在初始化的时候,就初始化为 NULL,表示没有任务持有互斥量。uxQueueType 表示队列的类型,设置为 queueQUEUE_IS_MUTEX(NULL),表示的是用作互斥量。

xSemaphoreCreateRecursiveMutex()

xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量,上面的创建方式只可以被同一个任务获取一次,如果同一个任务想再次获取则会失败。递归信号量则相反,可以被同一个任务获取很多次,获取多少次就释放多少次,递归信号量与互斥量一样,都实现了优先级继承机制,可以降低优先级反转的危害。

使用该函数前需要在FreeRTOSConfig.h中,把宏configSUPPORT_DYNAMIC_ALLOCATION configUSE_RECURSIVE_MUTEXES 均定义为 1。

#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) )
	#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
#endif

vSemaphoreDelete()

互斥量的本质是信号量,直接调用 vSemaphoreDelete()函数进行删除即可。

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

xSemaphoreTake()

当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后,其他任务才能获取成功,任务通过互斥量获取函数来获取互斥量的所有权。任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有,如果互斥量处于开锁状态,那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级

#define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

xQueueSemaphoreTake()中用于互斥量的部分

BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;

#if( configUSE_MUTEXES == 1 )
	BaseType_t xInheritanceOccurred = pdFALSE;
#endif

	for( ;; )
	{
		taskENTER_CRITICAL();
		{
            // 查看队列中当前是否有消息
			if( uxSemaphoreCount > ( UBaseType_t ) 0 )
			{
                /* 读取消息并且消息出队 */
				traceQUEUE_RECEIVE( pxQueue );
                /* 获取了消息,当前消息队列的消息个数需要减一 */
				pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;

				#if ( configUSE_MUTEXES == 1 )
				{
                    /* 如果队列类型是互斥量 */
					if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
					{
						/* 如果需要的话,记录实现优先级继承所需的信息。 */
                        //获取当前任务控制块
                        //如果互斥量是有效的,获取成功后结构体成员变量 pxMutexHolder指向当前任务控制块。pvTaskIncrementMutexHeldCount()函数做了两件事,把当前任务控制块的成员变量 uxMutexesHeld 加 1,表示当前任务持有的互斥量数量,然后返回指向当前任务控制块的指针 pxCurrentTCB。
						pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); 
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif /* configUSE_MUTEXES */

				/* 判断一下消息队列中是否有等待发送消息的任务 */
				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
				{
                    /* 将任务从阻塞中恢复 */
					if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
					{
                        /* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
						queueYIELD_IF_USING_PREEMPTION();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				taskEXIT_CRITICAL();
				return pdPASS;
			}
            /* 消息队列中没有消息可读 */
			else
			{
				if( xTicksToWait == ( TickType_t ) 0 )
				{
					
					#if( configUSE_MUTEXES == 1 )
					{
						configASSERT( xInheritanceOccurred == pdFALSE );
					}
					#endif /* configUSE_MUTEXES */

					/* 不等待,直接返回 */
					taskEXIT_CRITICAL();
					traceQUEUE_RECEIVE_FAILED( pxQueue );
					return errQUEUE_EMPTY;
				}
				else if( xEntryTimeSet == pdFALSE )
				{
					/* 初始化阻塞超时结构体变量,初始化进入
					  阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
					vTaskInternalSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		taskEXIT_CRITICAL();

		vTaskSuspendAll();
		prvLockQueue( pxQueue );

		/* 检查超时时间是否已经过去了*/
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
		{
			/* 如果队列还是空的 */
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
				traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );

				#if ( configUSE_MUTEXES == 1 )
				{
                    /* 如果队列类型是互斥量 */
					if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
					{
						taskENTER_CRITICAL();
						{
                            /* 进行优先级继承 */
                            //如果互斥量是无效状态,当前任务是无法获取到互斥量的,并且用户指定了阻塞时间,那么在当前任务进入阻塞的时候,需要进行优先级继承。
							xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
						}
						taskEXIT_CRITICAL();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif
                /* 将当前任务添加到队列的等待接收列表中 
                 以及阻塞延时列表,阻塞时间为用户指定的超时时间 xTicksToWait */
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
                    /* 如果有任务优先级比当前任务高,会进行一次任务切换 */
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				/* 如果队列有消息了,就再试一次获取消息 */
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else
		{
			/* 超时时间已过,退出 */
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();

			/* 如果队列还是空的,返回错误代码 errQUEUE_EMPTY */
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
				#if ( configUSE_MUTEXES == 1 )
				{
					/*  */
					if( xInheritanceOccurred != pdFALSE )
					{
						taskENTER_CRITICAL();
						{
							UBaseType_t uxHighestWaitingPriority;

							/* xInheritanceOccurred只能在pxQueue->uxQueueType == queueQUEUE_IS_MUTEX时设置,所以不需要再次测试互斥锁类型来检查它实际上是一个互斥锁。 */
							uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
							vTaskPriorityDisinheritAfterTimeout( ( void * ) pxQueue->pxMutexHolder, uxHighestWaitingPriority );
						}
						taskEXIT_CRITICAL();
					}
				}
				#endif /* configUSE_MUTEXES */

				traceQUEUE_RECEIVE_FAILED( pxQueue );
				return errQUEUE_EMPTY;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
}
/*-----------------------------------------------------------*/

如果互斥量有效,调用获取互斥量函数后结构体成员变量 uxMessageWaiting 会减 1,然后将队列结构体成员指针 pxMutexHolder 指向任务控制块,表示这个互斥量被哪个任务持 有, 只有 这个任 务才 拥有互斥量的所有权 ,并 且该任务的控制块结构体成员uxMutexesHeld 会加 1,表示任务已经获取到互斥量。

如果此时互斥量是无效状态并且用户指定的阻塞时间为 0,则直接返回错误码(errQUEUE_EMPTY)。

而如果用户指定的阻塞超时时间不为 0,则当前任务会因为等待互斥量有效而进入阻塞状态,在将任务添加到延时列表之前,会判断当前任务和拥有互斥量的任务优先级哪个更高,如果当前任务优先级高,则拥有互斥量的任务继承当前任务优先级,也就是我们说的优先级继承机制。

xTaskPriorityInherit()

#if ( configUSE_MUTEXES == 1 )

	BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
	{
	TCB_t * const pxMutexHolderTCB = ( TCB_t * ) pxMutexHolder;
	BaseType_t xReturn = pdFALSE;

		if( pxMutexHolder != NULL )
		{
			/* 判断当前任务与持有互斥量任务的优先级 */
			if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
			{
				/* 调整互斥锁持有者等待的事件列表项的优先级 */
				if( ( listGET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
				{
					listSET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority ); 
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				/* 如果被提升优先级的任务处于就绪列表中 */
				if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
				{
                    /* 先将任务从就绪列表中移除 */
					if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{
						taskRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* 暂时提升持有互斥量任务的优先级,提升到与当前任务优先级一致*/
					pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
                    /* 再插入就绪列表中 */
					prvAddTaskToReadyList( pxMutexHolderTCB );
				}
				else
				{
					/* 如果任务不是在就绪列表中,就仅仅是提升任务优先级即可 */
					pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
				}

				traceTASK_PRIORITY_INHERIT( pxMutexHolderTCB, pxCurrentTCB->uxPriority );

				xReturn = pdTRUE;
			}
			else
			{
				if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
				{
					xReturn = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		return xReturn;
	}

#endif /* configUSE_MUTEXES */

至此,获取互斥量的操作就完成了,如果任务获取互斥量成功,那么在使用完毕需要立即释放,否则很容易造成其他任务无法获取互斥量,因为互斥量的优先级继承机制是只能将优先级危害降低,而不能完全消除。

xSemaphoreTakeRecursive()

xSemaphoreTakeRecursive()是一个用于获取递归互斥量的宏,互 斥 量 之 前 必 须 由xSemaphoreCreateRecursiveMutex()这个函数创建。要注意的是该函数不能用于获取由函数xSemaphoreCreateMutex()创建的互斥量。

#if( configUSE_RECURSIVE_MUTEXES == 1 )
	#define xSemaphoreTakeRecursive( xMutex, xBlockTime )	xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
#endif

xQueueTakeMutexRecursive()

#if ( configUSE_RECURSIVE_MUTEXES == 1 )

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

		configASSERT( pxMutex );
		traceTAKE_MUTEX_RECURSIVE( pxMutex );
		/* 如果持有互斥量的任务就是当前任务 */
		if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() )
		{
            /* u.uxRecursiveCallCount 自加,表示调用了多少次递归互斥量获取 */
            //判断一下持有递归互斥量的任务是不是当前要获取的任务,如果是,则只需要将结构体中 u.uxRecursiveCallCount 成员变量自加,表示该任务调用了多少次递归互斥量获取即可,然后返回 pdPASS,这样子就无需理会用户指定的超时时间了,效率就会很高.
			( pxMutex->u.uxRecursiveCallCount )++;
			xReturn = pdPASS;
		}
		else
		{
            /* 如果持有递归互斥量的任务不是当前任务,就只能等待递归互斥量被释放 */
            //如果不是同一个任务去获取递归互斥量,那么按照互斥量的性质,当递归互斥量有效的时候才能被获取成功。如果此时有任务持有该递归互斥量,那么当前获取递归互斥量的任务就会进入阻塞等待,阻塞超时时间 xTicksToWait 由用户指定,这其实就是消息队列的出队操作.
			xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait );

			/* 获取递归互斥量成功,记录递归互斥量的获取次数 */
			if( xReturn != pdFAIL )
			{
                //当任务获取递归互斥量成功 , 就需要把结构体中u.uxRecursiveCallCount 成员变量加 1,记录递归互斥量的获取次数,并且返回获取成功。
				( pxMutex->u.uxRecursiveCallCount )++;
			}
			else
			{
				traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
			}
		}

		return xReturn;
	}

#endif /* configUSE_RECURSIVE_MUTEXES */

xSemaphoreGive()

任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问。

FreeRTOS 给我们提供了互斥量释放函数 xSemaphoreGive(),任务可以调用 xSemaphoreGive()函数进行释放互斥量,表示我已经用完了,别人可以申请使用,互斥量的释放函数与信号量的释放函数一致,都是调用 xSemaphoreGive()函数,但是要注意的是,互斥量的释放只能在任务中,不允许在中断中释放互斥量。

使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,当任务调用xSemaphoreGive()函数时会将互斥量变为开锁状态,等待获取该互斥量的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级.

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

我们知道互斥量、信号量的释放就是调用 xQueueGenericSend()函数,但是互斥量的处理还是有一些不一样的地方,因为它有优先级继承机制,在释放互斥量的时候我们需要恢复任务的初始优先级,其实就是prvCopyDataToQueue()这个函数,该函数在 xQueueGenericSend()中被调用:

#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 */
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;

真正恢复任务的优先级函数其实是调用 xTaskPriorityDisinherit(),而且系统会将结构体的pxMutexHolder成员变量指向 NULL,表示暂时没有任务持有改互斥量,对结构体成员 uxMessagesWaiting 加 1 操作就代表了释放互 斥 量 , 表 示 此 时 互 斥 量 是 有 效 的 , 其 他 任 务 可 以 来 获 取 。

#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 )
        {
			configASSERT( pxTCB == pxCurrentTCB );
			configASSERT( pxTCB->uxMutexesHeld );
			( pxTCB->uxMutexesHeld )--;

			/* 判断优先级是否被临时提升*/
			if( pxTCB->uxPriority != pxTCB->uxBasePriority )
			{
				/* 如果任务没有持有其他互斥量 */
                //一般都是一个任务一个互斥量
				if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
				{
					/* 将任务从状态列表中删除 */
                    //无论该任务处于什么状态,因为要恢复任务的初始优先级,就必须先从状态列表中移除,待恢复初后再添加到就绪列表中,按优先级进行排序。
					if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{
						taskRESET_READY_PRIORITY( pxTCB->uxPriority );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
                    /* 在将任务添加到新的就绪列表之前,恢复任务的初始优先级 */
					pxTCB->uxPriority = pxTCB->uxBasePriority;

					/* 同时要重置等待事件列表的优先级 */
					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); 
                    /* 将任务重新添加到就绪列表中 */
					prvAddTaskToReadyList( pxTCB );

					xReturn = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		return xReturn;
	}

#endif /* configUSE_MUTEXES */

被释放前的互斥量是处于无效状态,被释放后互斥量才变得有效,除了结构体成员变量 uxMessageWaiting 加 1 外,还要判断持有互斥量的任务是否有优先级继承,如果有的话,要将任务的优先级恢复到初始值。当然,该任务必须在没有持有其它互斥量的情况下,才能将继承的优先级恢复到原始值。然后判断是否有任务要获取互斥量并且进入阻塞状态,有的话解除阻塞,最后返回成功信息(pdPASS)。

xSemaphoreGiveRecursive()

xSemaphoreGiveRecursive()是一个用于释放递归互斥量的宏。要想使用该函数必须在头文件 FreeRTOSConfig.h 把宏 configUSE_RECURSIVE_MUTEXES 定义为 1。

#if( configUSE_RECURSIVE_MUTEXES == 1 )
	#define xSemaphoreGiveRecursive( xMutex )	xQueueGiveMutexRecursive( ( xMutex ) )
#endif

xSemaphoreGiveRecursive()函数用于释放一个递归互斥量。已经获取递归互斥量的任务可以重复获取该递归互斥量。使用 xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用 xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态,别的任务就无法获取该递归互斥量。

当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作),互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级。

#if ( configUSE_RECURSIVE_MUTEXES == 1 )

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

		configASSERT( pxMutex );

		/* 判断任务是否持有这个递归互斥量 */
		if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) 
		{
			traceGIVE_MUTEX_RECURSIVE( pxMutex );

			/* 调用次数的计数值减一 */
			( pxMutex->u.uxRecursiveCallCount )--;

			/* 如果计数值减到 0 */
			if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
			{
				/* 释放成功 */
				( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			xReturn = pdPASS;
		}
		else
		{
			/* 这个任务不具备释放这个互斥量的权利 */
			xReturn = pdFAIL;

			traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
		}

		return xReturn;
	}

#endif /* configUSE_RECURSIVE_MUTEXES */

互斥量和递归互斥量的最大区别在于一个递归互斥量可以被已经获取这个递归互斥量的 任务 重复 获取, 而不 会形 成死 锁。这 个递 归调 用功能 是通 过队 列结 构体成 员u**.**uxRecursiveCallCount 实现的,这个变量用于存储递归调用的次数,每次获取递归互斥量后,这个变量加 1,在释放递归互斥量后,这个变量减 1。只有这个变量减到 0,即释放和获取的次数相等时,互斥量才能变成有效状态,然后才允许使用 xQueueGenericSend()函数释放一个递归互斥量。

程序实现

开辟三个线程,优先级为高中低,可以观察到当低优先级拿到互斥量时,中优先级以及高优先级无法进行打断:

osMutexId Mutex_Handle;
osThreadId LowHandle;
osThreadId MiddleHandle;
osThreadId HighHandle;

osMutexDef(MuxSem);
Mutex_Handle = osMutexCreate(osMutex(MuxSem));

osThreadDef(Low, Lowpriority, osPriorityAboveNormal, 0, 128);
LowHandle = osThreadCreate(osThread(Low), NULL);

osThreadDef(Middle, Middlepriority, osPriorityHigh, 0, 128);
MiddleHandle = osThreadCreate(osThread(Middle), NULL);

osThreadDef(High, Highpriority ,osPriorityRealtime, 0, 128);
HighHandle = osThreadCreate(osThread(High), NULL);

void Lowpriority(void const * argument)
{
		static uint32_t i;
		BaseType_t xReturn = pdPASS;
		while(1)
		{
				printf("Low get mutex\r\n");
				xReturn = xSemaphoreTake(Mutex_Handle,portMAX_DELAY);
				if(pdTRUE==xReturn)
					printf("Low Task Running \r\n");
				for(i = 0;i<200000;i++)
				{
						taskYIELD();
				}
				printf("Low realse mutex\r\n");
				xReturn = xSemaphoreGive(Mutex_Handle);
				HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,0);
				vTaskDelay(1000);
		}
}

void Middlepriority(void const * argument)
{
		while(1)
		{
				printf("Middle Task running!!\r\n");
				vTaskDelay(1000);
		}
}

void Highpriority(void const * argument)
{
	 BaseType_t xReturn = pdTRUE;
		while(1)
		{
				//vTaskDelay(1000);
				printf("High get mutex\r\n");
				xReturn = xSemaphoreTake(Mutex_Handle,portMAX_DELAY);
				
				if(pdTRUE==xReturn)
					printf("High Task running \r\n");
				HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,1);
				printf("HighTask release \r\n");
				xReturn = xSemaphoreGive(Mutex_Handle);
				vTaskDelay(1000);
		}
}

0x02 事件

事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输

与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理; 也可以是几个事件都发生后才唤醒任务进行事件处理。同理,也可以多个任务同步多个事件。

每一个事件组只需要很少的 RAM 空间来保存事件组的状态,其事件组存储在一个EventBits_t类型变量中,此变量在事件结构体中定义。其位数根据宏来进行定义:

  • configUSE_16_BIT_TICKS:为1,该变量为16位,其中8个位用来存储事件组。
  • configUSE_16_BIT_TICKS:为0,该变量为32位,其中24个位用来存储事件组。

每一位代表一个事件,任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。

  • 事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;

  • 事件“逻辑与”则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。

多任务环境下,任务、中断之间往往需要同步操作,一个事件发生会告知等待中的任务,即形成一个任务与任务、中断与任务间的同步。

任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS 的事件仅用于同步,不提供数据传输功能

FreeRTOS提供的事件具有如下的特点:

读取事件信息标记具有三个属性:逻辑与、逻辑或以及是否清除标记

当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记来判断当前接收的事件是否满足要求,如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下去。

应用场景

FreeRTOS 的事件用于事件类型的通讯,无数据传输,也就是说,我们可以用事件来做标志位,判断某些事件是否发生了,但是这与我们使用变量标志位来进行标识有什么样的区别?

  • 若使用全局变量,则需对全局变量进行保护,而且我们需要处理多任务对它进行修改
  • 在内核中如何进行有效管理,若使用全局变量则需要在任务中轮询查看事件是否发送,不断地轮询查看一直是在浪费CPU的资源,还有等待超时的机制,若使用全局变量,则需要用户自己去实现。

事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断与任务间的同步。一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作是不可累计的,而信号量的释放动作是可累计的。事件另外一个特性是,接收任务可等待多种事件,即多个事件对应一个任务或多个任务。同时按照任务等待的参数,可选择是“逻辑或”触发还是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一同步动作,而不能同时等待多个事件的同步。

运作机制

接收事件时,可以设置触发条件是单个还是多个事件类型,也就是逻辑与、逻辑或的选项。其实就是一个位图,通过每个位的比对,形成一个触发条件。事件接收成功后,必须使用xClearOnExit选项来清除已接收到的事件类型,否则不会清除已接收到的事件,需要用户显示清除事件位。

用户可 以自定义 通过传入参数xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。

设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。

清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。事件不与任务相关联,事件相互独立,一个 32位的变量(事件集合,实际用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1表示该事件类型已经发生)。

在这里插入图片描述

其事件唤醒机制可以归纳为如下:

假设当前具有任务A,任务B,这个时候A任务需要等待事件C或事件D的到来而进行唤醒,B任务需要等地啊事件C与事件D的到来而进行唤醒。那么这个时候事件C到来,则任务A唤醒,执行操作,而任务B还在等待D的到来,过了段事件,D事件到来,则A还在继续执行,B任务唤醒,进行执行。

事件控制块

事件标志组存储在一个EventBits_t类型的变量中,该变量在事件组结构体xEventGroupDefinition中定义,除了事件标志组变量之外,RTOS还使用了一个链表来记录等待事件的任务,所有在等待事件的任务均会被挂在等待事件列表xTasksWaitingForBits

typedef uint32_t TickType_t;
typedef TickType_t EventBits_t;

typedef struct xEventGroupDefinition
{
	EventBits_t uxEventBits;
	List_t xTasksWaitingForBits;		/*< List of tasks waiting for a bit to be set. */

	#if( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxEventGroupNumber;
	#endif

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the event group is statically allocated to ensure no attempt is made to free the memory. */
	#endif
} EventGroup_t;

事件函数接口

xEventGroupCreate()

此函数用于创建一个事件组,并返回对应的句柄。若想使用该函数,则需要在头文件FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION为1,并且需要把 FreeRTOS/source/event_groups.c这个C文件添加到工程中。使用此函数进行创建是使用RAM动态内存分配的。如果静态需要使用函数xEventGroupCreateStatic()

当创建一个事件时,系统会首先给我们分配事件控制块的内存空间,然后对该事件控制块进行基本的初始化,创建成功返回事件句柄;创建失败返回 NULL。

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	EventGroupHandle_t xEventGroupCreate( void )
	{
		EventGroup_t *pxEventBits;

		// 分配事件控制块的内存
		pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) );

        //如果分配内存成功,那么久对事件控制块的成员变量进行初始化,事件标志组变量清零,因为现在是创建事件,还没有事件发生,所以事件集合中所有位都为 0,然后调用 vListInitialise()函数将事件控制块中的等待事件列表进行初始化,该列表用于记录等待在此事件上的任务。
		if( pxEventBits != NULL )
		{
			pxEventBits->uxEventBits = 0;
			vListInitialise( &( pxEventBits->xTasksWaitingForBits ) );

			#if( configSUPPORT_STATIC_ALLOCATION == 1 )
			{
				// 静态分配内存
				pxEventBits->ucStaticallyAllocated = pdFALSE;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */

			traceEVENT_GROUP_CREATE( pxEventBits );
		}
		else
		{
			traceEVENT_GROUP_CREATE_FAILED();
		}

		return ( EventGroupHandle_t ) pxEventBits;
	}

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

vEventGroupDelete()

对于只使用一次的事件,比如初始化这些流程,并且机器可以正常工作了,这个时候就可以删除事件。该函数不允许在中断里面使用。当事件组被删除之后,阻塞在该事件组上的任务都会被解锁,并向等待事件的任务返回事件组的值为 0。

void vEventGroupDelete( EventGroupHandle_t xEventGroup )
{
    EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
    const List_t *pxTasksWaitingForBits = &( pxEventBits->xTasksWaitingForBits );
    
	// 挂起调度器,因为接下来的操作不知道需要多长的时间,并且在删除的时候,不希望其他任务来操作这个事件标志组,所以暂时把调度器挂起,让当前任务占有 CPU。
	vTaskSuspendAll();
	{
		traceEVENT_GROUP_DELETE( xEventGroup );
		//当有任务被阻塞在事件等待列表中的时候,我们就要把任务恢复过来,否则删除了事件的话,就无法对事件进行读写操作,那这些任务可能永远等不到事件(因为任务有可能是一直在等待事件发生的),使用 while 循环保证所有的任务都会被恢复。
		while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 )
		{
			/* 如果有任务阻塞在这个事件上,那么就要把事件从等待事件列表中移除 */
			configASSERT( pxTasksWaitingForBits->xListEnd.pxNext != ( const ListItem_t * ) &( pxTasksWaitingForBits->xListEnd ) );
            //将任务从等待事件列表中移除,然后添加到就绪列表中,参与任务调度,当然,因为挂起了调度器,所以在这段时间里,即使是优先级更高的任务被添加到就绪列表,系统也不会进行任务调度,所以也就不会影响当前任务删除事件的操作,这也是为什么需要挂起调度器的原因。
            //尽量在没有任务阻塞在这个事件的时候进行删除,否则任务无法等到正确的事件,因为删除之后,所有被恢复的任务都只能获得事件的值为 0。
			vTaskRemoveFromUnorderedEventList( pxTasksWaitingForBits->xListEnd.pxNext, eventUNBLOCKED_DUE_TO_BIT_SET );
		}

		#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
		{
			/*释放事件的内存*/
			vPortFree( pxEventBits );
		}
		#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
		{
			/* 除静态创建释放内存部分代码 */
			if( pxEventBits->ucStaticallyAllocated == ( uint8_t ) pdFALSE )
			{
				vPortFree( pxEventBits );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
	}
    //恢复调度器,之前的操作是恢复了任务,现在恢复调度器,那么处于就绪态的最高优先级任务将被运行。
	( void ) xTaskResumeAll();
}

xEventGroupSetBits()

xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后阻塞在该位上的任务将会被解锁。使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。

简单来说,就是设置我们自己定义的事件标志位为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )
{
    ListItem_t *pxListItem, *pxNext;
    ListItem_t const *pxListEnd;
    List_t *pxList;
    EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;
    EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
    BaseType_t xMatchFound = pdFALSE;

	/*  断言,判断事件是否有效  */
	configASSERT( xEventGroup );
    /* 断言,判断要设置的事件标志位是否有效 */
	configASSERT( ( uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES ) == 0 );

	pxList = &( pxEventBits->xTasksWaitingForBits );
	pxListEnd = listGET_END_MARKER( pxList ); 
    // 挂起调度器 不知道需要处理多久 防止其他任务的干扰
	vTaskSuspendAll();
	{
		traceEVENT_GROUP_SET_BITS( xEventGroup, uxBitsToSet );

		pxListItem = listGET_HEAD_ENTRY( pxList );

		/* 设置事件标志位. */
		pxEventBits->uxEventBits |= uxBitsToSet;

		/*  设置这个事件标志位可能是某个任务在等待的事件就遍历等待事件列表中的任务 */
		while( pxListItem != pxListEnd )
		{
			pxNext = listGET_NEXT( pxListItem );
			uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );
			xMatchFound = pdFALSE;

			/* 获取要等待事件的标记信息,是逻辑与还是逻辑或  */
			uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES;
			uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES;
			
            /* 如果只需要有一个事件标志位满足即可 */
			if( ( uxControlBits & eventWAIT_FOR_ALL_BITS ) == ( EventBits_t ) 0 )
			{
				/* 判断要等待的事件是否发生了 */
				if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) != ( EventBits_t ) 0 )
				{
					xMatchFound = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
            // 否则就要所有事件都发生的时候才能解除阻塞
			else if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) == uxBitsWaitedFor )
			{
				 // 否则要所有事件都发生的时候才能解除阻塞
				xMatchFound = pdTRUE;
			}
			else
			{
				/* Need all bits to be set, but not all the bits were set. */
			}

			if( xMatchFound != pdFALSE )
			{
				/* 找到了,然后看下是否需要清除标志位 如果需要,就记录下需要清除的标志位,等遍历完队列之后统一处理*/
				if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 )
				{
					uxBitsToClear |= uxBitsWaitedFor;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				/* 将满足事件条件的任务从等待列表中移除,并且添加到就绪列表中 */
				vTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET );
			}

			/* 循环遍历事件等待列表,可能不止一个任务在等待这个事件 */
			pxListItem = pxNext;
		}

		/* 遍历完毕,清除事件标志位 */
		pxEventBits->uxEventBits &= ~uxBitsToClear;
	}
	( void ) xTaskResumeAll();

	return pxEventBits->uxEventBits;
}

xEventGroupSetBitsFromISR()

xEventGroupSetBitsFromISR() xEventGroupSetBits()的中断版本,用于置位事件组中指定的位。置位事件组中的标志位是一个不确定的操作,因为阻塞在事件组的标志位上的任务的个数是不确定的

FreeRTOS 是不允许不确定的操作在中断临界段中发生的,所以xEventGroupSetBitsFromISR()给 FreeRTOS 的守护任务发送一个消息,让置位事件组的操作在守护任务里面完成,守护任务是基于调度锁而非临界段的机制来实现的。

在中断中事件标志的置位是在守护任务(也叫软件定时器服务任务)中完成的。因此 FreeRTOS 的守护任务与其他任务一样,都是系统调度器根据其优先级进行任务调度的,但守护任务的优先级必须比任何任务的优先级都要高,保证在需要的时候能立即切换任务从而达到快速处理的目的,这是在中断中让事件标志位置位。其优先级由FreeRTOSConfig.h中的宏configTIMER_TASK_PRIORITY来定义。

其实 xEventGroupSetBitsFromISR()函数真正调用的也是 xEventGroupSetBits()函数,只不过是在守护任务中进行调用的,所以它实际上执行的上下文环境依旧是在任务中。使用该函数需要将configUSE_TIMERS INCLUDE_xTimerPendFunctionCall 置位。

在这里插入图片描述

xEventGroupWaitBits()

FreeRTOS提供了一个等待指定事件的函数,通过这个函数可以知道事件标志组中的哪些位,有什么事件发生了,然后通过“逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件消息。在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态

如果事件正确获取(等待到)则返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也会返回一个不能确定的事件值,所以需要判断任务所等待的事件是否真的发生。

在这里插入图片描述

在这里插入图片描述

/*-----------------------------------------------------------*/

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, 
                                const EventBits_t uxBitsToWaitFor, 
                                const BaseType_t xClearOnExit, 
                                const BaseType_t xWaitForAllBits, 
                                TickType_t xTicksToWait )
{
    EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
    EventBits_t uxReturn, uxControlBits = 0;
    BaseType_t xWaitConditionMet, xAlreadyYielded;
    BaseType_t xTimeoutOccurred = pdFALSE;

	/* 断言 */
	configASSERT( xEventGroup );
	configASSERT( ( uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES ) == 0 );
	configASSERT( uxBitsToWaitFor != 0 );
	#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
	{
		configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
	}
	#endif
	
    // 挂起调度器
	vTaskSuspendAll();
	{
		const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits;

		/* 先看下当前事件中的标志位是否已经满足条件了 */
        //其实就是判断一下用户等待的事件是否与当前事件标志位一致
		xWaitConditionMet = prvTestWaitCondition( uxCurrentEventBits, uxBitsToWaitFor, xWaitForAllBits );

        //满足条件了,就可以直接返回了,注意这里返回的是的当前事件的所有标志位,所以这是一个不确定的值,需要用户自己判断一下是否满足要求。然后把用户指定的等待超时时间 xTicksToWait 也重置为 0,这样子等下就能直接退出函数返回了。
		if( xWaitConditionMet != pdFALSE )
		{
			/*满足条件了,就可以直接返回了,注意这里返回的是的当前事件的所有标志位 */
			uxReturn = uxCurrentEventBits;
			xTicksToWait = ( TickType_t ) 0;

			/* 看看在退出的时候是否需要清除对应的事件标志位 */
			if( xClearOnExit != pdFALSE )
			{
				pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
        //  不满足条件,并且不等待
		else if( xTicksToWait == ( TickType_t ) 0 )
		{
			/*同样也是返回当前事件的所有标志位 */
			uxReturn = uxCurrentEventBits;
			xTimeoutOccurred = pdTRUE;
		}
        /* 用户指定超时时间了,那就进入等待状态 */
		else
		{
			/* 保存一下当前任务的信息标记,以便在恢复任务的时候对事件进行相应的操作 */
			if( xClearOnExit != pdFALSE )
			{
				uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			if( xWaitForAllBits != pdFALSE )
			{
				uxControlBits |= eventWAIT_FOR_ALL_BITS;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}

			/*  当前任务进入事件等待列表中,任务将被阻塞指定时间 xTicksToWait */
			vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | uxControlBits ), xTicksToWait );

			uxReturn = 0;

			traceEVENT_GROUP_WAIT_BITS_BLOCK( xEventGroup, uxBitsToWaitFor );
		}
	}
    // 恢复调度器
	xAlreadyYielded = xTaskResumeAll();

	if( xTicksToWait != ( TickType_t ) 0 )
	{
		if( xAlreadyYielded == pdFALSE )
		{
            /* 进行一次任务切换 */
			portYIELD_WITHIN_API();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* 进入到这里说明当前的任务已经被重新调度了 */
		uxReturn = uxTaskResetEventItemValue();

		if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 )
		{
           
			taskENTER_CRITICAL();
			{
				/* 超时返回时,直接返回当前事件的所有标志位 */
				uxReturn = pxEventBits->uxEventBits;

				/* 再判断一次是否发生了事件 */
				if( prvTestWaitCondition( uxReturn, uxBitsToWaitFor, xWaitForAllBits ) != pdFALSE )
				{
                    /* 如果发生了,那就清除事件标志位并且返回 */
					if( xClearOnExit != pdFALSE )
					{
						pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
				xTimeoutOccurred = pdTRUE;
			}
			taskEXIT_CRITICAL();
		}
		else
		{
			/* The task unblocked because the bits were set. */
		}

		/* 返回事件所有标志位 */
		uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
	}
	traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred );

	( void ) xTimeoutOccurred;

	return uxReturn;
}
/*-----------------------------------------------------------*/

当用户调用这个函数接口时,系统首先根据用户指定参数和接收选项来判断它要等待的事件是否发生,如果已经发生,则根据参数 xClearOnExit 来决定是否清除事件的相应标志位,并且返回事件标志位的值,但是这个值并不是一个稳定的值,所以在等待到对应事件的时候,还需我们判断事件是否与任务需要的一致; 如果事件没有发生,则把任务添加到事件等待列表中,把任务感兴趣的事件标志值和等待选项填用列表项的值来表示,直到事件发生或等待时间超时。

xEventGroupClearBits()、xEventGroupClearBitsFromISR()

xEventGroupClearBits()与 xEventGroupClearBitsFromISR()都是用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除,xEventGroupClearBits()函数不能在中断中使用,而是由具有中断保护功能 的xEventGroupClearBitsFromISR() 来代替,中断清除事件标志位的操作在守护任务(也叫定时器服务任务)里面完成 。

在这里插入图片描述

程序实现

开辟两个线程,一个线程控制事件的置位,另一个阻塞等待事件置位:

EventGroupHandle_t Event_Handle;

	Event_Handle = xEventGroupCreate();  /* Create the thread(s) */
  /* definition and creation of defaultTask */
  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

  /* definition and creation of Receive */
  osThreadDef(Receive, ReceiveTask, osPriorityIdle, 0, 128);
  ReceiveHandle = osThreadCreate(osThread(Receive), NULL);

  /* definition and creation of Send */
  osThreadDef(Send, SendTask, osPriorityLow, 0, 128);
  SendHandle = osThreadCreate(osThread(Send), NULL);

void ReceiveTask(void const * argument)
{
  /* USER CODE BEGIN ReceiveTask */
  /* Infinite loop */
	EventBits_t r_event;
  for(;;)
  {
		r_event = xEventGroupWaitBits(
																	Event_Handle,
																	TEST1_EVENT|TEST2_EVENT,
																	pdTRUE,
																	pdTRUE,
																	portMAX_DELAY
		);
		if((r_event &(TEST1_EVENT|TEST2_EVENT))==(TEST1_EVENT|TEST2_EVENT))
		{
				printf("all right !! the event 1 and 2 is set!!\r\n");
		}
		else
		{
				printf("event error!!\r\n");
		}
  }
  /* USER CODE END ReceiveTask */
}

/* USER CODE BEGIN Header_SendTask */
/**
* @brief Function implementing the Send thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_SendTask */
void SendTask(void const * argument)
{
  /* USER CODE BEGIN SendTask */
	int i = 0;
  /* Infinite loop */
  for(;;)
  {
			i++;
			
			if(i==10)				
			{
					printf("TEST1_EVENT is set \r\n");
					xEventGroupSetBits(Event_Handle,TEST1_EVENT);
			}
			
			if(i==15)
			{
					printf("TEST2_ENVENT is set\r\n");
					xEventGroupSetBits(Event_Handle,TEST2_EVENT);
			}
			
			vTaskDelay(1000);
  }
  /* USER CODE END SendTask */
}

效果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/527792.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过nginx配置 将vue项目运行到阿里云linux服务器上

先将 我们的vue项目打包起来 打包好之后 我们在项目根目录下 找到 dist 下的 index.html 保证这个文件要能正常运行 然后 我们将这个dist文件夹 压缩一下 然后 回到项目终端 执行 scp -r ./dist.zip 用户名(如果之前没设置过就是 root)服务器公网地址:/root然后 他会要求我…

多语言网站的外包开发流程

随着互联网/移动互联网的全球普及&#xff0c;越来越多的企业希望将产品卖向全球&#xff0c;这就首先需要有一个多语言的网站来宣传公司的产品&#xff0c;那设计和开发这样的网站需要注意什么呢&#xff0c;今天和大家分享这方面的知识。北京木奇移动技术有限公司&#xff0c…

C-函数栈帧

文章目录 函数栈帧栈帧创建栈帧销毁根据栈帧关系更改值拓展 可变参数列表基本原理整形提升 命令行参数打印环境变量 函数栈帧 int MyAdd(int a, int b) {int c 0;c a b;return c; } int main() {int x 0xA;int y 0xB;int z MyAdd(x,y);system("pause");return …

怎么隐藏回收站?3个方法轻松隐藏回收站!

案例&#xff1a;怎么隐藏回收站 【我不太想把回收站放到桌面上&#xff0c;想把它隐藏了&#xff0c;请问大家有什么好的方法可以隐藏回收站吗&#xff1f;】 回收站是一个非常常见的功能&#xff0c;允许用户恢复已删除的文件。然而&#xff0c;有些人可能不希望回收站一直…

SpringMVC高手进阶

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

【MySQL】-- 数据库基础

目录 MySQL概述 MySQL初期概念 小结 主流数据库 连接服务器 服务器&#xff0c;数据库&#xff0c;表关系 数据逻辑存储 MySQL架构 SQL分类 存储引擎 存储引擎 查看存储引擎 MySQL概述 #问&#xff1a;什么是数据库&#xff1f; MySQL初期概念 这个所谓的mysql严格…

从零开始Vue3+Element Plus后台管理系统(七)——手写一个简单的多页签组件

以前都是用别人现成的多页签组件&#xff0c;自己也想尝试下做个Vue3的版本&#xff0c;目前还只有基本功能&#xff0c;慢慢完善。 主要思路 使用 Pinia 记录页签数据、处理操作初始状态没有页签数据&#xff0c;使用默认路由数据填充右击页签&#xff0c;显示更多关闭操作…

移动云与启明星辰联合发布移动云|星辰安全品牌

数字中国时代&#xff0c;企业数字化转型不断深化&#xff0c;云安全市场发展持续高增&#xff0c;其安全更需自主可控、全程可信。基于此&#xff0c;移动云和启明星辰共同打造移动云|星辰安全品牌&#xff0c;聚力协行共筑安全云的压舱石&#xff0c;携手共塑中国云安全产业发…

原神服务器服务端多人联机教程

原神服务器服务端多人联机教程 大家好&#xff0c;我是艾西在上一篇文章中我们说了win系统服务器怎么搭建原神服务端&#xff0c;在最后结尾时有带一嘴怎么改为多人联机但不是很详细。哪么这篇文章艾西会给小伙伴们说清楚原神服务端怎么改为多人联机&#xff0c;毕竟玩游戏肯定…

MySQL高级语句(一)

一、SQL高级语句 1、 SELECT 显示表格中一个或数个栏位的所有资料 语法&#xff1a;SELECT "字段" FROM "表名"; select * from test1; select name from test1; select name,sex from test1;2、DISTINCT 不显示重复的内容 语法&#xff1a;SELECT D…

pdf怎么转换成ppt?4种方法1分钟处理

​ pdf怎么转换成ppt&#xff1f;在日常的办公中&#xff0c;经常需要进行PDF文件格式的转换。例如&#xff0c;我们从互联网上下载的许多资料都是以PDF格式保存的。此外&#xff0c;在使用Microsoft Office时&#xff0c;有些用户需要将Word文档转换为PDF格式&#xff0…

MySQL的概念、编译安装,以及自动补全

一.数据库的基本概念 1、数据&#xff08;Data&#xff09; • 描述事物的符号记录 • 包括数字&#xff0c;文字&#xff0c;图形&#xff0c;图像&#xff0c;声音&#xff0c;档案记录等 • 以“记录”形式按统一的格式进行存储 2、表 • 将不同的记录组织在一起 • …

JavaWeb08(MVC应用02[家居商城]连接数据库)

目录 一.绑定分类 1.1 效果预览 1.2 代码实现 ①底层代码 ②前端代码 二.绑定所有商品 2.1 效果预览 2.2.代码实现 ①底层代码 ②前端代码 三.分类查询 3.1效果预览 3.2代码实现 ①底层代码 ②前端代码 四.模糊查询 4.1 效果预览 4.2代码实现 ①底层代码 ②前…

一图看懂 zipp 模块:ZipFile 的一些兼容子类和补充接口,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 zipp 模块&#xff1a;ZipFile 的一些兼容子类和补充接口&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&#x1f9ca;类关…

直观理解torch.gather函数(带图)

直观理解torch.gather函数 1. gather的作用 因为深度学习里面&#xff0c;像分类或者分割&#xff0c;有时候去进行loss计算或准确度计算的时候&#xff0c;需要挑选某个维度特定的值&#xff0c;所以有了这么个函数。注意不要高估这个函数的能力&#xff0c;这个函数只是在指…

大数据技术之Sqoop

第1章 Sqoop简介 Sqoop是一款开源的工具&#xff0c;主要用于在Hadoop(Hive)与传统的数据库(mysql、postgresql…)间进行数据的传递&#xff0c;可以将一个关系型数据库&#xff08;例如 &#xff1a; MySQL ,Oracle ,Postgres等&#xff09;中的数据导进到Hadoop的HDFS中&…

破案小说中的《人月神话》和女装

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>> 在破案小说《谁是凶手》中&#xff0c;《人月神话》、《程序员修炼之道》以及女装作为素材出现了。 成功学&#xff08;鸡汤学&#xff09;书籍《用所有的存在与世界相会》…

如何制定一套有效的期货交易系统策略?

期货交易是一项全球性的金融交易&#xff0c;对于投资者来说&#xff0c;制定有效的期货交易系统策略是至关重要的。在制定期货交易策略时&#xff0c;需要考虑市场趋势、资产种类、交易成本、仓位控制等多个方面。 很多刚进入期货市场的朋友&#xff0c;甚至很多做了很久期货…

JS代码优化——逻辑判断

文章目录 JavaScript 语法篇嵌套层级优化多条件分支的优化处理使用数组新特性简化逻辑判断**多条件判断****判断数组中是否所有项都满足某条件****判断数组中是否有某一项满足条件** **函数默认值**使用默认参数使用解构与默认参数复杂数据解构 策略模式优化分支逻辑处理 JavaS…

Mars3d实现加载gif动图

官网有相关示例参考&#xff1a;功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 方式1&#xff1a; // [终点]绘制台风当前位置gif点 const gifGraphic new mars3d.graphic.DivGraphic({ position: [endItem.lon, e…