Freertos中事件标志的具体实现和概念是怎样的?

2026-05-05 16:201阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计5265个文字,预计阅读时间需要22分钟。

Freertos中事件标志的具体实现和概念是怎样的?

目录+前言+12.1+实现事件机制的预备知识+12.1.1+守护任务+12.1.2+事件的不确定性+12.1.3+事件组的报告+12.2+事件概念+12.3+事件用途参考+12.4+事件实现原理简述+12.5+事件实现所需支持问题+1

目录
  • 前言
  • 12.1 实现事件机制的预备知识
    • 12.1.1 守护任务
    • 12.1.2 事件的不确定性
    • 12.1.3 事件组的报文
  • 12.2 事件概念
  • 12.3 事件用途参考
  • 12.4 事件实现原理简述
  • 12.5 事件实现需要克服的问题
    • 12.5.1 避免在用户的应用程序中创建竞争条件
    • 12.5.2 避免不确定性
  • 12.6 事件控制块
  • 12.7 创建事件
  • 12.8 事件置位
  • 12.9 事件置位中断版
  • 12.10 等待事件
  • 12.11 清除事件
  • 12.12 删除事件


前言

默认以32bit事件类型和任务专用API讲解。

事件独立于消息队列、信号量和互斥量这些章节是因为内部实现机制不同。

参考:李柱明博客:www.cnblogs.com/lizhuming/p/16353453.html

Freertos中事件标志的具体实现和概念是怎样的?

12.1 实现事件机制的预备知识 12.1.1 守护任务

和守护进程一样理解即可。

守护任务(Daemon)又称为精灵任务,是运行在后台的一种特殊任务,周期性地执行某种任务或等待处理某些事情的发生,主要表现为以下两个特点:

  • 长期运行。守护任务是一种生存期很长的任务,一般在系统启动就开始运行,到系统退出或调用接口强制停止而结束。
  • 后台运行。用户一般不能直接接触控制该任务。用户的任务也不会影响守护任务的生存。

比如freertos的软件定时器服务任务。

12.1.2 事件的不确定性

先明白freertos不允许在中断或临界中操作不确定的业务。

而事件有个不确定的业务是因为事件的一对多特性,如当发生事件置位时,会遍历阻塞在这个事件组的链表,而阻塞在这个链表任务是不确定的。

所以任务专用的API事件置位时,不是在临界,而是在调度锁内完成的。

而中断专用的API事件置位,整个上下文都不符合要求,所以中断专用的API的事件置位实现是通过给FreeRTOS的守护任务发送一个消息,让置位事件组的操作在守护任务(软件定时器服务任务)里面完成,守护任务是基于调度锁而非临界段的机制来实现的。

12.1.3 事件组的报文

事件组报文就一个系统位长的变量。

最高位bit表示该值表示事件组有效。这个bit影响到整个系统的事件阻塞、优先级继承等等任务事件节点相关的业务的实现。

最高字节的[6:0]bit是该事件组的控制信息。

剩余bit表示各个事件。

12.2 事件概念

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

和信号量又是不同的,事件可以一对多,多对多同步。

事件(bit):

  • 0:事件没有发生。需要该事件的任务阻塞或直接返回失败。
  • 1:事件发生。需要该事件的任务解除阻塞返回成功或者直接返回成功。

事件组:多个事件组合在一起,用户可以选择等待某个事件或者等待所有事件实现同步。

12.3 事件用途参考

事件位用于指示事件是否发生。事件位通常被称为事件标志。例如,申请可以:

  • 定义一个位(或标志),当它设置为1时,表示“消息已接收并准备处理”,当它设置为0时,表示“没有消息等待处理”。
  • 定义一个位(或标志),当它设置为1时,表示“应用程序已将准备发送到网络的消息排队”,而当它设置为0时,表示“没有准备发送到网络的消息排队”。
  • 定义一个位(或标志),当它设置为1时,表示“是时候向网络发送一个心跳消息了”,而当它设置为0时,表示“还没有到发送另一个心跳消息的时候了”。

事件组是事件位的集合。事件组中的个别事件位由位号引用。展开上面提供的示例:

  • 表示“消息已接收并准备处理”的事件位可能是事件组中的位0。
  • 在同一个事件组中,表示“应用程序已将准备发送到网络的消息排队”的事件位可能是第1位。
  • 表示“是时候向网络发送心跳消息了”的事件位可能位于同一事件组中的第2位。
