实时嵌入式系统必须对事件做出响应。FreeRTOS允许将事件传递给任务。此类功能的示例包括信号量和队列,两者都具有以下属性:
它们允许任务在"阻塞"状态下等待单个事件发生。
当事件发生时,它们会取消阻塞单个任务------取消阻塞的任务是等待事件的最高优先级任务。
事件组是FreeRTOS的另一个功能,允许将事件传递给任务。与队列和信号量不同:
事件组允许任务在"阻塞"状态下等待一个或多个事件的组合发生。
事件组在事件发生时取消阻塞正在等待同一事件或事件组合的所有任务。
事件组的这些独特属性使其可用于同步多个任务、向多个任务广播事件、允许任务在阻塞状态下等待一组事件中的任何一个发生,以及允许任务在阻塞状态下等待多个操作完成。
事件组还提供了减少应用程序使用的RAM的机会,因为通常可以用单个事件组替换许多二进制信号量。
事件组功能是可选的。要包含事件组功能,请将FreeRTOS源文件event_groups.c构建为项目的一部分。
事件组的特征
事件组、事件标志和事件位
事件"标志"是一个布尔值(1或0),用于指示事件是否发生。事件"组"是一组事件标志。
事件标志只能为1或0,允许将事件标志的状态存储在单个位中,并将事件组中所有事件标志的状况存储在单个变量中;事件组中每个事件标志的状态由EventBits_t类型的变量中的单个位表示。因此,事件标志也称为事件"位"。如果EventBits_t变量中的一个位设置为1,则该位表示的事件已经发生。如果EventBits_t变量中的某个位设置为0,则该位表示的事件尚未发生。
图71显示了单个事件标志如何映射到EventBits_t类型变量中的单个位。
例如,如果事件组的值为0x92(二进制1001 0010),则仅设置事件位1、4和7,因此仅发生了位1、5和7表示的事件。图72显示了一个EventBits_t类型的变量,该变量设置了事件位1、4和7,所有其他事件位均为空,使事件组的值为0x92。
由应用程序编写者为事件组中的各个位分配含义。
例如,应用程序编写者可能会创建一个事件组,然后:
将事件组中的位0定义为"已从网络收到消息"。
将事件组中的位1定义为"消息已准备好发送到网络上"。
将事件组中的第2位定义为"中止当前网络连接"。
事件组中的事件位数取决于FreeRTOSConfig.h 中的configUSE_16_BIT_TICKS编译时配置常数:
如果configUSE_16_BIT_TICKS为1,则每个事件组包含8个可用的事件位。
如果configUSE_16_BIT_TICKS为0,则每个事件组包含24个可用的事件位。
多任务访问
事件组本身就是对象,任何知道其存在的任务或ISR都可以访问它们。任意数量的任务都可以在同一事件组中设置位,任意数量的作业都可以从同一事件组中读取位。
使用事件组的一个实例
FreeRTOS+TCP TCP/IP栈的实现提供了一个实例,说明如何使用事件组同时简化设计并最大限度地减少资源使用。
TCP套接字必须响应许多不同的事件。事件的示例包括接受事件、绑定事件、读取事件和关闭事件。套接字在任何给定时间可以预期的事件取决于套接字的状态。例如,如果一个套接字已经创建,但尚未绑定到地址,那么它可能会收到绑定事件,但不会收到读取事件(如果没有地址,它就无法读取数据)。
FreeRTOS+TCP套接字的状态保存在名为FreeRTOS_socket_t的结构中。该结构包含一个事件组,该事件组为套接字必须处理的每个事件定义了一个事件位。FreeRTOS+TCP API调用该块以等待一个事件或一组事件,只需在事件组上进行块。
事件组还包含一个"中止"位,允许中止TCP连接,无论套接字当时正在等待哪个事件。
使用事件组进行事件管理
xEventGroupCreate()API函数
必须显式创建事件组才能使用。
事件组使用EventGroupHandle_t类型的变量引用
xEventGroupCreate()API函数用于创建一个事件组,并返回一个EventGroupHandle_t来引用它创建的事件组。
c
EventGroupHandle_t xEventGroupCreate( void );
返回值
如果返回NULL,则无法创建事件组,因为FreeRTOS没有足够的堆内存来分配事件组数据结构。
返回的非NULL值表示事件组已成功创建。返回的值应作为创建的事件组的句柄存储。
The xEventGroupSetBits() API 函数
xEventGroupSetBits()API函数设置事件组中的一个或多个位,通常用于通知任务正在设置的位所表示的事件已经发生。
注意:切勿从中断服务例程调用xEventGroupSetBits()。应使用中断安全版本xEventGroupSetBitsFromISR()来代替它。
c
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
xEventGroup
设置位的事件组的句柄。事件组句柄将从用于创建事件组的xEventGroupIreate()调用中返回。
uxBitsToSet
一个位掩码,指定事件组中要设置为1的一个或多个事件位。通过将事件组的现有值与uxBitsToSet中传递的值按位OR来更新事件组的值。
例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件组中的事件位3被设置(如果尚未设置),同时保持事件组中所有其他事件位不变。
返回值
返回xEventGroupSetBits()调用时事件组的值。请注意,返回的值不一定具有uxBitsToSet指定的位,因为这些位可能已被其他任务再次清除。
xEventGroupSetBitsFromISR()API函数
xEventGroupSetBitsFromISR()是xEventGroupSetBits()的中断安全版本。
给出信号量是一种确定性操作,因为预先知道给出信号量最多会导致一个任务离开阻塞状态。当在事件组中设置位时,事先不知道有多少任务将离开"阻塞"状态,因此在事件组中将位设置不是一个确定的操作。
FreeRTOS设计和实现标准不允许在中断服务例程内或中断被禁用时执行非确定性操作。
因此,xEventGroupSetBitsFromISR()不直接在中断服务例程中设置事件位,而是将操作推迟到RTOS守护进程任务。
c
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken );
xEventGroup
设置位的事件组的句柄。事件组句柄将从用于创建事件组的xEventGroupIreate()调用中返回。
uxBitsToSet
一个位掩码,指定事件组中要设置为1的一个或多个事件位。通过将事件组的现有值与uxBitsToSet中传递的值按位OR来更新事件组的值。
例如,将uxBitsToSet设置为0x05(二进制0101)将导致事件组中的事件位3和事件位0被设置(如果它们尚未设置),同时保持事件组中所有其他事件位不变。
pxHigherPriorityTaskWoken
xEventGroupSetBitsFromISR()不直接在中断服务例程中设置事件位,而是通过在计时器命令队列上发送命令将操作推迟到RTOS守护进程任务。如果守护进程任务处于"阻塞"状态,等待计时器命令队列上的数据可用,则写入计时器命令队列将导致守护进程任务离开"阻塞"态。如果守护进程任务的优先级高于当前正在执行的任务(被中断的任务)的优先级,那么在内部,xEventGroupSetBitsFromISR()将把*pxHigherPriorityTaskWoken设置为pdTRUE。
如果xEventGroupSetBitsFromISR()将此值设置为pdTRUE,则应在退出中断之前执行上下文切换。这将确保中断直接返回到守护进程任务,因为守护进程任务将是最高优先级的就绪状态任务。
返回值
有两种可能的返回值:
1.pdPASS
只有当数据成功发送到计时器命令队列时,才会返回pdPASS。
2.pdFALSE
如果由于队列已满而无法将"set bits"命令写入计时器命令队列,则将返回pdFALSE。
xEventGroupWaitBits()API函数
xEventGroupWaitBits()API函数允许任务读取事件组的值,并可选地在Blocked状态下等待事件组中的一个或多个事件位被设置(如果事件位尚未设置)。
c
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
调度程序用于确定任务是否将进入"阻塞"状态以及任务何时将离开"阻塞"状态的条件称为"取消阻塞条件"。解锁条件由uxBitsToWaitFor和xWaitForAllBits参数值的组合指定:
uxBitsToWaitFor指定要测试事件组中的哪些事件位
xWaitForAllBits指定是使用位OR测试还是位AND测试
如果在调用xEventGroupWaitBits()时满足任务的解锁条件,则任务将不会进入阻塞状态。
表45中提供了将导致任务进入阻塞状态或退出阻塞状态的条件示例。表45仅显示了事件组和uxBitsToWaitFor值的最低有效的四个二进制位,这两个值的其他位假定为零。
uxBitsToWaitFor和xWaitForAllBits参数的影响
现有事件组值 | uxBitsToWaitFor值 | xWaitForAllBits值 | 结果行为 |
---|---|---|---|
0000 | 0101 | pdFALSE | 调用任务将进入"阻塞"状态,因为事件组中既没有设置位0也没有设置位2,当事件组中设置位0或位2时,调用任务将离开"阻塞"模式。 |
0100 | 0101 | pdTRUE | 调用任务将进入"阻塞"状态,因为位0和位2都未在事件组中设置,当位0和2都在事件组内设置时,调用任务将离开"阻塞"模式。 |
0100 | 0110 | pdFALSE | 调用任务将不会进入阻塞状态,因为xWaitForAllBits为pdFALSE,并且uxBitsToWaitFor指定的两个位之一已在事件组中设置 |
0100 | 0110 | pdTRUE | 调用任务将进入阻塞状态,因为xWaitForAllBits pdTRUE,并且在事件组中只设置了uxBitsToWaitFor指定的两个位中的一个。当事件组中的位2和位3都设置时,任务将离开"阻塞"状态。 |
调用任务使用uxBitsToWaitFor参数指定要测试的位,在满足其解锁条件后,调用任务可能需要将这些位清除回零。
可以使用xEventGroupClearBits()API函数清除事件位,但如果出现以下情况,则使用该函数手动清除事件位将导致应用程序代码中出现竞争条件:
有多个任务使用同一事件组。
位在事件组中由不同的任务或中断服务例程设置。
提供xClearOneExit参数是为了避免这些潜在的竞争条件。如果xClearOneExit设置为pdTRUE,则调用任务将事件位的测试和清除视为原子操作(不受其他任务或中断的干扰)。
xEventGroup
包含正在读取的事件位的事件组的句柄。
事件组句柄将从用于创建事件组的xEventGroupIreate()调用中返回。
uxBitsToWaitFor
一个位掩码,指定要在事件组中测试的一个或多个事件位。
例如,如果调用任务希望等待事件组中的事件位0和/或事件位2被设置,则将uxBitsToWaitFor设置为0x05(二进制0101)。
xClearOnExit
如果调用任务的解锁条件已满足,并且xClearOneExit为
设置为pdTRUE,则在调用任务退出xEventGroupWaitBits()API函数之前,由uxBitsToWaitFor指定的事件位将在事件组中被清除回0。
如果xClearOnExit设置为pdFALSE,则xEventGroupWaitBits()API函数不会修改事件组中事件位的状态。
xWaitForAllBits
uxBitsToWaitFor参数指定要在事件组中测试的事件位。xWaitForAllBits指定当设置了uxBitsToWaitFor参数指定的一个或多个事件位时,或者仅当设置了ux BitsToWWaitFor参数指定了所有事件位时是否应从Blocked状态中删除调用任务。
如果xWaitForAllBits设置为pdFALSE,则进入阻塞的任务
当uxBitsToWaitFor指定的任何位被设置(或xTicksToWait参数指定的超时到期)时,等待其解除阻塞条件得到满足的状态将离开"已阻塞"状态。
如果xWaitForAllBits设置为pdTRUE,则进入"阻塞"状态以等待其解除阻塞条件得到满足的任务只有在设置了uxBitsToWaitFor指定的所有位时(或xTicksToWait参数指定的超时到期时)才会离开"阻塞"状态。
xTicksToWait
任务应保持在"已阻塞"状态以等待其解除阻塞条件得到满足的最长时间。
如果xTicksToWait为零,xEventGroupWaitBits()将立即返回,或
在调用xEventGroupWaitBits()时满足解锁条件。
块时间以滴答周期指定,因此它表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。
如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(没有超时)。
返回值
如果xEventGroupWaitBits()返回是因为满足调用任务的解锁条件,则返回的值是满足调用任务解锁条件时事件组的值(如果xClearOneExit为pdTRUE,则在自动清除任何位之前)。在这种情况下,返回的值也将满足解锁条件。
如果xEventGroupWaitBits()返回是因为xTicksToWait参数指定的块时间已过期,则返回的值是块时间过期时事件组的值。在这种情况下,返回的值将不符合解锁条件。
示例22。事件组实验
此示例演示了如何:
创建事件组。
从中断服务例程中设置事件组中的位。
从任务中设置事件组中的位。
阻塞事件组。
xEventGroupWaitBits()xWaitForAllBits参数的效果可以通过以下方式演示:首先将xWaitForAll Bits设置为pdFALSE执行示例,然后将xWaitFor All Bits设为pdTRUE执行示例。
事件位0和事件位1是从任务中设置的。事件位2是从中断服务例程中设置的。使用清单136中所示的#define语句为这三个位赋予描述性名称。
清单137显示了设置事件位0和事件位1的任务的实现。它位于一个循环中,重复设置一个比特,然后设置另一个,每次调用xEventGroupSetBits()之间有200毫秒的延迟。在设置每个位之前,会打印出一个字符串,以便在控制台中看到执行顺序。
清单138显示了在事件组中设置位2的中断服务例程的实现。同样,在设置位之前,会打印出一个字符串,以便在控制台中看到执行序列。然而,在这种情况下,由于控制台输出不应直接在中断服务例程中执行,因此使用xTimerPendFunctionCallFromISR()在RTOS守护进程任务的上下文中执行输出。
与前面的示例一样,中断服务例程由一个简单的周期性任务触发,该任务强制软件中断。在这个例子中,中断每500毫秒生成一次。
清单139显示了调用xEventGroupWaitBits对事件组进行阻塞的任务的实现。该任务为事件组中设置的每个位打印一个字符串。
xEventGroupWaitBits xClearOnExit参数设置为pdTRUE,因此导致xEventGroupWaitBits调用返回的一个或多个事件位将在xEventGroupWittBits返回之前自动清除
main()函数在启动调度程序之前创建事件组和任务。其实现见清单140。从事件组读取的任务的优先级高于向事件组写入的任务,确保每次满足读取任务的取消阻塞条件时,读取任务都会优先于写入任务。
将xEventGroupWaitBits()xWaitForAllBits参数设置为pdFALSE执行示例22时产生的输出如图73所示。在图73中,可以看到,因为xEventGroupWaitBits()调用中的xWaitForAllBits参数设置为 pdFALSE,从事件组读取的任务离开阻塞状态,并在每次设置任何事件位时立即执行。
将xEventGroupWaitBits()xWaitForAllBits参数设置为pdTRUE执行示例22时产生的输出如图74所示。在图74中可以看出,由于xWaitForAllBits参数设置为pdTRUE,因此从事件组读取的任务只有在设置了所有三个事件位后才会离开阻塞状态。
使用事件组进行任务同步
有时,应用程序的设计需要两个或多个任务相互同步。例如,考虑一个设计,其中任务a接收一个事件,然后将该事件所需的一些处理委托给其他三个任务:任务B、任务C和任务D。
如果任务A在任务B、C和D都完成处理前一个事件之前无法接收另一个事件,那么所有四个任务都需要相互同步。每个任务的同步点将在该任务完成其处理之后,并且在其他每个任务都完成相同的处理之前无法继续。只有在所有四个任务都达到同步点后,任务A才能接收另一个事件。
在FreeRTOS+TCP演示项目中,可以找到一个不那么抽象的例子,说明需要这种类型的任务同步。演示在两个任务之间共享一个TCP套接字;一个任务向套接字发送数据,另一个任务从同一套接字接收数据。在确定另一个任务不会再次尝试访问套接字之前,关闭TCP套接字对任何一个任务来说都是不安全的。如果这两个任务中的任何一个希望关闭套接字,那么它必须将其意图通知另一个任务,然后等待另一任务停止使用套接字,然后再继续。清单140中所示的伪代码演示了向希望关闭套接字的套接字发送数据的任务的场景。
下面所示的场景很简单,因为只有两个任务需要相互同步,但很容易看出,如果其他任务正在执行依赖于打开套接字的处理,场景将变得更加复杂,需要更多的任务来加入同步。
c
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( ... );
}
}
事件组可用于创建同步点:
必须参与同步的每个任务在事件组中都被分配了一个唯一的事件位。
每个任务在到达同步点时都会设置自己的事件位。
在设置了自己的事件位后,事件组上的每个任务块都会等待表示所有其他同步任务的事件位也被设置
但是,在这种情况下不能使用xEventGroupSetBits()和xEventGroupWaitBits(API函数。如果使用了它们,则位的设置(用于指示任务已达到其同步点)和位的测试(用于确定其他同步任务是否已达到其同步点)将作为两个单独的操作执行。要了解为什么这会是一个问题,请考虑任务A、任务B和任务C尝试使用事件组进行同步的场景:
1.任务A和任务B已经到达同步点,因此它们的事件位被设置在事件组中,并且它们处于阻塞状态,等待任务C的事件位也被设置。
2.任务C到达同步点,并使用xEventGroupSetBits()设置其在事件组中的位。一旦设置了任务C的位,任务A和任务B就离开"阻塞"状态,并清除所有三个事件位。
3.然后,任务C调用xEventGroupWaitBits()等待所有三个事件位都被设置,但到那时,所有三个活动位都已经被清除,任务A和任务B已经离开了各自的同步点,因此同步失败。
要成功使用事件组创建同步点,必须将事件位的设置和随后的事件位测试作为单个不可中断的操作执行。xEventGroupSync()API函数就是为此目的而提供的。
The xEventGroupSync() API 函数
xEventGroupSync()允许两个或多个任务使用一个事件组进行同步。该函数允许任务在事件组中设置一个或多个事件位,然后等待事件位的组合作为单个不可中断的操作设置在同一事件组中。
xEventGroupSync()uxBitsToWaitFor参数指定调用任务的解锁条件。如果xEventGroupSync()因满足解除阻塞条件而返回,则在xEventGroupSync()返回之前,uxBitsToWaitFor指定的事件位将被清除回零。
c
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );
xEventGroup
事件组的句柄,其中要设置事件位,然后进行测试。事件组句柄将从用于创建事件组的xEventGroupIreate()调用中返回。
uxBitsToSet
一个位掩码,指定事件组中要设置为1的一个或多个事件位。通过将事件组的现有值与uxBitsToSet中传递的值按位OR来更新事件组的值。
例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件位3被设置(如果尚未设置),而事件组中的所有其他事件位保持不变。
uxBitsToWaitFor
一个位掩码,指定要在事件组中测试的一个或多个事件位。
例如,如果调用任务希望等待事件组中的事件位0、1和2被设置,则将uxBitsToWaitFor设置为0x07(二进制 111)
xTicksToWait
塞任务应保持在"已阻塞"状态以等待其解除阻s条件得到满足的最长时间。
如果xTicksToWait为零,或者在调用xEventGroupSync()时满足解锁条件,xEventGroupSync()将立即返回。
块时间以滴答周期指定,因此它表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以刻度为单位的时间。
如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(没有超时)。
如果xEventGroupSync()返回是因为满足调用任务的解锁条件,则返回的值是满足调用任务解锁条件时(在任何位自动清除回零之前)事件组的值。在这种情况下,返回的值也将满足调用任务的取消阻塞条件。
如果xEventGroupSync()返回是因为xTicksToWait参数指定的块时间已过期,则返回的值是块时间过期时事件组的值。在这种情况下返回的值将不符合调用任务的取消阻塞条件。
例23。同步任务
示例23使用xEventGroupSync()同步单个任务实现的三个实例。task参数用于将任务调用xEventGroupSync()时将设置的事件位传递给每个实例。
该任务在调用xEventGroupSync()之前打印一条消息,并在对xEventGroupSync()的调用返回后再次打印。每条消息都包含一个时间戳。这允许在生成的输出中观察执行顺序。伪随机延迟用于防止所有任务同时到达同步点。
任务的实现如下
c
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" );
}
}
main()函数创建事件组,创建所有三个任务,然后启动调度程序。其实现见清单144。
执行示例23时产生的输出如图75所示。可以看出,即使每个任务在不同的(伪随机)时间到达同步点,每个任务也会在同一时间1(即最后一个任务到达同步点的时间)退出同步点。