FreeRTOS学习-事件组(Event Groups)

1. 简介

事件组是FreeRTOS提供的另一种同步机制。它与队列和信号量的区别是:

  • 允许任务进入blocked状态,等待一个或者多个事件;
  • 事件组会唤醒所有等待这个事件或事件组的任务。

所以,事件组通常用于多个任务(中断)之间的同步。

1.1. 事件组(Event Groups),事件标志(Event Flags)和事件位图(Event Bits)

事件标志:是一个布尔值,用于指示一个事件是否发生;因此可以用一个位来表示。

事件组:是一组事件标志。

事件位图:是事件组的一种实现方式,将事件组中的所有事件标志用一张位图来表示。如果某个位为1,则表明对应的事件已发生。

2. 创建事件组

函数原型:

EventGroupHandle_t xEventGroupCreate( void ) PRIVILEGED_FUNCION;

创建时,FreeRTOS为事件组创建一小片内存,用于存储事件组的元信息。如果创建成功则返回事件组的句柄,否则返回NULL

下面来看看其具体实现:
创建事件组活动图

创建事件组的过程比较简单,在申请到所需的内存后,只是初始化事件组的事件位图以及等待任务队列。

3. 事件组的使用

3.1. 等待与查询事件

等待事件的函数原型:

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

可以阻塞地等待事件组xEventGroup中的一个或多个事件的发生。不能在中断处理函数中调用。

其中,uxBitsToWaitFor指示了调用者希望等待的事件组,用事件位图表示。

xClearOnExit如果设置为pdTRUE,那么在这个函数正常返回的时候(非超时),就会清除掉系统内部与uxBitsToWaitFor对应的事件位图;否则不会。

xWaitForAllBits如果设置为pdTRUE,表示只有在uxBitsToWaitFor事件位图中的所有事件发生时,才会唤醒任务。

xTicksToWait表示等待的时限。

如果是超时返回,那么返回的事件位图表示超时时刻已经发生的事件。因此,调用者一定要确认返回值(除非是不限期的等待)。

该函数的实现涉及到三个比较重要的行为:

  1. 检查等待条件是否已经满足,所以在介绍具体的实现之前,先来看看该检查的流程(static BaseType_t prvTestWaitCondition( const EventBits_t uxCurrentEventBits, const EventBits_t uxBitsToWaitFor, const BaseType_t xWaitForAllBits )):

    {
        初始化`xWaitConditionMet = pdFALSE`。
    
        如果是等待事件位图中某个事件的情况(`xWaitForAllBits == pdFALSE`):
        {
            如果事件位图满足条件(`( uxCurrentEventBits & uxBitsToWaitFor ) != 0`),则设置条件满足标志(`xWaitConditionMet = pdTRUE`)。
        }
        否则,即等待事件位图中所有事件:
        {
            如果事件位图满足条件(`( uxCurrentEventBits & uxBitsToWaitFor ) == uxBitsToWaitFor`),则设置条件满足标志(`xWaitConditionMet = pdFALSE`)。
        }
    
        返回`xWaitConditionMet`。
    }
    
  2. 将任务放置到无序事件队列(void vTaskPlaceOnUnorderedEventList( List_t * pxEventList, const TickType_t xItemValue, const TickType_t xTicksToWait )):

    {
        参数检查:Assert `pxEventList != NULL`。
    
        关键条件检查:Assert 调度器必须被挂起(`uxSchedulerSuspended != 0`)。
    
        设置任务队列的事件队列项值为等待的事件位图,并且设置占用标志(`listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xEventListItem ), xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE ) `)。
    
        将任务插入到事件队列的尾部(`vListInsertEnd( pxEventList, &( pxCurrentTCB->xEventListItem ) )`)。
    
        同时将任务加入到Delayed任务队列(`prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE )`)。
    }
    
  3. 从任务事件队列中获取当前的事件位图(TickType_t uxTaskResetEventItemValue( void )):

    {
        获取当前任务的事件位图(uxReturn = listGET_LIST_ITEM_VALUE( &( pxCurrentTCB->xEventListItem ) ));
    
        将当前任务的事件队列项的值恢复到默认, 以便于基于队列的任务同步机制使用(listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xEventListItem ), ( ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority ) ));
    }
    

下面来看看其具体的实现:

等待事件位图的活动图

