当前位置: 首页 > news >正文

Chapter 8 事件组

8.1 引言和范围

已经指出,实时嵌入式系统必须对事件采取行动。前面的章节已经描述了FreeRTOS允许事件被任务通信的功能。此类功能示例包括信号量和队列,两者都具有以下属性:

  • 它们允许任务在阻塞状态下等待单个事件发生。
  • 当事件发生时,它们将解除阻塞一个任务——被解除阻塞的任务是等待该事件的最高优先级任务。

事件组是FreeRTOS的另一个功能,允许事件被任务通信。与队列和信号量不同:

  • 事件组允许任务在阻塞状态下等待一个或多个事件的组合发生。
  • 当事件发生时,事件组将解除所有等待相同事件或事件组合的任务的阻塞。

事件组独特的属性使其在同步多个任务、向多个任务广播事件、允许任务在阻塞状态下等待事件组中任何一个事件发生、以及允许任务在阻塞状态下等待多个操作完成方面非常有用。

事件组还可以提供减少应用程序使用的RAM的机会,因为通常可以用单个事件组替换许多二进制信号量。

事件组功能是可选的。要包括事件组功能,请将FreeRTOS源文件event_groups.c作为项目的一部分进行构建。

范围

本章旨在使读者了解:

  • 事件组的实际用途。
  • 相对于FreeRTOS其他功能,事件组的优缺点。
  • 如何在事件组中设置位。
  • 如何在阻塞状态下等待事件组中的位被设置。
  • 如何使用事件组来同步一组任务。

8.2 事件组、事件标志和事件位

事件“标志”是一个布尔值(1或0),用于指示事件是否已经发生。事件“组”是一组事件标志。
事件标志只能为1或0,允许将事件标志的状态存储在单个位中,并将事件组中所有事件标志的状态存储在单个变量中;事件组中每个事件标志的状态由类型为EventBits_t的变量的单个位表示。因此,事件标志也被称为事件“位”。如果在EventBits_t变量中设置了位1,则表示那个位代表的事件已经发生。如果在EventBits_t变量中设置了位0,则表示那个位代表的事件尚未发生。
在这里插入图片描述

图71显示了如何将单个事件标志映射到EventBits_t变量中的单个位。
例如,如果事件组的值为0x92(二进制1001 0010),则只有事件位1、4和7被设置,因此只有代表位1、4和7的事件发生了。图72显示了一个具有事件位1、4和7设置的EventBits_t变量,其他事件位都被清除,使事件组具有值0x92。
在这里插入图片描述

应用程序编写者必须为事件组中的单个位分配意义。例如,应用程序编写者可能会创建一个事件组,然后:

  • 在事件组中定义位0为“从网络接收到一条消息”。
  • 在事件组中定义位1为“消息准备发送到网络”。
  • 在事件组中定义位2为“终止当前的网络连接”。

关于EventBits_t数据类型的一些更多信息
事件组中事件位的数量取决于FreeRTOSConfig.h中的编译时配置常量configUSE_16_BIT_TICKS

  • 如果configUSE_16_BIT_TICKS为1,则每个事件组包含8个可用的事件位。
  • 如果configUSE_16_BIT_TICKS为0,则每个事件组包含24个可用的事件位。

多任务访问
事件组是独立存在的对象,可以被任何知道其存在的任务或中断服务例程访问。任何数量的任务都可以在同一个事件组中设置位,任何数量的任务都可以从同一个事件组中读取位。
使用事件组的一个实际示例
FreeRTOS+TCP TCP/IP堆栈的实现提供了一个实际示例,说明如何使用事件组同时简化设计并最小化资源使用。
TCP套接字必须响应许多不同的事件。例如,事件包括接受事件、绑定事件、读取事件和关闭事件。套接字在任何给定时间可能期望的事件取决于套接字的状态。例如,如果一个套接字已经被创建,但尚未绑定到一个地址,那么它可以期望接收一个绑定事件,但不会期望接收一个读取事件(如果没有地址,它就不能读取数据)。
FreeRTOS+TCP套接字的状态存储在一个名为FreeRTOS_Socket_t的结构中。该结构包含一个事件组,其中为套接字必须处理的所有事件定义了一个事件位。FreeRTOS+TCP API调用会阻塞以等待一个事件或一组事件,只需阻塞在事件组上。
事件组还包含一个“终止”位,允许在套接字等待任何事件时终止TCP连接。

8.3 使用事件组进行事件管理

xEventGroupCreate() API 函数