12.4 事件实现原理简述

核心原理就是一个全局变量+访问机制+阻塞机制。

这些组件都用控制块数据结构管理起来。

封装一些API访问即可。

事件组由EventGroupHandle_t类型的变量引用。

如果configUSE_16_BIT_TICKS设置为1,则事件组中存储的比特数(或标志数)为8,如果configUSE_16_BIT_TICKS设置为0,则为24。

configUSE_16_BIT_TICKS的依赖源于任务内部实现中用于线程本地存储的数据类型。

事件组中的所有事件位都存储在EventBits_t类型的单个无符号变量中。

事件位0存储在位0中,事件位1存储在位1中,依此类推。

如图是一个24位事件组,它使用3位来保存前面描述的3个示例事件。在图像中,只设置了事件位2。

12.5 事件实现需要克服的问题

在实现事件组时,RTOS必须克服的两个主要挑战是:应用程序竞态混合运行不确定性。

12.5.1 避免在用户的应用程序中创建竞争条件

如果出现以下情况,事件组实现将在应用程序中产生竞争条件:

  • 不清楚谁负责清除单个事件。
  • 不清楚何时要清除位。
  • 不清楚在任务退出测试位值的 API 函数时是否设置或清除了事件(可能是另一个任务或中断已更改该位的状态)。

这样对全局资源的这个事件组来说,应用层的调用很模糊,所以为了解决这些问题,避免应用程序的竞态产生,实现事件机制时可以用一下方法解决:

  • FreeRTOS 事件组实现通过包含内置智能来确保位的设置、测试和清除看起来是原子的,在处理了所有对该事件感兴趣的任务后再统一对这个事件bit做更新,从而消除了竞争条件的可能性。

    • 但是这样就会出现不确定性。第二个问题就是解决不确定性。
  • 线程本地存储(任务事件节点值存储当前任务对该事件组的信息)和 API 函数返回值的谨慎使用。

12.5.2 避免不确定性

事件组概念意味着不确定性行为,因为它不知道在一个事件组上有多少任务被阻塞,因此当事件位被设置时,不知道有多少条件需要被测试或多少任务需要被解除阻塞。

FreeRTOS 质量标准不允许在中断被禁用时或在中断服务程序中执行不确定的动作。为了确保在设置事件位时不违反这些严格的质量标准:

  • 调度锁用于确保在 RTOS 任务设置事件位时中断保持启用状态。

    • 即是原子性不使用临界,而是调度锁级别。
  • 集中延迟中断机制用于在尝试从中断服务程序设置事件位时,将设置位的动作推迟到任务。

    • 即是把中断上下文对事件的操作转包给守护任务实现,这样就维护了调度锁级别的原子性。
12.6 事件控制块

从事件控制块看就知道事件使用了非常少的RAM实现。

