12、事件

1、事件的基本概念

  事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。

  每一个事件组只需要很少的 RAM 空间来保存事件组的状态。 事件组存储在一个EventBits_t 类 型 的 变 量 中 , 该 变 量 在 事 件 组 结 构 体 中 定 义 。 如 果 宏configUSE_16_BIT_TICKS 定义为 1, 那么变量 uxEventBits 就是 16 位的, 其中有 8 个位用来存储事件组; 而如果宏 configUSE_16_BIT_TICKS 定义为 0, 那么变量 uxEventBits 就是32 位 的 , 其 中 有 24 个 位 用 来 存 储 事 件 组 。 在 STM32 中 , 我 们 一 般 将configUSE_16_BIT_TICKS 定义为 0,那么 uxEventBits 是 32 位的,有 24 个位用来实现事件标志组。 每一位代表一个事件, 任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;事件“逻辑与” 则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。

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

  一对多同步模型:一个任务等待多个事件的触发,这种情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发。

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

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

  • 事件只与任务相关联,事件相互独立,一个 32 位的事件集合(EventBits_t 类型的变量, 实际可用与表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、 1 表示该事件类型已经发生),一共 24 种事件类型。
  • 事件仅用于同步,不提供数据传输功能。
  • 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走), 等效于只设置一次。
  • 允许多个任务对同一事件进行读写操作。
  • 支持事件等待超时机制。

  在 FreeRTOS 事件中, 每个事件获取的时候,用户可以选择感兴趣的事件,并且选择读取事件信息标记,它有三个属性,分别是逻辑与,逻辑或以及是否清除标记。当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记来判断当前接收的事件是否满足要求,如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下去。

2、 事件的应用场景

  FreeRTOS 的事件用于事件类型的通讯,无数据传输,也就是说,我们可以用事件来做标志位,判断某些事件是否发生了,然后根据结果做处理,那很多人又会问了,为什么我不直接用变量做标志呢,岂不是更好更有效率?非也非也,若是在裸机编程中,用全局变量是最为有效的方法,这点我不否认,但是在操作系统中,使用全局变量就要考虑以下问题了:

  • 如何对全局变量进行保护呢, 如何处理多任务同时对它进行访问?
  • 如何让内核对事件进行有效管理呢?使用全局变量的话,就需要在任务中轮询查看事件是否发送,这简直就是在浪费 CPU 资源啊,还有等待超时机制,使用全局变量的话需要用户自己去实现。

  所以,在操作系统中,还是使用操作系统给我们提供的通信机制就好了,简单方便还实用。

  在某些场合,可能需要多个时间发生了才能进行下一步操作,比如一些危险机器的启动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各个指标的时候,不能一下子检测完毕啊,所以,需要事件来做统一的等待,当所有的事件都完成了,那么机器才允许启动,这只是事件的其中一个应用。

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

  各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件, 任务仅对感兴趣的事件进行关注。当有它们感兴趣的事件发生时并且符合感兴趣的条件, 任务将被唤醒并进行后续的处理动作。

3 、事件运作机制

  接收事件时,可以根据感兴趣的参事件类型接收事件的单个或者多个事件类型。事件接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型,否则不会清除已接收到 的事件, 这样就需要用 户显式清 除事件位 。用 户可以自 定义通过传入 参数xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。

  设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可以一次同时写多个事件类型, 设置事件成功可能会触发任务调度。清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。

  事件不与任务相关联,事件相互独立,一个 32 位的变量(事件集合,实际用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、 1 表示该事件类型已经发生),一共 24 种事件类型具体下图:

           事件集合 set(一个 32 位的变量)

  事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的时候会被唤醒,其过程具体见下图 :

           事件唤醒任务示意图
  任务 1 对事件 3 或事件 5 感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒,并且执行相应操作。而任务 2 对事件 3 与事件 5 感兴趣(逻辑与),当且仅当事件 3 与事件 5 都发生的时候, 任务 2 才会被唤醒,如果只有一个其中一个事件发生,那么任务还是会继续等待事件发生。如果接在收事件函数中设置了清除事件位 xClearOnExit,那么当任务唤醒后将把事件 3 和事件 5 的事件标志清零,否则事件标志将依然存在。

4、 事件控制块

  事件标志组存储在一个 EventBits_t 类型的变量中, 该变量在事件组结构体中定义, 具体见代码清单 20-1 加粗部分。 如果宏 configUSE_16_BIT_TICKS 定义为 1, 那么变量uxEventBits 就 是 16 位 的 , 其 中 有 8 个 位 用 来 存 储 事 件 组 , 如 果 宏configUSE_16_BIT_TICKS 定义为 0, 那么变量 uxEventBits 就是 32 位的, 其中有 24 个位用来存储事件组, 每一位代表一个事件的发生与否,利用逻辑或、 逻辑与等实现不同事件的不同唤醒处理。 在 STM32 中, uxEventBits 是 32 位的, 所以我们有 24 个位用来实现事件组。 除了事件标志组变量之外, FreeRTOS 还使用了一个链表来记录等待事件的任务,所有在等待此事件的任务均会被挂载在等待事件列表 xTasksWaitingForBits。