FreeRTOS V9.0.0 还包括了 xEventGroupCreateStatic() 函数,该函数在编译时静态分配创建事件组所需的内存:

  • 在使用之前,必须显式创建事件组。
  • 使用 EventGroupHandle_t 类型的变量来引用事件组。
    xEventGroupCreate() API 函数用于创建事件组,并返回一个 EventGroupHandle_t 来引用它创建的事件组。
EventGroupHandle_t xEventGroupCreate( void );
参数名称描述返回值
返回值如果返回 NULL,则表示事件组无法创建,因为 FreeRTOS 无法为事件组数据结构分配足够的堆内存。
第2章提供了更多关于堆内存管理的详细信息。
返回一个非 NULL 的值表示事件组已成功创建。返回的值应该被存储为创建的事件组的句柄。

xEventGroupSetBits() API 函数

xEventGroupSetBits() API 函数设置事件组中的一个或多个位,通常用于通知任务某个事件已经发生。
注意:不要在中断服务例程中调用 xEventGroupSetBits()。应该使用中断安全的版本 xEventGroupSetBitsFromISR() 代替。

void xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );

表43: xEventGroupSetBits() 参数和返回值

参数名称描述
xEventGroup设置位的 event 组句柄。event 组句柄将是从调用 xEventGroupCreate() 用于创建 event 组时返回的。
uxBitsToSet指定在 event 组中设置为 1 的 event 位,或者 event 位。event 组的值通过按位或操作 event 组的现有值和通过 uxBitsToSet 传递的值来更新。
返回值xEventGroupSetBits() 调用返回时 event 组的值。注意,返回的值不一定会设置 uxBitsToSet 指定的位,因为这些位可能已经被不同的任务清除。

xEventGroupSetBitsFromISR() API 函数

xEventGroupSetBitsFromISR() 是 xEventGroupSetBits() 的中断安全版本。

因为释放信号量是一个确定性操作,因为在事先知道释放信号量最多只会导致一个任务离开阻塞状态。而当在事件组中设置位时,在事先并不知道会有多少任务离开阻塞状态,所以设置事件组中的位不是一个确定性操作。

FreeRTOS 设计和实现标准不允许在中断服务例程内部或在中断被禁用时执行非确定性操作。

因此,xEventGroupSetBitsFromISR() 不在中断服务例程内部直接设置事件位,而是将操作推迟到 RTOS 守护任务。

BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken ); 

Listing 134. The xEventGroupSetBitsFromISR() API function prototype

表44: xEventGroupSetBitsFromISR() 参数和返回值

参数名称描述
xEventGroup设置位的 event 组句柄。event 组句柄将是从调用 xEventGroupCreate() 用于创建 event 组时返回的。
uxBitsToSet一个位掩码,用于指定在 event 组中设置为 1 的 event 位,或者 event 位。event 组的值通过按位或操作 event 组的现有值和通过 uxBitsToSet 传递的值来更新。
返回值xEventGroupSetBitsFromISR() 调用返回时 event 组的值。注意,返回的值不一定会设置 uxBitsToSet 指定的位,因为这些位可能已经被不同的任务清除。
pxHigherPriorityTaskWokenxEventGroupSetBitsFromISR() 不在中断服务例程内部直接设置事件位,而是通过向定时器命令队列发送命令来推迟到 RTOS 守护任务执行。如果守护任务正在等待定时器命令队列上数据变得可用,那么写入定时器命令队列将导致守护任务离开阻塞状态。
如果守护任务的优先级高于当前正在执行的任务(即被中断的任务)的优先级,那么 xEventGroupSetBitsFromISR() 内部会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE。如果 xEventGroupSetBitsFromISR() 将此值设置为 pdTRUE,那么在中断退出之前应执行上下文切换。这将确保中断直接返回到守护任务,因为守护任务将是最高优先级的就绪状态任务。
返回值有两个可能的返回值:
1. pdPASS
pdPASS 只有在成功将数据发送到定时器命令队列时才会返回。
2. pdFALSE
pdFALSE 会在“设置位”命令无法写入定时器命令队列时返回,因为队列已经满了。

xEventGroupWaitBits() API 函数

xEventGroupWaitBits() API 函数允许任务读取事件组的值,并可选地等待在阻塞状态下,事件组中的一个或多个事件位被设置,如果这些事件位尚未设置。

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

调度器使用的条件
调度器用于确定任务是否进入阻塞状态,以及任务何时离开阻塞状态的条件称为“解阻塞条件”。解阻塞条件由 uxBitsToWaitForxWaitForAllBits 参数值的组合指定:

uxBitsToWaitFor 指定要测试的事件组中的哪个事件位。
xWaitForAllBits 指定是否使用按位或测试,或按位与测试。
如果任务的解阻塞条件在调用 xEventGroupWaitBits() 时满足,则任务不会进入阻塞状态。

表45: uxBitsToWaitFor 和 xWaitForAllBits 参数的影响

现有事件组值uxBitsToWaitFor 值xWaitForAllBits 值结果行为
00000101pdFALSE调用任务将进入阻塞状态,因为事件组中位0或位2都没有被设置,并且将在事件组中位0或位2被设置时离开阻塞状态。
01000101pdTRUE调用任务将进入阻塞状态,因为位0和位2都没有同时在事件组中设置,并且将在事件组中位0和位2都被设置时离开阻塞状态。
01000110pdFALSE调用任务将不会进入阻塞状态,因为 xWaitForAllBits 是 pdFALSE,并且由 uxBitsToWaitFor 指定的两个位中有一个已经在事件组中被设置。
01000110pdTRUE调用任务将进入阻塞状态,因为 xWaitForAllBits 是 pdTRUE,并且由 uxBitsToWaitFor 指定的两个位中只有一个已经在事件组中被设置。任务将在事件组中位2和位3都被设置时离开阻塞状态。

调用任务通过 uxBitsToWaitFor 参数指定要测试的位,并且很可能在满足其解阻塞条件后,调用任务需要将这些位清除回零。可以使用 xEventGroupClearBits() API 函数清除事件位,但如果手动清除事件位,则会在应用程序代码中引发竞态条件,如果:

  • 存在使用同一事件组的多个任务。
  • 事件组中的位由不同的任务或中断服务例程设置。

为了避免这些潜在的竞态条件,提供了 xClearOnExit 参数。如果 xClearOnExit 设置为 pdTRUE,那么对调用任务来说,测试和清除事件位看起来像是一个原子操作(不会被其他任务或中断中断)。

表46,xEventGroupWaitBits() 参数及返回值

参数名称描述
xEventGroup包含要读取的事件位的事件组句柄。事件组句柄是在创建事件组时调用 xEventGroupCreate() 返回的。
uxBitsToWaitFor一个位掩码,用于指定在事件组中要测试的事件位或事件位。例如,如果调用任务想要等待事件组中的事件位0和/或事件位2被设置,那么将 uxBitsToWaitFor 设置为 0x05(二进制 0101)。有关更多示例,请参考表45。
xClearOnExit如果调用任务的解阻塞条件已满足,并且 xClearOnExit 设置为 pdTRUE,则在调用任务退出 xEventGroupWaitBits() API 函数之前,事件组中由 uxBitsToWaitFor 指定的事件位将被清除回0。

如果 xClearOnExit 设置为 pdFALSE,则事件组中的事件位状态不会被 xEventGroupWaitBits() API 函数修改。
xWaitForAllBitsxWaitForAllBits 指定调用任务是否应该在 uxBitsToWaitFor 参数指定的一个或多个事件位被设置时从阻塞状态移除,还是在 uxBitsToWaitFor 参数指定的所有事件位都被设置时才移除。

如果 xWaitForAllBits 设置为 pdFALSE,那么进入阻塞状态等待其解阻塞条件满足的任务将在 uxBitsToWaitFor 指定的任何位被设置时离开阻塞状态(或者当 xTicksToWait 参数指定的时间到期)。

如果 xWaitForAllBits 设置为 pdTRUE,那么进入阻塞状态等待其解阻塞条件满足的任务只会在 uxBitsToWaitFor 指定的所有位都被设置时离开阻塞状态(或者当 xTicksToWait 参数指定的时间到期)。

有关示例,请参考表45。
xTicksToWait任务在阻塞状态下等待其解阻塞条件满足的最大时间。

如果 xTicksToWait 为零,或者调用 xEventGroupWaitBits() 时解阻塞条件已经满足,xEventGroupWaitBits() 将立即返回。

阻塞时间以滴答周期指定,因此它代表的确切时间取决于滴答频率。可以使用宏 pdMS_TO_TICKS() 将以毫秒为单位指定的时间转换为以滴答为单位指定的时间。