typedef struct EventGroupDef_t { EventBits_t uxEventBits; /* 事件组 */ List_t xTasksWaitingForBits; /* 等待事件阻塞任务链表 */ #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxEventGroupNumber; /* 事件number */ #endif #if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) uint8_t ucStaticallyAllocated; /* 标记是否为静态分配 */ #endif } EventGroup_t;

EventBits_t uxEventBits

  • 最高bit:表示当前值为事件组件使用。主要用于在任务事件节点值中区分任务优先级。
  • 最高字节的[6:0]bit:表示事件组控制信息。
  • 剩下的bit:表示各个事件。

事件控制信息:

/* The following bit fields convey control information in a task's event list item value. It is important they don't clash with the taskEVENT_LIST_ITEM_VALUE_IN_USE definition. */ #if configUSE_16_BIT_TICKS == 1 #define eventCLEAR_EVENTS_ON_EXIT_BIT 0x0100U /* 事后清空事件 */ #define eventUNBLOCKED_DUE_TO_BIT_SET 0x0200U /* 事后唤醒 */ #define eventWAIT_FOR_ALL_BITS 0x0400U /* 等待所有事件 */ #define eventEVENT_BITS_CONTROL_BYTES 0xff00U /* 事件控制字段所有bit */ #else #define eventCLEAR_EVENTS_ON_EXIT_BIT 0x01000000UL /* 事后清空事件 */ #define eventUNBLOCKED_DUE_TO_BIT_SET 0x02000000UL /* 发生事件而解除阻塞的标记 */ #define eventWAIT_FOR_ALL_BITS 0x04000000UL /* 等待所有事件 */ #define eventEVENT_BITS_CONTROL_BYTES 0xff000000UL /* 事件控制字段所有bit */ #endif

  • eventCLEAR_EVENTS_ON_EXIT_BIT:该标记用于等待事件时配置,表示获得事件触发任务解锁后,要清除这些事件。
  • eventUNBLOCKED_DUE_TO_BIT_SET:用于区分等待事件而阻塞的任务被唤醒的原因(事件或超时),该标记表示因为事件而触发唤醒。
  • eventWAIT_FOR_ALL_BITS:用于等待事件时配置,表示该任务等待标记的所有事件都发生时才有效。否则就是任意事件有效。
  • eventEVENT_BITS_CONTROL_BYTES:用于区分事件控制字段和事件字段。
12.7 创建事件

创建事件使用API xEventGroupCreate()

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) EventGroupHandle_t xEventGroupCreate( void ) { EventGroup_t * pxEventBits; /* 申请事件组资源 */ pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) ); 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 pxEventBits; } #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ 12.8 事件置位

注意:事件置位是没有阻塞这个说法的,事件发生了就置位即可。

xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。

事件置位,只需要设置事件字段即可,事件标记字段和事件控制字段只是在等待事件时使用的。

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ) { ListItem_t * pxListItem, * pxNext; ListItem_t const * pxListEnd; List_t const * pxList; EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits; EventGroup_t * pxEventBits = 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 { /* 条件不满足解锁当前任务。事件组状态没有符合当前任务的要求 */ } 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; /* 返回该事件组的当前状态 */ }

事件组的解除任务阻塞处理:vTaskRemoveFromUnorderedEventList()

void vTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem, const TickType_t xItemValue ) { TCB_t * pxUnblockedTCB; /* 该函数在挂起调度器内调用 */ configASSERT( uxSchedulerSuspended != pdFALSE ); /* 把新的任务事件节点值写入该任务 */ listSET_LIST_ITEM_VALUE( pxEventListItem, xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE ); /* 找到节点持有者,即是对应的任务 */ pxUnblockedTCB = listGET_LIST_ITEM_OWNER( pxEventListItem ); /* 参数校验 */ configASSERT( pxUnblockedTCB ); /* 把该节点从事件等待阻塞链表中移除 */ listREMOVE_ITEM( pxEventListItem ); #if ( configUSE_TICKLESS_IDLE != 0 ) { /* 更新一下下次检索延时链表的系统节拍值。 在没有开启低功耗模式下,这里唤醒一个任务后不更新也无妨,最多也就系统心跳服务中多检索一次延时链表, 但是如果开启了低功耗模式后,唤醒一个任务后需要刷新一下,否则提前唤醒退出低功耗模式的多余操作不值得。 */ prvResetNextTaskUnblockTime(); } #endif /* 解除需要解锁的任务的任务状态 */ listREMOVE_ITEM( &( pxUnblockedTCB->xStateListItem ) ); /* 把解锁的任务重新插入到对应的就绪链表中 */ prvAddTaskToReadyList( pxUnblockedTCB ); if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority ) { /* 如果新解锁的任务优先级比当前任务优先级要高,在下次检索切换任务时,需要继续任务切换 */ xYieldPending = pdTRUE; } } 12.9 事件置位中断版

xEventGroupSetBitsFromISR()

#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 ) ) BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t * pxHigherPriorityTaskWoken ) { BaseType_t xReturn; traceEVENT_GROUP_SET_BITS_FROM_ISR( xEventGroup, uxBitsToSet ); xReturn = xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken ); /*lint !e9087 Can't avoid cast to void* as a generic callback function not specific to this use case. Callback casts back to original type so safe. */ return xReturn; } #endif

事件置位业务发送到守护任务中运行,发送的API实现:

#if ( INCLUDE_xTimerPendFunctionCall == 1 ) BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void * pvParameter1, uint32_t ulParameter2, BaseType_t * pxHigherPriorityTaskWoken ) { DaemonTaskMessage_t xMessage; BaseType_t xReturn; /* 把相关数据发送给守护任务执行 */ xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR; xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend; xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1; xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2; xReturn = xQueueSendFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken ); tracePEND_FUNC_CALL_FROM_ISR( xFunctionToPend, pvParameter1, ulParameter2, xReturn ); return xReturn; } #endif

事件置位函数在守护任务的回调:

void vEventGroupSetBitsCallback( void * pvEventGroup, const uint32_t ulBitsToSet ) { ( void ) xEventGroupSetBits( pvEventGroup, ( EventBits_t ) ulBitsToSet ); } 12.10 等待事件

xEventGroupWaitBits()

  • 因为有阻塞机制,所以实现的框架和消息队列接收消息的函数类似,上段检查、获取数据,下段处理阻塞。

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait ) { EventGroup_t * pxEventBits = 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 ); 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大于0的情况下) 把当前任务插入到事件组阻塞等待事件链表中。 */ 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 /* 因为事件组状态满足而解除阻塞的 */ {;} /* 获取事件字段 */ uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES; } traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred ); /* 防止编译警告 */ ( void ) xTimeoutOccurred; /* 返回事件字段 */ return uxReturn; }