可以看到,事件组的实现和队列的实现不一样。队列在使用事件任务队列时,将任务按照优先级顺序排列,以便于唤醒优先级最高的队列;而事件组则不需要,因为它可能一次性唤醒所有任务,并且每个任务等待的事件位图不一致。

如果不希望阻塞地等事件,则可以通过以下接口获取事件:

#define xEventGroupGetBits( xEventGroup ) xEventGroupClearBits( xEventGroup, 0 )

该函数实际是通过xEventGroupClearBits()实现的,只是它清除的事件位图为0,所以并不会影响该事件组当前的事件位图。

另外,该函数还有ISR的版本:

EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup ) PRIVILEGED_FUNCTION;

其具体实现如下:

{
    初始化`pxEventBits = ( EventGroup_t * ) xEventGroup`。

    进入临界区(`uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()`)。
    获取事件组的事件位图(`uxReturn = pxEventBits->uxEventBits`)。

    退出临界区(`portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus )`)。

    返回事件位图(`xReturn`)。
}

3.2. 设置事件

函数原型:

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ) PRIVILEGED_FUNCTION;

该函数可以设置事件组xEventGroup的事件位图,按照uxBitsToSet事件位图来进行设置。这可能唤醒正在等待事件的任务。

函数返回值是该函数返回时刻的事件位图:例如,如果在xEventGroupWaitBits()中设置了自动清除,那么函数返回时uxBitsToSet所代表的事件位图就会被清空;或者更高优先级的任务被唤醒,它操作了同一个事件组,也会导致该函数返回的事件位图与设置的uxBitsToSet不一致。因此这个返回值的意义不是特别大.

该函数涉及到从无序任务队列中删除任务,需要特别说明一下(BaseType_t xTaskRemoveFromUnorderedEventList( ListItem_t * pxEventListItem, const TickType_t xItemValue )):FreeRTOSv10后取消了该函数的返回值。

{
    条件检查:Assert 调度器必须挂起(`uxSchedulerSuspended != pdFALSE`)。

    设置任务事件队列列表项的值(`listSET_LIST_ITEM_VALUE( pxEventListItem, xItemValue | taskEVENT_LIST_ITEM_VALUE_IN_USE )`)。

    获取被唤醒任务的TCB(`pxUnblockedTCB = ( TCB_t * ) listGET_LIST_ITEM_OWNER( pxEventListItem )`)。
    将任务从事件任务队列中移除(`uxListRemove( pxEventListItem )`)。

    将任务从Delayed任务队列中删除(`uxListRemove( &( pxUnblockedTCB->xStateListItem ) )`)。
    将任务加入到Ready任务队列(`prvAddTaskToReadyList( pxUnblockedTCB )`)。

    如果被唤醒任务的优先级较高(`pxUnblockedTCB->uxPriority > pxCurrentTCB->uxPriority`):
    {
        设置调度请求标志用于返回(`xReturn = pdTRUE`)。

        设置调度挂起标志(`xYieldPending = pdTRUE`)。
    }
    否则:清除调度请求标志用于返回(`xReturn = pdFALSE`)。

    返回`xReturn`。
}

下面来看看该函数的具体实现:

设置事件的活动图

该函数的中断版本如下:

#if (configUSE_TRACE_FACILITY == 1 )
    BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
#else
    #define xEventGroupSetBitsFromISR( xEventGroup, uxBitsToSet, pxHigherPriorityTaskWoken ) xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken )
#endif

void vEventGroupSetBitsCallback( void *pvEventGroup, const uint32_t ulBItsToSet ) PRIVILEGED_FUNCTION;

下面看看具体的实现:

{
    调用通过Pended函数完成`xReturn = xTimerPendFunctionCallFromISR( vEventGroupSetBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToSet, pxHigherPriorityTaskWoken ) `。
}

其中vEventGroupSetBitsCallback()的实现如下:

{
    xEventGroupSetBits( pvEventGroup, ( EventBits_t ) ulBitsToSet )}

所以最终还是通过Pend函数调用xEventGroupSetBits()完成设置。

3.3. 清除事件

函数原型:

EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear ) PRIVILEGED_FUNCTION;

清除事件组xEventGroup的事件位图,以uxBitsToClear为清除的事件位图。函数会返回被清除之前的位图。

该函数的具体实现如下:

{
    初始化`pxEventBits = ( EventGroup_t * ) xEventGroup`。

    参数检验:Assert `xEventGroup != NULL`,`( uxBitsToClear & eventEVENT_BITS_CONTROL_BYTES ) == 0`。

    进入临界区(`taskENTER_CRITICAL()`)。

    暂存事件组当前的事件位图用于返回(`uxReturn = pxEventBits->uxEventBits`)。

    清除事件组当前的事件位图(`pxEventBits->uxEventBits &= ~uxBitsToClear`)。

    退出临界区(`taskEXIT_CRITICAL()`)。

    返回事件位图`uxReturn`。
}

该函数不能在中断上下文中调用。但可以使用中断上下文中的版本:

#if ( configUSE_TRACE_FACILITY == 1 )
    BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet ) PRIVILEGED_FUNCTION;
#else
    #define xEventGroupClearBitsFromISR( xEventGroup, uxBitsToClear ) xTimerPendFunctionCallFromISR( vEventGroupClearBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToClear, NULL )
#endif

void vEventGroupClearBitsCallback( void *pvEventGroup, const uint32_t ulBitsToClear ) PRIVILEGED_FUNCTION;

从函数原型定义就可以看出,如果是没有开启调试的功能,则可以通过Pend函数功能来实现。

具体实现如下:

{
    调用Pend函数`xReturn = xTimerPendFunctionCallFromISR( vEventGroupClearBitsCallback, ( void * ) xEventGroup, ( uint32_t ) uxBitsToClear, NULL )`。
}

vEventGroupClearBitsCallback()的实现如下:

{
    xEventGroupClearBits( pvEventGroup, ( EventBits_t ) ulBitsToClear )
}

可以看到,与设置事件的ISR版本一样,ISR的版本实际上是通过Pend函数,调用xEventGroupClearBits()实现。

3.4. 同步事件

同步事件主要用于以下场景:多个任务之间的同步,通常指的是需要一起达到某个同步点(即每个任务都需要达到一个预设的状态)后,他们才能继续往下执行,否则先抵达同步点的任务处于Blocked状态。

函数原型:

EventBits_t xEventGroupSyc( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait ) RPIVILEGED_FUNCTION;

如果在等待的过程中,uxBitsToWaitFor对应的事件位图已经被设置,函数则正常返回,并且对应的事件位图也会被清除。

uxBitsToSet是被同步的多个任务分别传入的事件位图,如果同步成功,那么这些任务调用时传入的uxBitsToSet将会组成完整的uxBitsToWaitFor

xTicksToWait是等待的时间。

如果同步成功,那么函数返回的事件位图就是uxBitsToWaitFor所代表的事件位图;如果超时,那么则返回超时时刻的事件位图。

其具体实现如下:

{
    初始化`pxEventBits = ( EventGroup_t * ) xEventGroup`,`xTimeoutOccured = pdFALSE`。

    参数校验:Assert `( uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES ) == 0`,`uxBitsToWaitFor != 0`
    (仅开启xTaskGetSchedulerState || USE_TIMERS)参数校验:Assert `!( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPEND ) && ( xTicksToWait != 0 ) )`。

    挂起调度器(`vTaskSuspendAll()`)。
    获取事件组的事件位图(`uxOriginalBitValue = pxEventBits->uxEventBits`)。

    设置事件组的事件位图(`xEventGroupSetBits( xEventGroup, uxBitsToSet )`)。

    如果条件满足唤醒条件(`( ( uxOriginalBitValue | uxBitsToSet ) & uxBitsToWaitFor ) == uxBitsToWaitFor`):
    {
        暂存事件位图用于返回(`uxReturn = uxOriginalBitsValue | uxBitsToSet`)。

        清除事件组的事件位图(`pxEventBits->uxEventBits &= ~uxBitsToWaitFor`)。

        清空等待事件(`xTicksToWait = 0`)。
    }
    否则:
    {
        如果调用者愿意阻塞(`xTicksToWait != 0`):
        {
            将任务加入到无序事件任务队列(`vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | eventCLEAR_EVENTS_ON_EXIT_BIT | eventWAIT_FOR_ALL_BITS ), xTicksToWait )`)。
        }
        否则,即调用者不愿意阻塞:
        {
            缓存当前事件组的事件位图(`uxReturn = pxEventBits->uxEventBits`)。
            设置超时标志`xTimeoutOccured = pdTRUE`。
        }
    }

    恢复调度器,并获取调度标志(`xTasksResumeAll()`)。

    如果调用者愿意阻塞(`xTicksToWait != 0`):
    {
        如果恢复调度器时为发生调度(`xAlReadyYielded == pdFALSE`),则强制触发一次调度(`portYIELD_WITHIN_API()`)。

        // 此时任务已经被唤醒
        复位并获取事件组当前的事件位图(`uxReturn = uxTaskResetEventItemValue()`)。

        如果该任务是因为超时而被唤醒(`( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == 0`):
        {
            进入临界区(`taskENTER_CRITICAL()`)。

            获取当前的事件位图(`uxReturn = pxEventBits->uxEventBits`)。

            再次判断是否满足需求,如果满足(`( uxReturn & uxBitsToWaitFor ) == uxBitsToWaitFor`),清除对应的事件位图(`pxEventBits->uxEventBits &= ~uxBitsToWaitFor`)。

            退出临界区(`taskEXIT_CRITICAL()`)。

            设置超时标志(`xTimeoutOccured = pdTRUE`)。
        }

        清除控制位信息(`uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES`)。
    }

    返回缓存的事件位图`uxReturn`。
}