如果将 xTicksToWait 设置为 portMAX_DELAY,任务将无限期等待(不会超时),前提是在 FreeRTOSConfig.h 中将 INCLUDE_vTaskSuspend 设置为 1。
返回值如果 xEventGroupWaitBits() 因为调用任务的解阻塞条件被满足而返回,那么返回值是调用任务的解阻塞条件被满足时(如果 xClearOnExit 是 pdTRUE,则在任何位被自动清除之前)事件组的值。在这种情况下,返回值也将满足解阻塞条件。

如果 xEventGroupWaitBits() 因为 xTicksToWait 参数指定的阻塞时间到期而返回,那么返回值是阻塞时间到期时事件组的值。在这种情况下,返回值不会满足解阻塞条件。

示例22:事件组的实验

此示例演示了如何:

  • 创建一个事件组。
  • 在中断服务例程中设置事件组的位。
  • 在任务中设置事件组的位。
  • 在事件组上阻塞。

通过首先将 xWaitForAllBits 设置为 pdFALSE 执行示例,然后将其设置为 pdTRUE,演示了 xEventGroupWaitBits()xWaitForAllBits 参数的效果。
事件位0和事件位1由任务设置。事件位2由中断服务例程设置。这三个位使用列表136中显示的 #define 语句赋予了描述性的名称。

/* 定义事件组中的事件位。 */
#define mainFIRST_TASK_BIT  ( 1UL << 0UL ) /* 事件位0,由任务设置。 */
#define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* 事件位1,由任务设置。 */
#define mainISR_BIT         ( 1UL << 2UL ) /* 事件位2,由ISR设置。 */

列表136. 示例22中使用的事件位定义

列表137显示了设置事件位0和事件位1的任务的实现。它在循环中,重复地设置一个位,然后设置另一个位,每次调用 xEventGroupSetBits() 之间有200毫秒的延迟。在设置每个位之前打印一条字符串,以便可以在控制台中看到执行序列。

static void vEventBitSettingTask( void *pvParameters ) 
{ const TickType_t xDelay200ms = pdMS_TO_TICKS( 200UL ), xDontBlock = 0; for( ;; ) { /* 在开始下一个循环之前短暂延迟。 */vTaskDelay( xDelay200ms ); /* 打印一条消息,说任务即将设置事件位0,然后设置事件位0。 */vPrintString( "Bit setting task -\t about to set bit 0.\r\n" ); xEventGroupSetBits( xEventGroup, mainFIRST_TASK_BIT ); /* 在设置另一个位之前短暂延迟。 */vTaskDelay( xDelay200ms ); /* 打印一条消息,说任务即将设置事件位1,然后设置事件位1。 */vPrintString( "Bit setting task -\t about to set bit 1.\r\n" ); xEventGroupSetBits( xEventGroup, mainSECOND_TASK_BIT ); } 
}

列表137. 示例22中在事件组中设置两个位的任务

列表138显示了在中断服务例程中设置事件组位2的实现。同样,在设置位之前打印一条字符串,以便可以在控制台中看到执行序列。然而,在这种情况下,由于不应在中断服务例程中直接执行控制台输出,因此使用 xTimerPendFunctionCallFromISR() 在RTOS守护任务上下文中执行输出。
与之前的示例一样,中断服务例程由一个简单的周期性任务触发,该任务强制一个软件中断。在此示例中,中断每500毫秒生成一次。

static uint32_t ulEventBitSettingISR( void ) 
{ /* 字符串不是在中断服务例程中打印的,而是发送到RTOS守护任务进行打印。因此,它被声明为静态的,以确保编译器不会在ISR的堆栈上分配字符串,因为当从守护任务打印字符串时,ISR的堆栈帧将不存在。 */static const char *pcString = "Bit setting ISR -\t about to set bit 2.\r\n"; BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 打印一条消息,说即将设置位2。由于不能从中断服务例程打印消息,所以通过挂起一个函数调用,在RTOS守护任务的上下文中运行,将实际的输出推迟到RTOS守护任务。 */xTimerPendFunctionCallFromISR( vPrintStringFromDaemonTask,  ( void * ) pcString,  0,  &xHigherPriorityTaskWoken ); /* 在事件组中设置位2。 */xEventGroupSetBitsFromISR( xEventGroup, mainISR_BIT, &xHigherPriorityTaskWoken ); /* xTimerPendFunctionCallFromISR() 和 xEventGroupSetBitsFromISR() 都写入定时器命令队列,并且都使用了相同的 xHigherPriorityTaskWoken 变量。如果写入定时器命令队列导致RTOS守护任务离开阻塞状态,并且RTOS守护任务的优先级高于当前执行的任务(此中断中断的任务)的优先级,那么 xHigherPriorityTaskWoken将被设置为 pdTRUE。xHigherPriorityTaskWoken 用作 portYIELD_FROM_ISR() 的参数。如果 xHigherPriorityTaskWoken 等于 pdTRUE,则调用 portYIELD_FROM_ISR() 将请求上下文切换。如果 xHigherPriorityTaskWoken 仍然是 pdFALSE,则调用 portYIELD_FROM_ISR() 将没有效果。Windows端口使用的 portYIELD_FROM_ISR() 实现包括一个返回语句,这就是为什么这个函数没有显式返回一个值。 */portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); 
}

