《Mastering the FreeRTOS Real Time Kernel》读书笔记(7)事件组

发布时间 2023-10-16 17:49:54作者: BD4RTZ

8.事件组

之前已经介绍了多任务之间的交流桥梁,包括了队列和信号量。
与队列和信号量不同:事件组允许任务在“阻塞”状态下等待一个或多个事件的组合发生。事件组在事件发生时,取消等待同一事件或事件组合的所有任务的阻塞状态。
事件组的这些独特属性可用于同步多个任务、向多个任务广播事件、允许任务在“阻塞”状态下等待一组事件中的任何一个事件发生,以及允许任务在阻止状态下等待多个操作完成。事件组还提供了减少应用程序使用的RAM的机会,因为通常可以用单个事件组替换许多二进制信号量。

8.2 事件组的特性

事件“flag”是一个布尔值(1或0),用于指示事件是否发生。事件“group”是一组事件标志。
事件标志只能是1或0,允许将事件标志的状态存储在单个位中,并将事件组中所有事件标志的状况存储在单个变量中;32位变量可以存储32个事件?
事件组中的每个事件标志的状态由EventBits_ t类型的变量中的单个比特表示。因此,事件标志也称为事件“位”。
如果EventBits_t变量中的某个位设置为1,则该位表示的事件已经发生。如果EventBits_t变量中的某个位设置为0,则该位表示的事件尚未发生。

用户可以自己命名事件组的每个位。比如:将事件组中的位0定义为“已从网络接收到消息”。 将事件组中的第1位定义为“消息已准备好发送到网络”。 将事件组中的第2位定义为“中止当前网络连接”。

位数分配

32位变量可以存储32个事件?不是的
如果configUSE_16BIT_TICKS为1,则每个事件组包含8个可用的事件位。如果configUSE_16BIT_TICKS为0,则每个事件组包含24个可用的事件位。

事件组访问权限

事件组是具有自身权限的对象,任何知道其存在的任务或ISR都可以访问这些对象。任何数量的任务都可以在同一事件组中设置位,任何数量的工作都可以从同一事件群中读取位。

事件组最适用的事例

使用FreeRTOS实现TCP/IP,因为需要很多握手和挥手过程。

8.3 如何使用事件组

创建一个事件组

EventGroupHandle_t xEventGroupCreate( void );

### 在事件组中置位
```c
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet );
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
                                      const EventBits_t uxBitsToSet,
                                      BaseType_t *pxHigherPriorityTaskWoken );

1.xEventGroup:创建的事件组句柄。
2.uxBitsToSet:一个位掩码,用于指定事件组中要设置为1的一个或多个事件位。事件组的值是通过将事件组的现有值与uxBitsToSet中传递的值进行位“或”运算来更新的。
例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件组中的事件位3被设置(如果尚未设置),同时保持事件组中所有其他事件位不变。
3.*pxHigherPriorityTaskWoken:xEventGroupSetBitsFromISR()不直接在中断服务例程内设置事件位,而是通过在计时器命令队列上发送命令来保护对RTOS守护进程任务的操作。
4.返回值:pdPASS、pdFALSE。

在任务中获取事件组情况

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

1.xEventGroup:事件组的句柄。
2.uxBitsToWaitFor:需要等待的事件组状态。
3.xClearOnExit:退出时是否清除。为pdTRUE,参数2等待的指定位清0,
4.xWaitForAllBits:是否等待所有的位都为1。pdTRUE,等待。pdFALSE,不等待。
5.xTicksToWait :最大超时事件。

例子22:事件组实验

所使用的步骤还是同上所述的三个基本步骤。
具体编程方面的内容,API如何使用,详见原文。

8.4 使用任务组实现任务同步

很多任务可以访问事件组,已完成一个需要合作的工程。但是由于事件组更像是树状结构,有些任务可能都知道有一个事件组,但是互相不了解对面,这样在做一个工程时,很容易由于不同步,导致出错。

可以将事件组中的某些位拿出来,做成“同步点”,以供各个子任务进行交流。能够知道互相的状况。

同步点设置

同步点的设置不能使用上一节介绍的基本事件组操作函数,需要有一个新的函数,来赋予同步点的特殊地位,并使其能和其他任务进行交流。

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

1.xEventGroup:事件组的句柄。
2.uxBitsToSet:需要设置的事件组位。
3.uxBitsToWaitFor:需要等待的事件组位。
4.xTicksToWait :最长等待时间。
5.返回值:略。

可以看出xEventGroupSync()有both两个功能,既能够置位,也能够等待事件组的位。
但是有一个疑惑,这个同步位是不是应该有多少个时间需要同步,就有多少个位。等到例子中,再作解答。

例子23:同步任务

上面的答案是,对,例子中是三个任务同步。

const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT |
                                    mainSECOND_TASK_BIT |
                                    mainTHIRD_TASK_BIT );

#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* Event bit 0, set by the first task. */
#define mainSECOND_TASK_BIT( 1UL << 1UL ) /* Event bit 1, set by the second task. */
#define mainTHIRD_TASK_BIT ( 1UL << 2UL ) /* Event bit 2, set by the third task. */

这样就很好理解了,也就是说事件组在分配任务方面在8.3节实现,8.4节中任务又能通过事件组互相反馈运行状态,以实现同步。