事件组(Event Groups)是一种用于任务间通信的机制,主要用于多任务之间的同步。事件组允许任务通过设置和清除标志位来彼此协作,完成类似于“事件通知”的功能。事件组适合用于当多个任务需要等待某些条件或事件时,它们之间进行同步。
事件位(事件标志)
事件位用于指示事件 是否发生。事件位通常称为事件标志。例如,应用程序可以:
- 定义一个位(或标志),设置为 1 时表示“已收到消息并准备好处理”, 设置为 0 时表示“没有消息等待处理”。
- 定义一个位(或标志),设置为 1 时表示“应用程序已将准备发送到网络的消息排队”, 设置为 0 时表示 “没有消息需要排队准备发送到网络”。
- 定义一个位(或标志),设置为 1 时表示“需要向网络发送心跳消息”, 设置为 0 时表示“不需要向网络发送心跳消息”。
事件组
事件组就是一组事件位。事件组中的事件位通过位编号来引用。同样,以上面列出的三个例子为例:
- 事件组位 0 号位表示“已收到消息并准备好处理”。
- 事件组位 1 号位表示“应用程序已将准备发送到网络的消息排队”。
- 事件组位 2 号位表示“需要向网络发送心跳消息”。
事件组和事件位数据类型
事件组由 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 个事件,在图片中仅设置了事件位 2
事件组相关函数
提供的事件组 API 函数允许任务在事件组中设置一个或多个事件位, 清除事件组中的一个或多个事件位,并挂起(进入阻塞状态, 因此任务不会消耗任何处理时间)以等待事件组中一个或多个事件位固定下来。
事件组也可用于同步任务,创建通常称为“集合”的任务。任务同步点是应用程序代码中的一个位置,在该位置任务将在阻塞状态(不消耗任何 CPU 时间)下等待,直到参与同步的所有其他任务也到达其同步点。
实现事件组时 RTOS 必须克服的挑战
在实现事件组时,RTOS 必须克服以下两项主要挑战:
避免在用户的应用程序中创建争用条件:
如果出现以下情况,事件组实现将在应用程序中创建争用条件:- 不清楚各个位(或标志)由谁清除。
- 不清楚何时清除位。
- 不清楚在任务退出测试位值的 API 函数时是否设置或清除了位(可能是 另一个任务或中断已更改该位的状态)。
FreeRTOS 事件组的实现通过内置智能来确保位的设置、测试和清除具有原子性, 从而消除了争用条件的可能性。线程本地存储和谨慎使用 API 函数返回值使之成为可能。
避免不确定性:
事件组概念意味着非确定性行为,因为 不知道在一个事件组上有多少任务被阻塞,因此 不清楚在设置事件位时 需要测试多少条件或解除多少阻塞任务。
根据 FreeRTOS 质量标准,无法在禁用中断时 或在中断服务程序中 执行非确定性操作。为确保在设置事件位时 不违反这些严格的质量标准,需要以下两点:
- RTOS 调度器的锁定机制用于确保 在 RTOS 任务中设置事件位时中断仍处于启用状态。
- 尝试在中断服务程序中设置事件位时, 集中延迟中断机制用于 将设置位的动作延迟到任务。
API
xEventGroupCreate
创建一个新的 RTOS 事件组,并返回可以引用新创建的事件组的句柄。
1 | // event_groups.h |
要使此 RTOS API 函数可用:
- configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或保留未定义状态(此时 默认为 1)。
- 必须将 RTOS 源文件 FreeRTOS/source/event_groups.c 包含在构建中。
每个事件组都需要(非常)少量的 RAM 来保存 事件组的状态。如果使用 xEventGroupCreate() 创建事件组, 则所需的 RAM 将从 FreeRTOS 堆自动分配。 如果使用 xEventGroupCreateStatic() 创建事件组 则 RAM 由应用程序编写器提供,这需要用到一个附加参数, 但允许在编译时静态分配 RAM 。
事件组存储在 EventBits_t 类型的变量中。如果 configUSE_16_BIT_TICKS 设置为 1,则事件组内实现的位数(或标志数)为 8; 如果 configUSE_16_BIT_TICKS 设置为 0,则事件组内实现的位数(或标志数)为 24。 对 configUSE_16_BIT_TICKS 的依赖 取决于 RTOS 任务内部实现中用于线程本地存储的数据类型。
参数:
无
返回:
- 如果创建了事件组,则返回事件组的句柄。
- 如果没有足够的 FreeRTOS 堆来创建事件组,则返回 NULL。
1 | EventGroupHandle_t xCreatedEventGroup; |
xEventGroupSetBits
在 RTOS 事件组中设置位(标志)。该函数不能从中断中调用。xEventGroupSetBitsFromISR() 是可从中断调用的版本。
1 | EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ); |
在事件组中设置位时,所有等待该位的任务将自动解除阻塞。
必须将 RTOS 源文件 FreeRTOS/source/event_groups.c 包含在构建中,xEventGroupSetBits() 函数才可用。
参数:
xEventGroup
要设置位的事件组。必须事先通过调用 xEventGroupCreate() 创建事件组。
uxBitsToSet
指定要在事件组中设置的一个或多个位的按位值。例如,将 uxBitsToSet 设置为 0x08,则只设置第 3 位。将 uxBitsToSet 设置为 0x09,即可设置第 3 位和第 0 位。
返回:
返回的是事件组当前的值,即在调用该函数之后,事件组中所有位的最新状态。
调用 xEventGroupSetBits() 后,返回的事件组值可能会受到以下两种情况的影响:
- 如果设置某个事件位使得等待该位的任务从阻塞状态中解除,该位可能会被自动清除(具体取决于 xClearBitOnExit 参数的设置,参考 xEventGroupWaitBits() 函数)。
- 如果在调用 xEventGroupSetBits() 之后,有优先级更高的任务变为就绪状态并执行,它们可能会在返回之前修改事件组的值。
示例
1 | EventBits_t uxBits; |
xEventGroupWaitBits
读取 RTOS 事件组中的位,选择性地进入“阻塞”状态(已设置 超时值)以等待设置单个位或一组位。
1 | // event_groups.h |
无法从中断调用此函数。
必须将 RTOS 源文件 FreeRTOS/source/event_groups.c 包含在构建中,xEventGroupWaitBits() 函数才可用。
参数:
xEventGroup
事件组的句柄,表示你想要操作的事件组。必须事先通过调用 xEventGroupCreate() 创建事件组。
uxBitsToWaitFor
指定你希望等待的位。它是一个位掩码,每一位对应一个特定的事件标志。
例如,要等待 第 0 位和/或第 2 位,将 uxBitsToWaitFor 设置为 0x05 即可。要等待第 0 位和/或第 1 位和/或第 2 位, 将 uxBitsToWaitFor 设置为 0x07 即可。uxBitsToWaitFor 不得 设置为 0。
xClearOnExit
如果 xClearOnExit 设置为 pdTRUE,那么在发生以下情况之前,作为 uxBitsToWaitFor 参数传递的值中设置的任何位都将在 xEventGroupWaitBits() 返回之前在事件组中清除;xEventGroupWaitBits() 因超时以外的任何原因返回。超时值由 xTicksToWait 参数设置。(只要不是超时返回都将清除标志位)
如果 xClearOnExit 设置为 pdFALSE,那么当调用 xEventGroupWaitBits() 返回时,事件组中设置的位不会改变。
xWaitForAllBits
xWaitForAllBits 用于创建逻辑 AND(必须设置所有位) 或逻辑 OR(必须设置一个或多个位):
如果 xWaitForAllBits 设置为 pdTRUE(逻辑 &&),那么 xEventGroupWaitBits() 在以下条件下将返回:
- 作为 uxBitsToWaitFor 参数传递的值中的所有位在事件组中被设置(置 1)或指定的阻塞时间到期。
如果 xWaitForAllBits 设置为 pdFALSE(逻辑 ||),那么 xEventGroupWaitBits() 在以下条件下将返回:
- 作为 uxBitsToWaitFor 参数传递的值中的任何位在事件组中被设置(置 1)或指定的阻塞时间到期。
xTicksToWait
等待以下情况发生的最长时间(以“滴答”为单位,取决于 xWaitForAllBits 值):
- uxBitsToWaitFor 指定的一个/所有位被设置。
返回:
事件位等待完成设置或阻塞时间过期时的事件组值。在以下情况下,事件组中事件位的当前值将与返回值不同: 高优先级任务或中断在调用任务解除“阻塞”状态和退出 xEventGroupWaitBits() 函数之间更改了事件位的值 。
测试返回值以确定哪些位已完成设置。如果 xEventGroupWaitBits() 因为超时过期而返回, 则并非在等待的所有位都会进行设置。如果 xEventGroupWaitBits() 返回因为其等待的位被设置而返回, 则返回值是由于任何位因为 xClearOnExit 参数被设置为 pdTRUE
而自动清除之前的事件组值。
示例
1 |
|
xEventGroupSync
以原子方式设置 RTOS 事件组中的位(标志),然后等待在同一事件组中设置位的组合。此功能通常用于同步多个任务(通常称为任务集合),其中每个 任务必须等待其他任务到达同步点后才能继续。
1 | event_groups.h |
不能从中断使用此函数。
如果设置了 uxBitsToWait 参数指定的位,或者在该时间内设置了这些位,则该函数将在其阻塞时间到期之前返回。这种情况下,uxBitsToWait 指定的所有位将在 函数返回之前自动清除。
必须将 RTOS 源文件 FreeRTOS/source/event_groups.c 包含在构建中,xEventGroupSync() 函数才可用。
参数:
xEventGroup
事件组的句柄,表示你想要操作的事件组。必须事先通过调用 xEventGroupCreate() 创建事件组。
uxBitsToSet
在确定 uxBitsToWait 参数指定的所有位是否都已设置(可能还要等待)之前,要在事件组中设置的一个或多个位。
例如,将 uxBitsToSet 设置为 0x04, 即可设置事件组内的第 2 位。
uxBitsToWaitFor
表示任务希望等待哪些位被设置为 1。只有当这些位中的所有位都被设置时,任务才会解除阻塞并继续执行。
例如,将 uxBitsToWaitFor 设置为 0x05,即可等待第 0 位和第 2 位。将 uxBitsToWaitFor 设置为 0x07,即可等待第 0 位、第 1 位和第 2 位等。
xTicksToWait
等待 uxBitsToWaitFor 参数值指定的所有位被设置的最长时间(以滴答为单位) 。
返回:
等待置位时或阻塞到期时事件组的值。测表示事件组中所有位的当前状态。
如果 xEventGroupSync() 因为超时过期而返回, 则并非在等待的所有位都会进行设置(有部分状态位没有被设置)。它会返回事件组的当前状态。
如果 xEventGroupSync() 因其所等待的所有位都被设置而返回, 那么返回值是自动清除任何位之前的事件组值。
示例
1 | // 设置 0 标志位,等待 ALL_SYNC_BITS 对应的标志位被置位 |
实现
按键被按下时,通过事件组触发三个任务依次执行,打印不同的消息。
1 |
|