列表138. 示例22中在中断服务例程中设置事件组位2的实现

列表139展示了调用xEventGroupWaitBits()来阻塞在事件组上的任务的实现。该任务会为事件组中设置的每个位打印一条字符串。xEventGroupWaitBits()的xClearOnExit参数被设置为pdTRUE,因此导致xEventGroupWaitBits()返回的事件位或位在xEventGroupWaitBits()返回之前会被自动清除。

static void vEventBitReadingTask( void *pvParameters ) 
{ EventBits_t xEventGroupValue; const EventBits_t xBitsToWaitFor = ( mainFIRST_TASK_BIT  |  mainSECOND_TASK_BIT |  mainISR_BIT ); for( ;; ) { /* 阻塞等待事件位在事件组中被设置。 */ xEventGroupValue = xEventGroupWaitBits( /* 要读取的事件组 */ xEventGroup, /* 要测试的位 */ xBitsToWaitFor, /* 如果满足解阻塞条件,则在退出时清除位 */ pdTRUE, /* 不等待所有位。这个参数在第二次执行时设置为 pdTRUE。 */ pdFALSE, /* 不超时。 */ portMAX_DELAY ); /* 为每个被设置的位打印一条消息。 */ if( ( xEventGroupValue & mainFIRST_TASK_BIT ) != 0 ) { vPrintString( "Bit reading task -\t Event bit 0 was set\r\n" ); } if( ( xEventGroupValue & mainSECOND_TASK_BIT ) != 0 ) { vPrintString( "Bit reading task -\t Event bit 1 was set\r\n" ); } if( ( xEventGroupValue & mainISR_BIT ) != 0 ) { vPrintString( "Bit reading task -\t Event bit 2 was set\r\n" ); } } 
} 

主函数在启动调度器之前创建事件组和任务。请参阅列表140以查看其实现。从事件组读取的任务的优先级高于向事件组写入的任务,确保读取任务每次满足其解阻塞条件时都会抢占写入任务。

int main( void ) 
{ /* 在使用事件组之前,必须先创建它。 */ xEventGroup = xEventGroupCreate(); /* 创建一个任务,该任务在事件组中设置事件位。 */ xTaskCreate( vEventBitSettingTask, "Bit Setter", 1000, NULL, 1, NULL ); /* 创建一个任务,该任务等待事件组中的事件位被设置。 */ xTaskCreate( vEventBitReadingTask, "Bit Reader", 1000, NULL, 2, NULL ); /* 创建一个任务,该任务用于周期性地生成软件中断。 */ xTaskCreate( vInterruptGenerator, "Int Gen", 1000, NULL, 3, NULL ); /* 安装软件中断的处理程序。所需的语法取决于使用的FreeRTOS端口。这里显示的语法只能与FreeRTOS Windows端口一起使用,其中此类中断仅被模拟。 */ vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulEventBitSettingISR ); /* 启动调度器,使创建的任务开始执行。 */ vTaskStartScheduler(); /* 以下行不应该被达到。 */ for( ;; ); return 0; 
} 

当在示例22中执行时,将xEventGroupWaitBits()xWaitForAllBits参数设置为pdFALSE时产生的输出显示在图73中。在图73中,可以看到,因为xWaitForAllBits参数在调用xEventGroupWaitBits()时被设置为pdFALSE,所以每当事件位被设置时,从事件组读取的任务都会立即离开阻塞状态并执行。
在这里插入图片描述

当在示例22中执行时,将xEventGroupWaitBits()xWaitForAllBits参数设置为pdTRUE时产生的输出显示在图74中。在图74中,可以看到,因为xWaitForAllBits参数被设置为pdTRUE,所以从事件组读取的任务只在所有三个事件位都被设置后才离开阻塞状态。
在这里插入图片描述

8.4 使用事件组进行任务同步

