10-FreeRTOS 通信机制(二)
在freeRTOS中,除了信号量、队列、互斥量这些通信方式,其实还提供了比较轻量的两种方式,这两种为事件和通知。为何说他们两种通信方式是轻量化的方式呢?这两种方式如何实现的?
事件
学习事件之前,先思考几个问题,什么是事件?事件和消息队列的差异在哪?事件在任务之间同步时可以进行阻塞吗?如果可以阻塞,那么阻塞是如何实现的?
定义
先来回答第一个问题,事件是任务之间进行通信的一种机制,主要用于任务之间的同步,相比对信号量的同步操作,事件具有以下优势。
-
占用的内存空间很小,信号量其实是依附在队列结构体中实现的,但是事件是一个单独的结构体,由uxEventBits和xTasksWaitingForBits组成;
typedef struct xEventGroupDefinition { EventBits_t uxEventBits; List_t xTasksWaitingForBits; /*< List of tasks waiting for a bit to be set. */ } EventGroup_t; -
事件可以实现一对多,多对多的同步(逻辑与和逻辑或操作),而信号量一般用于一对一的同步;
-
事件仅仅是用于同步,不能进行消息的传输,只与任务关链,事件相互独立;
-
事件没有排队这一说,而信号量支持累计,支持事件等待超时机制;
uxEventBits