事件匹配prvTestWaitCondition()

static BaseType_t prvTestWaitCondition( const EventBits_t uxCurrentEventBits, const EventBits_t uxBitsToWaitFor, const BaseType_t xWaitForAllBits ) { BaseType_t xWaitConditionMet = pdFALSE; if( xWaitForAllBits == pdFALSE ) /* 匹配任意事件 */ { if( ( uxCurrentEventBits & uxBitsToWaitFor ) != ( EventBits_t ) 0 ) { /* 某个事件满足即可 */ xWaitConditionMet = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } else /* 标记事件全匹配 */ { if( ( uxCurrentEventBits & uxBitsToWaitFor ) == uxBitsToWaitFor ) { /* 全匹配才生效 */ xWaitConditionMet = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } /* 返回匹配结果 */ return xWaitConditionMet; } 12.11 清除事件

xEventGroupClearBits()xEventGroupClearBitsFromISR()都是用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除。

xEventGroupClearBitsFromISR()这个中断专用API也是和事件置位一样,都是发通知给守护任务执行置位业务。

这里主要分析xEventGroupClearBits()

EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear ) { EventGroup_t * pxEventBits = xEventGroup; EventBits_t uxReturn; /* 参数校验 */ configASSERT( xEventGroup ); /* 事件字段检查 */ configASSERT( ( uxBitsToClear & eventEVENT_BITS_CONTROL_BYTES ) == 0 ); taskENTER_CRITICAL(); /* 进入临界 */ { traceEVENT_GROUP_CLEAR_BITS( xEventGroup, uxBitsToClear ); /* 返回的是清除事件前的事件组状态 */ uxReturn = pxEventBits->uxEventBits; /* 清除目标事件 */ pxEventBits->uxEventBits &= ~uxBitsToClear; } taskEXIT_CRITICAL(); /* 退出临界 */ return uxReturn; } 12.12 删除事件

vEventGroupDelete()

void vEventGroupDelete( EventGroupHandle_t xEventGroup ) { EventGroup_t * pxEventBits = xEventGroup; const List_t * pxTasksWaitingForBits; /* 参数校验 */ configASSERT( pxEventBits ); /* 获取等待事件阻塞链表 */ pxTasksWaitingForBits = &( pxEventBits->xTasksWaitingForBits ); vTaskSuspendAll(); /* 挂起调度器 */ { traceEVENT_GROUP_DELETE( xEventGroup ); while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 ) /* 遍历等待事件阻塞链表 */ { /* 相当于参数校验 */ configASSERT( pxTasksWaitingForBits->xListEnd.pxNext != ( const ListItem_t * ) &( pxTasksWaitingForBits->xListEnd ) ); /* 解除这些阻塞的任务,解锁理由是事件原因,并把事件无效(事件bit全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(); }

本文共计5265个文字,预计阅读时间需要22分钟。

Freertos中事件标志的具体实现和概念是怎样的?

目录+前言+12.1+实现事件机制的预备知识+12.1.1+守护任务+12.1.2+事件的不确定性+12.1.3+事件组的报告+12.2+事件概念+12.3+事件用途参考+12.4+事件实现原理简述+12.5+事件实现所需支持问题+1

目录
  • 前言
  • 12.1 实现事件机制的预备知识
    • 12.1.1 守护任务
    • 12.1.2 事件的不确定性
    • 12.1.3 事件组的报文
  • 12.2 事件概念
  • 12.3 事件用途参考
  • 12.4 事件实现原理简述
  • 12.5 事件实现需要克服的问题
    • 12.5.1 避免在用户的应用程序中创建竞争条件
    • 12.5.2 避免不确定性
  • 12.6 事件控制块
  • 12.7 创建事件
  • 12.8 事件置位
  • 12.9 事件置位中断版
  • 12.10 等待事件
  • 12.11 清除事件
  • 12.12 删除事件


前言

默认以32bit事件类型和任务专用API讲解。

事件独立于消息队列、信号量和互斥量这些章节是因为内部实现机制不同。

参考:李柱明博客:www.cnblogs.com/lizhuming/p/16353453.html

Freertos中事件标志的具体实现和概念是怎样的?

12.1 实现事件机制的预备知识 12.1.1 守护任务

和守护进程一样理解即可。

守护任务(Daemon)又称为精灵任务,是运行在后台的一种特殊任务,周期性地执行某种任务或等待处理某些事情的发生,主要表现为以下两个特点:

  • 长期运行。守护任务是一种生存期很长的任务,一般在系统启动就开始运行,到系统退出或调用接口强制停止而结束。
  • 后台运行。用户一般不能直接接触控制该任务。用户的任务也不会影响守护任务的生存。

比如freertos的软件定时器服务任务。

12.1.2 事件的不确定性

先明白freertos不允许在中断或临界中操作不确定的业务。

而事件有个不确定的业务是因为事件的一对多特性,如当发生事件置位时,会遍历阻塞在这个事件组的链表,而阻塞在这个链表任务是不确定的。

所以任务专用的API事件置位时,不是在临界,而是在调度锁内完成的。

而中断专用的API事件置位,整个上下文都不符合要求,所以中断专用的API的事件置位实现是通过给FreeRTOS的守护任务发送一个消息,让置位事件组的操作在守护任务(软件定时器服务任务)里面完成,守护任务是基于调度锁而非临界段的机制来实现的。

12.1.3 事件组的报文

事件组报文就一个系统位长的变量。

最高位bit表示该值表示事件组有效。这个bit影响到整个系统的事件阻塞、优先级继承等等任务事件节点相关的业务的实现。

最高字节的[6:0]bit是该事件组的控制信息。

剩余bit表示各个事件。

12.2 事件概念

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

和信号量又是不同的,事件可以一对多,多对多同步。

事件(bit):

  • 0:事件没有发生。需要该事件的任务阻塞或直接返回失败。
  • 1:事件发生。需要该事件的任务解除阻塞返回成功或者直接返回成功。

事件组:多个事件组合在一起,用户可以选择等待某个事件或者等待所有事件实现同步。

12.3 事件用途参考

事件位用于指示事件是否发生。事件位通常被称为事件标志。例如,申请可以:

  • 定义一个位(或标志),当它设置为1时,表示“消息已接收并准备处理”,当它设置为0时,表示“没有消息等待处理”。
  • 定义一个位(或标志),当它设置为1时,表示“应用程序已将准备发送到网络的消息排队”,而当它设置为0时,表示“没有准备发送到网络的消息排队”。
  • 定义一个位(或标志),当它设置为1时,表示“是时候向网络发送一个心跳消息了”,而当它设置为0时,表示“还没有到发送另一个心跳消息的时候了”。

事件组是事件位的集合。事件组中的个别事件位由位号引用。展开上面提供的示例:

  • 表示“消息已接收并准备处理”的事件位可能是事件组中的位0。
  • 在同一个事件组中,表示“应用程序已将准备发送到网络的消息排队”的事件位可能是第1位。
  • 表示“是时候向网络发送心跳消息了”的事件位可能位于同一事件组中的第2位。
12.4 事件实现原理简述

核心原理就是一个全局变量+访问机制+阻塞机制。

这些组件都用控制块数据结构管理起来。

封装一些API访问即可。

事件组由EventGroupHandle_t类型的变量引用。

如果configUSE_16_BIT_TICKS设置为1,则事件组中存储的比特数(或标志数)为8,如果configUSE_16_BIT_TICKS设置为0,则为24。

configUSE_16_BIT_TICKS的依赖源于任务内部实现中用于线程本地存储的数据类型。

事件组中的所有事件位都存储在EventBits_t类型的单个无符号变量中。

事件位0存储在位0中,事件位1存储在位1中,依此类推。

如图是一个24位事件组,它使用3位来保存前面描述的3个示例事件。在图像中,只设置了事件位2。

12.5 事件实现需要克服的问题

在实现事件组时,RTOS必须克服的两个主要挑战是:应用程序竞态混合运行不确定性。

12.5.1 避免在用户的应用程序中创建竞争条件

如果出现以下情况,事件组实现将在应用程序中产生竞争条件:

  • 不清楚谁负责清除单个事件。
  • 不清楚何时要清除位。
  • 不清楚在任务退出测试位值的 API 函数时是否设置或清除了事件(可能是另一个任务或中断已更改该位的状态)。

这样对全局资源的这个事件组来说,应用层的调用很模糊,所以为了解决这些问题,避免应用程序的竞态产生,实现事件机制时可以用一下方法解决:

  • FreeRTOS 事件组实现通过包含内置智能来确保位的设置、测试和清除看起来是原子的,在处理了所有对该事件感兴趣的任务后再统一对这个事件bit做更新,从而消除了竞争条件的可能性。

    • 但是这样就会出现不确定性。第二个问题就是解决不确定性。
  • 线程本地存储(任务事件节点值存储当前任务对该事件组的信息)和 API 函数返回值的谨慎使用。

12.5.2 避免不确定性

事件组概念意味着不确定性行为,因为它不知道在一个事件组上有多少任务被阻塞,因此当事件位被设置时,不知道有多少条件需要被测试或多少任务需要被解除阻塞。

FreeRTOS 质量标准不允许在中断被禁用时或在中断服务程序中执行不确定的动作。为了确保在设置事件位时不违反这些严格的质量标准:

  • 调度锁用于确保在 RTOS 任务设置事件位时中断保持启用状态。

    • 即是原子性不使用临界,而是调度锁级别。
  • 集中延迟中断机制用于在尝试从中断服务程序设置事件位时,将设置位的动作推迟到任务。

    • 即是把中断上下文对事件的操作转包给守护任务实现,这样就维护了调度锁级别的原子性。
12.6 事件控制块

从事件控制块看就知道事件使用了非常少的RAM实现。

typedef struct EventGroupDef_t { EventBits_t uxEventBits; /* 事件组 */ List_t xTasksWaitingForBits; /* 等待事件阻塞任务链表 */ #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxEventGroupNumber; /* 事件number */ #endif #if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) uint8_t ucStaticallyAllocated; /* 标记是否为静态分配 */ #endif } EventGroup_t;

EventBits_t uxEventBits

  • 最高bit:表示当前值为事件组件使用。主要用于在任务事件节点值中区分任务优先级。
  • 最高字节的[6:0]bit:表示事件组控制信息。
  • 剩下的bit:表示各个事件。

事件控制信息:

/* The following bit fields convey control information in a task's event list item value. It is important they don't clash with the taskEVENT_LIST_ITEM_VALUE_IN_USE definition. */ #if configUSE_16_BIT_TICKS == 1 #define eventCLEAR_EVENTS_ON_EXIT_BIT 0x0100U /* 事后清空事件 */ #define eventUNBLOCKED_DUE_TO_BIT_SET 0x0200U /* 事后唤醒 */ #define eventWAIT_FOR_ALL_BITS 0x0400U /* 等待所有事件 */ #define eventEVENT_BITS_CONTROL_BYTES 0xff00U /* 事件控制字段所有bit */ #else #define eventCLEAR_EVENTS_ON_EXIT_BIT 0x01000000UL /* 事后清空事件 */ #define eventUNBLOCKED_DUE_TO_BIT_SET 0x02000000UL /* 发生事件而解除阻塞的标记 */ #define eventWAIT_FOR_ALL_BITS 0x04000000UL /* 等待所有事件 */ #define eventEVENT_BITS_CONTROL_BYTES 0xff000000UL /* 事件控制字段所有bit */ #endif

  • eventCLEAR_EVENTS_ON_EXIT_BIT:该标记用于等待事件时配置,表示获得事件触发任务解锁后,要清除这些事件。
  • eventUNBLOCKED_DUE_TO_BIT_SET:用于区分等待事件而阻塞的任务被唤醒的原因(事件或超时),该标记表示因为事件而触发唤醒。
  • eventWAIT_FOR_ALL_BITS:用于等待事件时配置,表示该任务等待标记的所有事件都发生时才有效。否则就是任意事件有效。
  • eventEVENT_BITS_CONTROL_BYTES:用于区分事件控制字段和事件字段。
12.7 创建事件

创建事件使用API xEventGroupCreate()

#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) EventGroupHandle_t xEventGroupCreate( void ) { EventGroup_t * pxEventBits; /* 申请事件组资源 */ pxEventBits = ( EventGroup_t * ) pvPortMalloc( sizeof( EventGroup_t ) ); 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 pxEventBits; } #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ 12.8 事件置位

注意:事件置位是没有阻塞这个说法的,事件发生了就置位即可。

xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。

事件置位,只需要设置事件字段即可,事件标记字段和事件控制字段只是在等待事件时使用的。

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ) { ListItem_t * pxListItem, * pxNext; ListItem_t const * pxListEnd; List_t const * pxList; EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits; EventGroup_t * pxEventBits = 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 { /* 条件不满足解锁当前任务。事件组状态没有符合当前任务的要求 */ } 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; /* 返回该事件组的当前状态 */ }

事件组的解除任务阻塞处理:vTaskRemoveFromUnorderedEventList()

void vTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem, const TickType_t xItemValue ) { TCB_t * pxUnblockedTCB; /* 该函数在挂起调度器内调用 */ configASSERT( uxSchedulerSuspended != pdFALSE ); /* 把新的任务事件节点值写入该任务 */ listSET_LIST_ITEM_VALUE( pxEventListItem, xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE ); /* 找到节点持有者,即是对应的任务 */ pxUnblockedTCB = listGET_LIST_ITEM_OWNER( pxEventListItem ); /* 参数校验 */ configASSERT( pxUnblockedTCB ); /* 把该节点从事件等待阻塞链表中移除 */ listREMOVE_ITEM( pxEventListItem ); #if ( configUSE_TICKLESS_IDLE != 0 ) { /* 更新一下下次检索延时链表的系统节拍值。 在没有开启低功耗模式下,这里唤醒一个任务后不更新也无妨,最多也就系统心跳服务中多检索一次延时链表, 但是如果开启了低功耗模式后,唤醒一个任务后需要刷新一下,否则提前唤醒退出低功耗模式的多余操作不值得。 */ prvResetNextTaskUnblockTime(); } #endif /* 解除需要解锁的任务的任务状态 */ listREMOVE_ITEM( &( pxUnblockedTCB->xStateListItem ) ); /* 把解锁的任务重新插入到对应的就绪链表中 */ prvAddTaskToReadyList( pxUnblockedTCB ); if( pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority ) { /* 如果新解锁的任务优先级比当前任务优先级要高,在下次检索切换任务时,需要继续任务切换 */ xYieldPending = pdTRUE; } } 12.9 事件置位中断版

xEventGroupSetBitsFromISR()

#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 ) ) BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t * pxHigherPriorityTaskWoken ) { BaseType_t xReturn; traceEVENT_GROUP_SET_BITS_FROM_ISR( xEventGroup, uxBitsToSet ); xReturn = xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken ); /*lint !e9087 Can't avoid cast to void* as a generic callback function not specific to this use case. Callback casts back to original type so safe. */ return xReturn; } #endif

事件置位业务发送到守护任务中运行,发送的API实现:

#if ( INCLUDE_xTimerPendFunctionCall == 1 ) BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, void * pvParameter1, uint32_t ulParameter2, BaseType_t * pxHigherPriorityTaskWoken ) { DaemonTaskMessage_t xMessage; BaseType_t xReturn; /* 把相关数据发送给守护任务执行 */ xMessage.xMessageID = tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR; xMessage.u.xCallbackParameters.pxCallbackFunction = xFunctionToPend; xMessage.u.xCallbackParameters.pvParameter1 = pvParameter1; xMessage.u.xCallbackParameters.ulParameter2 = ulParameter2; xReturn = xQueueSendFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken ); tracePEND_FUNC_CALL_FROM_ISR( xFunctionToPend, pvParameter1, ulParameter2, xReturn ); return xReturn; } #endif

事件置位函数在守护任务的回调:

void vEventGroupSetBitsCallback( void * pvEventGroup, const uint32_t ulBitsToSet ) { ( void ) xEventGroupSetBits( pvEventGroup, ( EventBits_t ) ulBitsToSet ); } 12.10 等待事件

xEventGroupWaitBits()

  • 因为有阻塞机制,所以实现的框架和消息队列接收消息的函数类似,上段检查、获取数据,下段处理阻塞。

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait ) { EventGroup_t * pxEventBits = 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 ); 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大于0的情况下) 把当前任务插入到事件组阻塞等待事件链表中。 */ 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 /* 因为事件组状态满足而解除阻塞的 */ {;} /* 获取事件字段 */ uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES; } traceEVENT_GROUP_WAIT_BITS_END( xEventGroup, uxBitsToWaitFor, xTimeoutOccurred ); /* 防止编译警告 */ ( void ) xTimeoutOccurred; /* 返回事件字段 */ return uxReturn; }