有时应用程序的设计需要两个或更多的任务相互同步。例如,考虑这样一种设计:任务A接收到一个事件,然后将事件所需的一些处理委托给其他三个任务:任务B、任务C和任务D。如果任务A在任务B、C和D完成前一个事件的处理之前不能接收另一个事件,那么这四个任务都需要相互同步。每个任务的同步点将是在该任务完成其处理之后,且在其他每个任务完成相同操作之前不能继续进行。任务A只能在所有四个任务都达到它们的同步点之后,才能接收另一个事件。

这种任务同步需求的一个不那么抽象的例子可以在FreeRTOS+TCP演示项目之一中找到。该演示在两个任务之间共享一个TCP套接字;一个任务向套接字发送数据,另一个任务从同一个套接字接收数据。除非确信另一个任务不会再次尝试访问套接字,否则关闭套接字对任一任务来说都是不安全的。如果这两个任务中的任一个希望关闭套接字,那么它必须通知另一个任务其意图,并等待另一个任务停止使用套接字后再继续。展示的是任务希望关闭套接字的场景,伪代码在清单140中显示。

清单140展示的场景比较简单,因为只有两个任务需要相互同步,但是很容易看出,如果其他任务正在执行依赖于套接字打开的处理,场景将变得更为复杂,并且需要更多的任务加入同步。

 
void SocketTxTask( void *pvParameters ) 
{ 
xSocket_t xSocket; 
uint32_t ulTxCount = 0UL; for( ;; ) { /* Create a new socket.  This task will send to this socket, and another task will receive  from this socket. */ xSocket = FreeRTOS_socket( ... ); /* Connect the socket. */ FreeRTOS_connect( xSocket, ... ); /* Use a queue to send the socket to the task that receives data. */ xQueueSend( xSocketPassingQueue, &xSocket, portMAX_DELAY ); /* Send 1000 messages to the socket before closing the socket. */ for( ulTxCount = 0; ulTxCount < 1000; ulTxCount++ ) { if( FreeRTOS_send( xSocket, ... ) < 0 ) { /* Unexpected error - exit the loop, after which the socket will be closed. */ break; } } /* Let the Rx task know the Tx task wants to close the socket. */ TxTaskWantsToCloseSocket(); /* This is the Tx task’s synchronization point.  The Tx task waits here for the Rx task to  reach its synchronization point.  The Rx task will only reach its synchronization point  when it is no longer using the socket, and the socket can be closed safely. */  xEventGroupSync( ... ); /* Neither task is using the socket.  Shut down the connection, then close the socket. */ FreeRTOS_shutdown( xSocket, ... ); WaitForSocketToDisconnect(); FreeRTOS_closesocket( xSocket ); } 
} 
/*-----------------------------------------------------------*/ void SocketRxTask( void *pvParameters ) 
{ 
xSocket_t xSocket; for( ;; ) { /* Wait to receive a socket that was created and connected by the Tx task. */ xQueueReceive( xSocketPassingQueue, &xSocket, portMAX_DELAY ); /* Keep receiving from the socket until the Tx task wants to close the socket. */ while( TxTaskWantsToCloseSocket() == pdFALSE ) { /* Receive then process data. */ FreeRTOS_recv( xSocket, ... ); ProcessReceivedData(); } /* This is the Rx task’s synchronization point - it only reaches here when it is no longer  using the socket, and it is therefore safe for the Tx task to close the socket. */ xEventGroupSync( ... ); } 
} 

清单141. 两个任务之间相互同步的伪代码,以确保在关闭套接字之前,共享的TCP套接字不再被任一任务使用。

事件组可用于创建同步点:

  • 必须参与同步的每个任务都在事件组内分配一个唯一的事件位。
  • 每个任务在其达到同步点时设置自己的事件位。
  • 设置了自己的事件位后,每个任务都会在事件组上阻塞,等待代表所有其他同步任务的事件位也被设置。
    然而,在这个场景中不能使用 xEventGroupSetBits()xEventGroupWaitBits() API函数。如果使用了这些函数,那么设置位(表示任务已达到其同步点)和测试位(确定其他同步任务是否已达到其同步点)将作为两个单独的操作执行。为了理解为什么会成为问题,考虑以下场景,其中任务A、任务B和任务C尝试使用事件组进行同步:
  1. 任务A和任务B已经达到同步点,因此它们的事件位在事件组中被设置,并且它们处于阻塞状态,等待任务C的事件位也被设置。
  2. 任务C达到同步点,并使用 xEventGroupSetBits() 在事件组中设置其位。一旦任务C的位被设置,任务A和任务B离开阻塞状态,并清除所有三个事件位。
  3. 然后,任务C调用 xEventGroupWaitBits() 等待所有三个事件位被设置,但到那时,所有三个事件位已经被清除,任务A和任务B已经离开了各自的同步点,因此同步失败。
    为了成功使用事件组创建同步点,必须将设置事件位和随后的事件位测试作为单一不可中断的操作执行。为此提供了 xEventGroupSync() API函数。