typedef struct xEventGroupDefinition {
EventBits_t uxEventBits;
List_t xTasksWaitingForBits;

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

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

5、 事件函数接口讲解

  5.1 、事件创建函数 xEventGroupCreate()

  xEventGroupCreate()用于创建一个事件组,并返回对应的句柄。 要想使用该函数必须在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1(在FreeRTOS.h 中默认定义为 1) 且需要把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。

  每一个事件组只需要很少的 RAM 空间来保存事件的发生状态。如果使用函数xEventGroupCreate()来创建一个事件,那么需要的 RAM 是动态分配的。 如果使用函数xEventGroupCreateStatic()来创建一个事件,那么需要的 RAM 是静态分配的。我们暂时不讲解静态创建函数 xEventGroupCreateStatic()。事件创建函数,顾名思义,就是创建一个事件,与其他内核对象一样,都是需要先创建才能使用的资源, FreeRTOS 给我们提供了一个创建事件的函数 xEventGroupCreate(),当

  创建一个事件时, 系统会首先给我们分配事件控制块的内存空间,然后对该事件控制块进行基本的初始化,创建成功返回事件句柄;创建失败返回 NULL。 所以,在使用创建函数之前,我们需要先定义有个事件的句柄。

/*******************************************************************************************************
  *@ 函数功能:事件创建函数
  *@ 函数参数:
  *@ 返 回 值:返回事件句柄
*******************************************************************************************************/        
EventGroupHandle_t xEventGroupCreate( void )

  5.2 、事件删除函数 vEventGroupDelete()

  在很多场合,某些事件只用一次的,就好比在事件应用场景说的危险机器的启动,假如各项指标都达到了,并且机器启动成功了,那这个事件之后可能就没用了,那就可以进行销毁了。想要删除事件怎么办? FreeRTOS 给我们提供了一个删除事件的函数——vEventGroupDelete(),使用它就能将事件进行删除了。当系统不再使用事件对象时,可以通过删除事件对象控制块来释放系统资源。

/*******************************************************************************************************
  *@ 函数功能:事件删除函数
  *@ 函数参数:要删除的事件句柄
  *@ 返 回 值:
*******************************************************************************************************/    
void vEventGroupDelete( EventGroupHandle_t xEventGroup )

  5.3 、事件组置位函数 xEventGroupSetBits()(任务)

  xEventGroupSetBits()用于置位事件组中指定的位, 当位被置位之后,阻塞在该位上的任务将会被解锁。 使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。简单来说,就是设置我们自己定义的事件标志位为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。

  注意的是该函数不允许在中断中使用。

/*******************************************************************************************************
  *@ 函数功能:置位事件组中指定的位
  *@ 函数参数:xEventGroup:事件句柄    uxBitsToSet:指定事件中的事件标志位。如设置 uxBitsToSet 为 0x08 则只置位位 3,如果设置 uxBitsToSet 为 0x09 则位 3 和位 0 都需要被置位。
  *@ 返 回 值:返回调用 xEventGroupSetBits() 时事件组中的值
*******************************************************************************************************/    
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )

  5.4、 事件组置位函数 xEventGroupSetBitsFromISR()(中断)

  xEventGroupSetBitsFromISR()是 xEventGroupSetBits()的中断版本,用于置位事件组中指定的位。 置位事件组中的标志位是一个不确定的操作,因为阻塞在事件组的标志位上的任务的个数是不确定的。 FreeRTOS 是不允许不确定的操作在中断和临界段中发生的, 所以xEventGroupSetBitsFromISR()给 FreeRTOS 的守护任务发送一个消息,让置位事件组的操作在守护任务里面完成,守护任务是基于调度锁而非临界段的机制来实现的。需要注意的地方: 正如上文提到的那样,在中断中事件标志的置位是在守护任务(也叫软件定时器服务任务) 中完成的。 因此 FreeRTOS 的守护任务与其他任务一样, 都是系统调度器根据其优先级进行任务调度的, 但守护任务的优先级必须比任何任务的优先级都要高, 保证在需要的时候能立即切换任务从而达到快速处理的目的, 因为这是在中断中让事 件 标 志 位 置 位 , 其 优 先 级 由 FreeRTOSConfig.h 中 的 宏configTIMER_TASK_PRIORITY 来定义。

  其实 xEventGroupSetBitsFromISR()函数真正调用的也是 xEventGroupSetBits()函数,只不过是在守护任务中进行调用的,所以它实际上执行的上下文环境依旧是在任务中。要 想 使 用 该 函 数 , 必 须 把 configUSE_TIMERS 和INCLUDE_xTimerPendFunctionCall 这些宏在 FreeRTOSConfig.h 中都定义为 1,并且把FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中编译。

