前言

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

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

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

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

【freertos】012-事件标志概念和实现细节的更多相关文章

  1. FreeRTOS 事件标志组

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 为什么要使用事件标志事件标志组是实现多任务同步的有效机制之一.也许有不理解的初学者会问采用事件标志组多麻烦, ...

  2. FreeRTOS 任务计数信号量,任务二值信号量,任务事件标志组,任务消息邮箱

    以下基础内容转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 计数信号量的另一种实现方式----基于任务通知(Task Not ...

  3. FreeRTOS 事件标志组 ——提高篇

    假设你已经看过FreeRTOS 事件标志组这篇随笔了. 之前的基础篇,真的就只是简单了解一下,相当于大学实验室的实验,但是,我们实际公司项目中,需要更多地思考,就算我们之前只是学习了基础概念以及基础语 ...

  4. FreeRTOS 任务通知模拟事件标志组

    实验 //设置事件位的任务 void eventsetbit_task(void *pvParameters) { u8 key; while(1) { if(EventGroupTask_Handl ...

  5. 【freertos】013-任务通知及其实现细节

    前言 参考: https://www.freertos.org/RTOS-task-notifications.html 原文:https://www.cnblogs.com/lizhuming/p/ ...

  6. FreeRTOS_事件标志组

    FreeRTOS事件标志组 事件标志组简介 1. 事件位(事件标志) 事件位用于表明某个事件是否发生,事件位通常用作事件标志,比如下面的几个例子: 当收到一条消息并且把这条消息处理掉以后就可以将某个位 ...

  7. 【uTenux实验】事件标志

    事件标志是一个用来实现同步的对象,由多个位组成,用作指示对应事件存在的标志.事件标志由用来指示对应事件存在的位模式(bitpattern)和一个等待事件标志的任务队列组成. uTenux提供了一组AP ...

  8. c/c++ 多线程 等待一次性事件 future概念

    多线程 等待一次性事件 future概念 背景:有时候,一个线程只等待另一个线程一次,而且需要它等待的线程的返回值. 案例:滴滴叫车时,点完了叫车按钮后,叫车的后台线程就启动了,去通知周围的出租车.这 ...

  9. ucos中信号量 事件标志 消息队列都怎么用

    信号量 事件标志和消息队列分别应用于什么场景(反正我学的时候有点闹不清,现在总结一下): 信号量和事件标志用于任务同步.详细来说,这个功能可以替代以前裸机中你打一个标记的功能,比如使用了一个定时器,5 ...

随机推荐

  1. ES7中前端异步特性:async、await。

    在最新的ES7(ES2017)中提出的前端异步特性:async.await. 什么是async.await? async顾名思义是"异步"的意思,async用于声明一个函数是异步的 ...

  2. EMS监控用户邮箱

    案例任务:部署日记规则,用户"王淑江"监控用户"王春海"的邮箱. 1.EMS监控用户邮箱 使用PowerShell命令完成任务:使用日记规则部署"王淑 ...

  3. Z-blog csrf漏洞学习

    Z-blog csrf 环境搭建 1. 首先我在本地搭了一个z-blog. ​ 思路:csrf并不侧重于哪种功能点,只要检测不规范,就可能利用成功,所以我考虑了一下后台添加管理员的地方. 数据包构造 ...

  4. Mybatis-sql语句的抽取

    1.抽取之前的UserMapper.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...

  5. 序列化器中钩子函数源码分析、many关键字源码分析

    局部钩子和全局钩子源码分析(2星) # 入口是 ser.is_valid(),是BaseSerializer的方法 # 最核心的代码 self._validated_data = self.run_v ...

  6. 初识gradle, idea+springboot Demo

    写在前面; 使用maven管理写过几个springboot的系统, 此篇博客纯属记录整理学习的过程. 另外, 源码分享地址在最后. Java: 1.8.0_281 tomcat: 1.8 IDE: I ...

  7. eclipse-java-2018-09-win32-x86_64配置tomcat(内含更新eclipse,如何解决添加时找不到最新tomcat版本)

    我下的是eclipse精简版,建议下载企业版,可以省略后面的很多步骤(其中的辛酸...) 这里就是说明下载精简版的eclipse如何配置tomcat的步骤,其实还是更新eclipse的步骤 1.首先点 ...

  8. VS Code失焦时自动保存编辑器内容

    vs code有一个非常好用的功能:就是自动保存. 而且不需要安装什么插件,只需要在编辑器设置就可以了.接下来我们一起来设置吧: 1.打开我们的vs code编辑器.在左下角有个  齿轮图标(管理), ...

  9. HTTP:聊一聊HTTP中的强制缓存

    http响应response headers中会有一个cache-control,这个参数就是用来做强制缓存的 一.什么是强制缓存 强制缓存就是服务端告诉客户端某些资源(JS CSS IMG)需要按照 ...

  10. PostgreSQL培训认证讲师招募

    中国PostgreSQL考试认证中心 以"知"之名,携手共进!不蒂于知识共享,技术传承,更愿为讲师耕织嫁衣,助您成为PostgreSQL圈的意见领袖,用知识改变世界! 一.入选讲师 ...