xEventGroupSync() API 函数

xEventGroupSync() 函数允许两个或更多任务使用事件组相互同步。该函数允许任务在事件组中设置一个或多个事件位,然后作为单一不可中断的操作,等待同一事件组中的事件位组合被设置。
xEventGroupSync()uxBitsToWaitFor 参数指定了调用任务的解除阻塞条件。如果 xEventGroupSync() 因为满足了解除阻塞条件而返回,那么在 xEventGroupSync() 返回之前,由 uxBitsToWaitFor 指定的事件位将被清除回零。

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

清单142. xEventGroupSync() API函数原型

表47, xEventGroupSync() 参数和返回值

参数名称描述
xEventGroup要设置并测试事件位的事件组的句柄。事件组句柄将从创建事件组的 xEventGroupCreate() 调用返回。
uxBitsToSet指定在事件组中设置为1的事件位或事件位的位掩码。事件组的值通过将事件组的现有值与 uxBitsToSet 传递的值进行按位或操作来更新。例如,将 uxBitsToSet 设置为0x04(二进制0100)将导致事件位3被设置(如果它尚未被设置),同时保持事件组中的其他所有事件位不变。
uxBitsToWaitFor指定在事件组中测试的事件位或事件位的位掩码。例如,如果调用任务希望等待事件组中的事件位0、1和2被设置,那么将 uxBitsToWaitFor 设置为0x07(二进制111)。
xTicksToWait任务应在阻塞状态中等待其解除阻塞条件被满足的最大时间量。如果 xTicksToWait 为零,或者 xEventGroupSync() 被调用时解除阻塞条件已满足,则 xEventGroupSync() 将立即返回。阻塞时间以刻度周期指定,因此它表示的绝对时间取决于刻度频率。宏 pdMS_TO_TICKS() 可以用来将以毫秒指定的时间转换为以刻度指定的时间。将 xTicksToWait 设置为 portMAX_DELAY 将导致任务无限期等待(不会超时),前提是在 FreeRTOSConfig.h 中将 INCLUDE_vTaskSuspend 设置为1。
返回值如果 xEventGroupSync() 因为调用任务的解除阻塞条件被满足而返回,那么返回值是调用任务的解除阻塞条件被满足时事件组的值(在任何位被自动清除回零之前)。在这种情况下,返回值也将满足调用任务的解除阻塞条件。
如果 xEventGroupSync() 因为 xTicksToWait 参数指定的阻塞时间过期而返回,那么返回值是阻塞时间过期时事件组的值。在这种情况下,返回值不会满足调用任务的解除阻塞条件。

示例23:任务同步