可以看到,在同步事件中调用了xEventGroupSetBits(),因此可以实现由多个任务来等待并更新该事件组的事件位图的需求。

4. 删除事件组

函数原型:

void vEventGroupDelete( EventGroupHandle_t xEventGroup ) PRIVILEGED_FUNCTION;

该函数会删除事件组xEventGroup,当前所有等待该事件组的任务都会被唤醒,并且获取的事件位图为0。

具体实现如下:

{
    初始化`pxEventBits = ( EventGroup_t * ) xEventGroup`,`pxTaskWaitingForBits = &( pxEventBits->xTasksWaitingForBits )`。

    挂起调度器(`vTaskSuspendAll()`)。

    遍历事件组的事件任务队列,直到队列为空(`while ( listCURRENT_LIST_LENGTH( pxTasksWaitingForBits ) > 0 )`):
    {
        将任务从无序事件队列中删除(`xTaskRemoveFromUnorderedEventList( pxTaskWaitingForBits->xListEnd.pxNext, eventUNBLOCKED_DUE_TO_BIT_SET`)
    }

    (仅开启SUPPORT_DYNAMIC_ALLOCATION && SUPPORT_STATIC_ALLOCATION)如果没有设置静态申请标志(`pxEventBits->ucStaticallyAllocated == pdFALSE`),则回收事件组所使用的内存(`pxEventBits`)。

    恢复调度器(`vTaskResumeAll()`)。
}

5. 事件组调试接口

获取事件组编号的函数原型:

UBaseType_t uxEventGroupGetNumber( void* xEventGroup ) PRIVILEGED_FUNCTION;

6. 事件组的实现细节

6.1. 事件组的接口

6.1.1. 事件组的数据结构接口
6.1.1.1. 事件组句柄

句柄定义:

typedef void * EventGroupHandle_t;

创建事件组时返回的句柄,用作所有的事件组API的参数。

6.1.1.2. 事件位图

类型定义:

typedef TickType_t EventBits_t;

TickType_t被定义为系统的机器字长。因此,事件位图的大小与体系结构相关。

另外,TickType_t还可以通过configUSE_16_BIT_TICKS定义成16位。

但具体实现时,最高有效8位是系统预留事件位,不可用。

6.2. 事件组的内涵

6.2.1. 事件组依赖的头文件

C标准库头文件:

#include <stdlib.h>

FreeRTOS头文件:

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "event_groups.h"

可以看出,事件组的事件与任务和软件定时器有关。

6.2.2. 事件组的数据结构
6.2.2.1. 事件组的元信息结构体

结构体定义:

typedef struct xEventGroupDefinition
{
    EventBits_t uxEventBits;
    List_t xTasksWaitingForBits;    /*< List of tasks waiting for a bitmap 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;
6.2.3. 事件组的私有宏
6.2.3.1. 事件组的私有常量

事件组的内部事件标志定义:

#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
#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_BYITES  0xff000000UL
#endif

可以看到,事件位图的最高8位用于系统控制。其中最高有效位用于表示xItemValue被用于其他用途。详情可参看队列管理

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值