uxEventBits划分为两部分,其中0-23位为事件组的标志位,1代表事件发生,0代表事件没有发生;其余的高位定位于控制位,BIT24定义退出时是否清除事件位,BIT25定义事件unlock的原因,BIT26代表是否为逻辑与,也就是等待所有的事件发生,BIT31代表是否在使用中。
接口函数
通过接口函数,要搞清楚事件是如何实现逻辑与和逻辑或的。
在使用事件之前,与其他通信方式一样,需要先创建事件,创建事件的本质操作是向内存中申请struct xEventGroupDefinition大小的内存空间。申请成功之后,对uxEventBits和xTasksWaitingForBits成员变量进行初始化。
1. 置位函数
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )
{
ListItem_t *pxListItem, *pxNext;
ListItem_t const *pxListEnd;
List_t *pxList;
EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;
EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
BaseType_t xMatchFound = pdFALSE;
//获取事件组等待链表
pxList = &( pxEventBits->xTasksWaitingForBits );
pxListEnd = listGET_END_MARKER( pxList ); /* 获取等待链表的尾结点 */
vTaskSuspendAll();
{
/* 获取等待链表的头结点 */
pxListItem = listGET_HEAD_ENTRY( pxList );
/* 设置事件标志位 */
pxEventBits->uxEventBits |= uxBitsToSet;
/* 检查该标志位设置后,是否有任务条件满足 */
while( pxListItem != pxListEnd )
{ /* 获取该结点等待的位 */
pxNext = listGET_NEXT( pxListItem );
uxBitsWaitedFor = listGET_LIST_ITEM_VALUE( pxListItem );
xMatchFound = pdFALSE;
/* 对uxBitsWaitedFor进行分割 */
uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES; /* 获取32位的高8位 */
uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES; /*获取32位的低24位 */
/* 逻辑或 */
if( ( uxControlBits & eventWAIT_FOR_ALL_BITS ) == ( EventBits_t ) 0 )
{
/* 有单个位满足要求 */
if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) != ( EventBits_t ) 0 )
{
xMatchFound = pdTRUE; /* 已经匹配到 */
}
} /* 逻辑与,所有位都要满足要求 */
else if( ( uxBitsWaitedFor & pxEventBits->uxEventBits ) == uxBitsWaitedFor )
{
/* All bits are set. */
xMatchFound = pdTRUE;
}
else
{
/* Need all bits to be set, but not all the bits were set. */
}
if( xMatchFound != pdFALSE ) /* 已经匹配到 */
{
/* The bits match. Should the bits be cleared on exit? 是否要清除*/
if( ( uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT ) != ( EventBits_t ) 0 )
{
uxBitsToClear |= uxBitsWaitedFor;
} /* 将满足事件条件的任务从等待链表移除,添加到就绪链表 */
(void)xTaskRemoveFromUnorderedEventList( pxListItem, pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET );
}
pxListItem = pxNext; /* 下一个链表结点 */
}
pxEventBits->uxEventBits &= ~uxBitsToClear; /* 在事件链表编译结束时,清除事件组置位 */
}
( void ) xTaskResumeAll();
return pxEventBits->uxEventBits;
}
置位函数的流程大致为首先是将要置的位写入uxEventBits,写入后判断xEventGroup中等待链表上的任务有没有条件符合解除等待的任务,这个过程会遍历整个等待链表中的每一个结点,如果有任务,则将任务从等待链表移除,然后加入到延时链表中。
注意在这个过程中会设置xItemValue的数值,具体如下:
newValue = uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET | taskEVENT_LIST_ITEM_VALUE_IN_USE
对应的中断版本的函数为xEventGroupSetBitsFromISR,让守护任务完成事件组的置位;
2. 等待事件函数
入口参数:
- xEventGroup : 事件句柄;
- uxBitsToWaitFor : 需要等待哪些位是1;
- xClearOnExit : 是否需要清除uxBitsToWaitFor所指的位;
- xWaitForAllBits: 是等待uxBitsToWaitFor的所有位还是某一位;
- xTicksToWait: 等待的超时时间;
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait )
{
EventGroup_t *pxEventBits = ( EventGroup_t * ) xEventGroup;
EventBits_t uxReturn, uxControlBits = 0;
BaseType_t xWaitConditionMet, xAlreadyYielded;
BaseType_t xTimeoutOccurred = pdFALSE;
vTaskSuspendAll();
{
const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits; /* 当前事件组的状态 */
/* Check to see if the wait condition is already met or not. */
xWaitConditionMet = prvTestWaitCondition( uxCurrentEventBits, uxBitsToWaitFor, xWaitForAllBits );
if( xWaitConditionMet != pdFALSE ) /* 满足置位 */
{
uxReturn = uxCurrentEventBits; /* 需要用户在收到标志位后判断返回数值 */
xTicksToWait = ( TickType_t ) 0;
/* Clear the wait bits if requested to do so. 需要清除置位 */
if( xClearOnExit != pdFALSE )
{
pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
}
} /* 不满足置位,且等待时间为0 */
else if( xTicksToWait == ( TickType_t ) 0 )
{
uxReturn = uxCurrentEventBits; /* 直接返回,同样需要用户判断返回数值 */
}
else /* 不满足置位,且等待时间不为0 */
{
if( xClearOnExit != pdFALSE ) /* 需要清除置位 */
{
uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;
}
if( xWaitForAllBits != pdFALSE ) /* 逻辑与 */
{
uxControlBits |= eventWAIT_FOR_ALL_BITS;
}
/* 添加到等待链表中 */
vTaskPlaceOnUnorderedEventList( &( pxEventBits->xTasksWaitingForBits ), ( uxBitsToWaitFor | uxControlBits ), xTicksToWait );
uxReturn = 0;
}
}
xAlreadyYielded = xTaskResumeAll();
if( xTicksToWait != ( TickType_t ) 0 ) //阻塞的时间不为0
{
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();/* 可能会发生任务切换 */
}
uxReturn = uxTaskResetEventItemValue(); /* 阻塞延时已过,从新调度 */
if( ( uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET ) == ( EventBits_t ) 0 )
{
taskENTER_CRITICAL();
{
/* The task timed out, just return the current event bit value. */
uxReturn = pxEventBits->uxEventBits;
/* 再次判断是否发生了调度 */
if( prvTestWaitCondition( uxReturn, uxBitsToWaitFor, xWaitForAllBits ) != pdFALSE )
{
if( xClearOnExit != pdFALSE ) /* 清除标志位,并返回 */
{
pxEventBits->uxEventBits &= ~uxBitsToWaitFor;
}
}
}
taskEXIT_CRITICAL();
xTimeoutOccurred = pdFALSE;
}
/* 返回事件所有标志 */
uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;
}
return uxReturn;
}
-
检查事件组中等待的事件是否满足
static BaseType_t prvTestWaitCondition( const EventBits_t uxCurrentEventBits, const EventBits_t uxBitsToWaitFor, const BaseType_t xWaitForAllBits ) { BaseType_t xWaitConditionMet = pdFALSE; if( xWaitForAllBits == pdFALSE ) /* 逻辑或 */ { if( ( uxCurrentEventBits & uxBitsToWaitFor ) != ( EventBits_t ) 0 ) { xWaitConditionMet = pdTRUE; } } else { /* 逻辑与 */ if( ( uxCurrentEventBits & uxBitsToWaitFor ) == uxBitsToWaitFor ) { xWaitConditionMet = pdTRUE; } } return xWaitConditionMet; } -
vTaskPlaceOnUnorderedEventList函数首先会设置TCB->xEventListItem->xItemValue数值,然后将任务TCB->xEventListItem挂载到事件的等待链表的末尾,最后将任务添加到延时等待链表中。
-
xItemValue的数值
uxBitsToWaitFor | uxControlBits | taskEVENT_LIST_ITEM_VALUE_IN_USEuxControlBits位会依据是否等待所有位和退出时是否清除,进行eventCLEAR_EVENTS_ON_EXIT_BIT/eventWAIT_FOR_ALL_BITS的与操作。
-
设置xItemValue数值的目的在于告诉置位函数,我这个任务在等待什么事件,
-
-
对应的中断版本为xEventGroupClearBitsFromISR,通过守护任务调用xEventGroupWaitBits函数;
通知
定义
任务的控制块中提供了一个32位的通知值,该数值可以代替二值信号量、计数信号量、事件组和长度为1的队列。但是任务通知存在必须指定接收通知的任务,只有等待通知的任务可以阻塞,发送通知的任务不会阻塞。
typedef struct tskTaskControlBlock
{
/* 省略部分函数 */
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState; /* 用于比奥是任务是否在等待通知 */
#endif
/* 省略部分函数 */
}
关键代码
任务通知主要围绕ulNotifiedValue展开,在发送通知方面包含7个函数,接收侧有2个函数。函数被划分为中断版本和任务版本。发送测除了通用的xTaskNotify函数,还设计了专门用于二值信号量和计数信号量的收发函数。
发送通知的函数
-
xTaskGenericNotify 通用
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) { TCB_t * pxTCB; BaseType_t xReturn = pdPASS; uint8_t ucOriginalNotifyState; pxTCB = ( TCB_t * ) xTaskToNotify; taskENTER_CRITICAL(); { if( pulPreviousNotificationValue != NULL ) { /* 保存任务原本的通知数值 */ *pulPreviousNotificationValue = pxTCB->ulNotifiedValue; } ucOriginalNotifyState = pxTCB->ucNotifyState; /* 获取任务的通知状态 */ pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED; /* 更新状态为收到任务通知 */ switch( eAction ) { case eSetBits : /* 新值 | 旧值 可代替事件组 */ pxTCB->ulNotifiedValue |= ulValue; break; case eIncrement : ( pxTCB->ulNotifiedValue )++; /* 通知数值++ 可代替信号量通信 */ break; case eSetValueWithOverwrite : /* 覆盖写入,可代替xQueueoverwrite*/ pxTCB->ulNotifiedValue = ulValue; break; case eSetValueWithoutOverwrite : if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED ) { pxTCB->ulNotifiedValue = ulValue; /* 代替长度为1的队列 */ } else { /* The value could not be written to the task. 上一次通知的任务没有取走 */ xReturn = pdFAIL; } break; case eNoAction: /* The task is being notified without its notify value being updated. */ break; } if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ) /* 如果任务在等待通知 */ { /* 将任务解除等待,加入就绪链表 */ ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); prvAddTaskToReadyList( pxTCB ); if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ) /* 高优先级进行一次切换 */ { taskYIELD_IF_USING_PREEMPTION(); } } } taskEXIT_CRITICAL(); return xReturn; }- 入口参数xTaskToNotify代表被通知的任务句柄、ulValue代表通知的数值、eAction指明更新通知值的方式、pulPreviousNotificationValue任务原本的通知数值;
- 发送任务通知就是根据eAction的数值对pxTCB->ulNotifiedValue做不同的操作,然后检查是否等待通知,如果在等待通知,就解除等待,加入就绪链表,进行调度;
-
xTaskNotifyGive 二值信号量和计数信号量的代替
#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL ) -
vTaskNotifyGiveFromISR 任务通知中断版本,这个只是对ulNotifiedValue进行加加;
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken ) { TCB_t * pxTCB; uint8_t ucOriginalNotifyState; UBaseType_t uxSavedInterruptStatus; portASSERT_IF_INTERRUPT_PRIORITY_INVALID(); pxTCB = ( TCB_t * ) xTaskToNotify; uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); { ucOriginalNotifyState = pxTCB->ucNotifyState; pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED; ( pxTCB->ulNotifiedValue )++; if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ) /* 在等待通知 */ { /* 调度器在允许中 */ if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); prvAddTaskToReadyList( pxTCB ); /* 添加到就绪链表 */ } else { /* 添加到就绪挂起链表 */ vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); } if( pxTCB->uxPriority > pxCurrentTCB->uxPriority ) { if( pxHigherPriorityTaskWoken != NULL ) { *pxHigherPriorityTaskWoken = pdTRUE; /* 标记中断退出后是否要进行切换 */ } else { /* 在系统节拍中断服务函数中切换 */ xYieldPending = pdTRUE; } } } } portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); }- pxHigherPriorityTaskWoken在使用之前要初始化为pdFALSE;
-
xTaskNotify 用于向指定任务发送一个通知数值,可以指定通知方式
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL ) -
xTaskNotifyFromISR xTaskNotify的中断版本
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) ) -
xTaskGenericNotifyFromISR 在中断中发送通知的函数;
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) -
xTaskNotifyAndQuery 带上次通知值
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) ) -
xTaskNotifyAndQueryFromISR 中断版本的带上一次通知值
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
获取任务通知
获取任务通知函数有两个,分别是ulTaskNotifyTake和xTaskNotifyWait。ulTaskNotifyTake是代替信号量设计,与xTaskNotifyGive()、vTaskNotifyGiveFromISR()成对使用;xTaskNotifyWait是通用版本。
-
ulTaskNotifyTake
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) { uint32_t ulReturn; taskENTER_CRITICAL(); { if( pxCurrentTCB->ulNotifiedValue == 0UL ) /* 通知数值为0,则阻塞任务 */ { pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION; if( xTicksToWait > ( TickType_t ) 0 ) { prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); portYIELD_WITHIN_API(); } } } taskEXIT_CRITICAL(); taskENTER_CRITICAL(); /*1.获取到了任务 2.阻塞超时 */ { ulReturn = pxCurrentTCB->ulNotifiedValue; if( ulReturn != 0UL ) { if( xClearCountOnExit != pdFALSE ) { pxCurrentTCB->ulNotifiedValue = 0UL; /* 类似于二值信号量 */ } else { pxCurrentTCB->ulNotifiedValue = ulReturn - 1; /* 类似于计数信号量 */ } } pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION; } taskEXIT_CRITICAL(); return ulReturn; } -
xTaskNotifyWait 获取任务通知
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ) { BaseType_t xReturn; taskENTER_CRITICAL(); { /* 没有收到通知,进行阻塞 */ if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED ) { pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry; pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION; if( xTicksToWait > ( TickType_t ) 0 ) { prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); portYIELD_WITHIN_API(); /* 调度切换 */ } } } taskEXIT_CRITICAL(); /* 再一次回到该函数 */ taskENTER_CRITICAL(); { if( pulNotificationValue != NULL ) { *pulNotificationValue = pxCurrentTCB->ulNotifiedValue; /* 返回当前任务通知值 */ } if( pxCurrentTCB->ucNotifyState == taskWAITING_NOTIFICATION ) { xReturn = pdFALSE; /* 阻塞超时 */ } else { pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit; /* 将通知值的某些位清零 */ xReturn = pdTRUE; } pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION; } taskEXIT_CRITICAL(); return xReturn; }
总结
现在来回答下开始的问题,为什么说他们两种是轻量化的方式,这个轻量主要指的是内存空间的占用,事件通过两个成员变量实现,而通知更加简单,通过任务的TCB中的成员变量实现,相比于消息队列中申请的内存空间,是轻量化的。再来看如何实现的,以及在使用的过程中有什么注意的地方。实现的话,事件通过一个24位的事件组来完成,可以用于一对多或多对多,一个任务可以等待多个事件,多个任务可以同时向这个事件写入数值。通知的话使用一个变量实现,可以完成信号量和互斥量的操作,对这个变量进行加加就是计数信号量等,但是有一点,任务通知仅仅是对于任务来讲的,通常是一对一的实现,任务的接受者可以阻塞,但是任务的发送者无法阻塞。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)