事件匹配prvTestWaitCondition()

static BaseType_t prvTestWaitCondition( const EventBits_t uxCurrentEventBits, const EventBits_t uxBitsToWaitFor, const BaseType_t xWaitForAllBits ) { BaseType_t xWaitConditionMet = pdFALSE; if( xWaitForAllBits == pdFALSE ) /* 匹配任意事件 */ { if( ( uxCurrentEventBits & uxBitsToWaitFor ) != ( EventBits_t ) 0 ) { /* 某个事件满足即可 */ xWaitConditionMet = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } else /* 标记事件全匹配 */ { if( ( uxCurrentEventBits & uxBitsToWaitFor ) == uxBitsToWaitFor ) { /* 全匹配才生效 */ xWaitConditionMet = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } } /* 返回匹配结果 */ return xWaitConditionMet; } 12.11 清除事件

xEventGroupClearBits()xEventGroupClearBitsFromISR()都是用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除。

xEventGroupClearBitsFromISR()这个中断专用API也是和事件置位一样,都是发通知给守护任务执行置位业务。

这里主要分析xEventGroupClearBits()

EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear ) { EventGroup_t * pxEventBits = xEventGroup; EventBits_t uxReturn; /* 参数校验 */ configASSERT( xEventGroup ); /* 事件字段检查 */ configASSERT( ( uxBitsToClear & eventEVENT_BITS_CONTROL_BYTES ) == 0 ); taskENTER_CRITICAL(); /* 进入临界 */ { traceEVENT_GROUP_CLEAR_BITS( xEventGroup, uxBitsToClear ); /* 返回的是清除事件前的事件组状态 */ uxReturn = pxEventBits->uxEventBits; /* 清除目标事件 */ pxEventBits->uxEventBits &= ~uxBitsToClear; } taskEXIT_CRITICAL(); /* 退出临界 */ return uxReturn; } 12.12 删除事件

vEventGroupDelete()

void vEventGroupDelete( EventGroupHandle_t xEventGroup ) { EventGroup_t * pxEventBits = xEventGroup; const List_t * pxTasksWaitingForBits; /* 参数校验 */ configASSERT( pxEventBits ); /* 获取等待事件阻塞链表 */ pxTasksWaitingForBits = &( pxEventBits->xTasksWaitingForBits ); vTaskSuspendAll(); /* 挂起调度器 */ { traceEVENT_GROUP_DELETE( xEventGroup ); while( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > ( UBaseType_t ) 0 ) /* 遍历等待事件阻塞链表 */ { /* 相当于参数校验 */ configASSERT( pxTasksWaitingForBits->xListEnd.pxNext != ( const ListItem_t * ) &( pxTasksWaitingForBits->xListEnd ) ); /* 解除这些阻塞的任务,解锁理由是事件原因,并把事件无效(事件bit全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(); }