示例23使用 xEventGroupSync() 来同步单一任务实现的三个实例。任务参数用于将事件位传递给每个实例,该事件位是任务在调用 xEventGroupSync() 时将设置的。
任务在调用 xEventGroupSync() 之前打印一条消息,并在 xEventGroupSync() 调用返回后再次打印消息。每条消息都包含一个时间戳。这允许观察在输出中执行序列。使用一个伪随机延迟来防止所有任务同时达到同步点。
请参见清单143以获取任务的实现。

 
static void vSyncingTask( void *pvParameters ) 
{ 
const TickType_t xMaxDelay = pdMS_TO_TICKS( 4000UL ); 
const TickType_t xMinDelay = pdMS_TO_TICKS( 200UL ); 
TickType_t xDelayTime; 
EventBits_t uxThisTasksSyncBit; 
const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT  |  mainSECOND_TASK_BIT |  mainTHIRD_TASK_BIT ); /* Three instances of this task are created - each task uses a different event  bit in the synchronization.  The event bit to use is passed into each task  instance using the task parameter.  Store it in the uxThisTasksSyncBit  variable. */ uxThisTasksSyncBit = ( EventBits_t ) pvParameters; for( ;; ) { /* Simulate this task taking some time to perform an action by delaying for a  pseudo random time.  This prevents all three instances of this task reaching the synchronization point at the same time, and so allows the example’s  behavior to be observed more easily. */ xDelayTime = ( rand() % xMaxDelay ) + xMinDelay; vTaskDelay( xDelayTime ); /* Print out a message to show this task has reached its synchronization point.  pcTaskGetTaskName() is an API function that returns the name assigned  to the task when the task was created. */ vPrintTwoStrings( pcTaskGetTaskName( NULL ), "reached sync point" ); /* Wait for all the tasks to have reached their respective synchronization  points. */ xEventGroupSync( /* The event group used to synchronize. */ xEventGroup, /* The bit set by this task to indicate it has reached the  synchronization point. */ uxThisTasksSyncBit, /* The bits to wait for, one bit for each task taking part  in the synchronization. */ uxAllSyncBits, /* Wait indefinitely for all three tasks to reach the synchronization point. */ portMAX_DELAY ); /* Print out a message to show this task has passed its synchronization  point.  As an indefinite delay was used the following line will only be  executed after all the tasks reached their respective synchronization  points. */ vPrintTwoStrings( pcTaskGetTaskName( NULL ), "exited sync point" ); } 
} 
/* Definitions for the event bits in the event group. */ 
#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. */ 
/* Declare the event group used to synchronize the three tasks. */ 
EventGroupHandle_t xEventGroup; 
int main( void ) 
{ 
/* Before an event group can be used it must first be created. */ 
xEventGroup = xEventGroupCreate(); 
/* Create three instances of the task.  Each task is given a different name, 
which is later printed out to give a visual indication of which task is 
executing.  The event bit to use when the task reaches its synchronization point 
is passed into the task using the task parameter. */ 
xTaskCreate( vSyncingTask, "Task 1", 1000, mainFIRST_TASK_BIT, 1, NULL ); 
xTaskCreate( vSyncingTask, "Task 2", 1000, mainSECOND_TASK_BIT, 1, NULL ); 
xTaskCreate( vSyncingTask, "Task 3", 1000, mainTHIRD_TASK_BIT, 1, NULL ); 
/* Start the scheduler so the created tasks start executing. */ 
vTaskStartScheduler(); 
/* As always, the following line should never be reached. */ 
for( ;; ); 
return 0; 
}  

当执行示例23时产生的输出显示在图75中。可以看出,尽管每个任务在不同的(伪随机)时间达到同步点,但每个任务都是在同一时间退出同步点(即最后一个任务达到同步点的时间)。
在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • gitlab修改默认访问端口
  • 简单的class.getResource与classLoader.getResource区别
  • 【Golang】go mod的使用
  • 性能测试 —— linux服务器搭建JMeter+Grafana+Influxdb监控可视化平台!
  • Spring Boot集成钉钉群通知机器人
  • Vue2 和 Vue3 自定义指令比较
  • 昂科烧录器支持PAI-IC澎湃微电子的32位微控制器PT32L031K6T6
  • 多模态论文自己学习路程_每天推出新版本_请看当天版本
  • 【vue3|第23期】Vite + Vue3: 深入理解public和assets文件夹的作用与使用
  • 安装postgresql和PGVector
  • Linux线程基础学习记录(线程的创建、回收以及结束)
  • C#中的S7协议
  • 【计算机网络】应用层自定义协议与序列化
  • 批量查询全国快递单号:高效追踪物流信息
  • HarmonyOS应用开发学习-ArkUI-容器组件
  • JavaScript 如何正确处理 Unicode 编码问题!
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • Angular 2 DI - IoC DI - 1
  • angular2开源库收集
  • CentOS 7 防火墙操作
  • JavaScript设计模式之工厂模式
  • Java-详解HashMap
  • laravel 用artisan创建自己的模板
  • learning koa2.x
  • Magento 1.x 中文订单打印乱码
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • ViewService——一种保证客户端与服务端同步的方法
  • Vue.js-Day01
  • 浮动相关
  • 给第三方使用接口的 URL 签名实现
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 推荐一款sublime text 3 支持JSX和es201x 代码格式化的插件
  • 我是如何设计 Upload 上传组件的
  • 怎样选择前端框架
  • Python 之网络式编程
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • # Panda3d 碰撞检测系统介绍
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #define,static,const,三种常量的区别
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • $nextTick的使用场景介绍
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (2)leetcode 234.回文链表 141.环形链表
  • (2)nginx 安装、启停
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (TOJ2804)Even? Odd?
  • (纯JS)图片裁剪
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (二) 初入MySQL 【数据库管理】
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (排序详解之 堆排序)
  • (四)库存超卖案例实战——优化redis分布式锁
  • (五)activiti-modeler 编辑器初步优化
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)