/*******************************************************************************************************
  *@ 函数功能:置位事件组中指定的位, 在中断函数中使用。
  *@ 函数参数:    xEventGroup:事件句柄    
            uxBitsToSet:指定事件中的事件标志位。如设置 uxBitsToSet 为 0x08 则只置位位 3,如果设置 uxBitsToSet 为 0x09 则位 3 和位 0 都需要被置位。
            pxHigherPriorityTaskWoken:pxHigherPriorityTaskWoken 在使用之前必须初始化成pdFALSE。调用 xEventGroupSetBitsFromISR()会给守护任务发送一个消息, 如果守护任务的优先级高于当前被中断的任务的优先级的话(一般情况下都需要将守护任务 的 优 先 级 设 置 为 所 有 任 务 中 最 高 优 先 级 ) ,pxHigherPriorityTaskWoken 会被置为 pdTRUE, 然后在中断退出前执行一次上下文切换。
  *@ 返 回 值:返回调用 xEventGroupSetBits() 时事件组中的值
*******************************************************************************************************/
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken )

  5.5 、等待事件函数 xEventGroupWaitBits()

  既然标记了事件的发生,那么我怎么知道他到底有没有发生,这也是需要一个函数来获 取 事 件 是 否 已 经 发 生 , FreeRTOS 提 供 了 一 个 等 待 指 定 事 件 的 函 数 — —xEventGroupWaitBits(),通过这个函数, 任务可以知道事件标志组中的哪些位,有什么事件发生了, 然后通过 “逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制, 当且仅当任务等待的事件发生时,任务才能获取到事件信息。在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态。这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到) 则返回对应的事件标志位,由用户判断再做处理, 因为在事件超时的时候也会返回一个不能确定的事件值,所以需要判断任务所等待的事件是否真的发生。

  EventGroupWaitBits()用于获取事件组中的一个或多个事件发生标志, 当要读取的事件标 志 位 没 有 被 置 位 时 任 务 将 进 入 阻 塞 等 待 状 态 。 要 想 使 用 该 函 数 必 须 把FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。

/*******************************************************************************************************
  *@ 函数功能:用于获取任务感兴趣的事件
  *@ 函数参数:    xEventGroup:事件句柄
            uxBitsToWaitFor:一个按位或的值,指定需要等待事件组中的哪些位置 1。如果需要等待 bit 0 and/or bit 2 那么 uxBitsToWaitFor 配置为 0x05(0101b)。如果需要等待 bits 0 and/or bit 1 and/or bit 2 那么 uxBitsToWaitFor 配置为 0x07(0111b)。
            xClearOnExit:pdTRUE:当 xEventGroupWaitBits()等待到满足任务唤醒的事件时, 系统将清除由形参 uxBitsToWaitFor 指定的事件标志位。pdFALSE:不会清除由形参 uxBitsToWaitFor 指定的事件标志位。
            xWaitForAllBits:pdTRUE :当 形 参 uxBitsToWaitFor 指 定 的 位 都 置 位 的 时 候 ,xEventGroupWaitBits()才满足任务唤醒的条件, 这也是“逻辑与”等待事件, 并且在没有超时的情况下返回对应的事件标志位的值。
                             pdFALSE:当形参 uxBitsToWaitFor 指定的位有其中任意一个置位的时候,这也是常说的“逻辑或”等待事件, 在没有超时的情况下函数返回对应的事件标志位的值。
            xTicksToWait:最大超时时间,单位为系统节拍周期,常量 portTICK_PERIOD_MS用于辅助把时间转换成 MS。
  *@ 返 回 值:返回事件中的哪些事件标志位被置位,返回值很可能并不是用户指定的事件位,需要对返回值进行判断再处理。
*******************************************************************************************************/    
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait )

  5.6 、xEventGroupClearBits()与 xEventGroupClearBitsFromISR()

  xEventGroupClearBits()与 xEventGroupClearBitsFromISR()都是用于清除事件组指定的位, 如果在获取事件的时候没有将对应的标志位清除, 那么就需要用这个函数来进行显式清除, xEventGroupClearBits()函数不能在中断中使用,而是由具有中断保护功能的xEventGroupClearBitsFromISR() 来代替,中断清除事件标志位的操作在守护任务(也叫定时 器 服 务 任 务 ) 里 面 完 成 。 守 护 进 程 的 优 先 级 由 FreeRTOSConfig.h 中 的 宏configTIMER_TASK_PRIORITY 来 定 义 。 要 想 使 用 该 函 数 必 须 把FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。 

/*******************************************************************************************************
  *@ 函数功能:清除事件组中指定的位
  *@ 函数参数:xEventGroup:事件句柄
            uxBitsToClear:位 0 都需要被清除。指定事件组中的哪个位需要清除。如设置 uxBitsToSet 为0x08 则只清除位 3, 如果设置 uxBitsToSet 为 0x09 则位 3和位 0 都需要被清除。
  *@ 返 回 值:事件在还没有清除指定位之前的值
*******************************************************************************************************/    
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )

 

 

 

posted @ 2020-11-07 20:55  孤情剑客  阅读(484)  评论(0)    收藏  举报