FreeRTOS学习-任务通知(Task Notification)

1. 简介

任务通知本质上就是一种进程间通信机制。之前的文章介绍的消息队列、事件组、信号量等都是一种间接的通信方式,而任务通知则是更加直接的方式,允许两个任务(或中断和任务)之间直接通信。

2. 任务通知的优势和局限性

优势:

  • 直接通信的方式性能更好;
  • 更少的内存占用,目前的实现每个任务只有5字节的占用。

局限性:

  • 无法给中断响应函数发送任务通知;
  • 难以支持超过一个接收者的情况,无法广播;
  • 传输的数据有限;
  • 不支持类似于队列一样的阻塞写。

3. 任务通知的使用

3.1. 发送通知

发送通知的函数原型:需要设置configUSE_TASK_NOTIFICATIONS = 1

BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue ) PRIVILEGED_FUNCTION;
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL)
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )

开启了configUSE_TASK_NOTIFICATION后, 每个任务则会记录其私有的notification value,这是一个uint32_t的整数。与FreeRTOS的其他IPC不同,它将直接把事件发送给指定的任务。在提高效率的同时,它牺牲了灵活性,也即,它能实现的行为有限:在eNotifyAction中定义了该函数能做的一些操作,例如更新、重写、增加任务的notification value,如下所示:

  • eSetBits:使用ulValue与任务当前的notification value取“或”;这时,该函数永远返回pdPASS
  • eIncreament:将任务当前的notification value自增1,ulValue将被忽略;此时,该函数永远返回pdPASS
  • eSetValueWithOverwrite:用ulValue覆盖任务当前的notification value,无论此时是否还有别的notification未被处理。此时,函数永远返回pdPASS
  • eSetValueWithoutOverwrite:如果此时还有别的notification未被处理,那么该函数直接返回pdFAIL;否则用ulValue覆盖当前的notification value。
  • eNoAction:不对notification value进行任何操作,此时函数永远返回pdPASS

当一个notification发送到某个任务后,该notification会在将来的某个时候被获取,即当接收者调用了xTaskNotifyWait()ulTaskNotifyTake()时。而如果在notification到达之前,接受者就已经处于等待notifcation的状态(Blocked),notification抵达时,任务将会变成Ready状态,同时清掉notification。

您可能在忧虑应该如何获取被覆盖之前的notification value?pulPreviousNotificationValue为你排忧解难。它记录了被覆盖前的notification value。

这两个接口都是通过宏定义,最终调用xTaskGenericNotify()实现,下面我们来看看该函数的具体实现,其活动图如下:

发送通知的活动图
该函数大致的实现流程与消息队列相似。比较重要的区别是,这里没有阻塞的行为,无论是否成功唤醒(除非唤醒的更高优先级的任务),都会直接返回。

另外,FreeRTOS还提供了中断上下文版本的接口:

BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )

与任务上下文中的版本类似,ISR的版本最终通过xTaskGenericNotifyFromISR()实现,下面就来看看其实现:

{
    初始化`xReturn = pdPASS`。

    参数校验:Assert `xTaskToNotify != NULL`

    检查中断的优先级是否满足要求(`portASSERT_IF_INTERRUPT_PRIORITY_INVALID()`)。

    获取TCB(`pxTCB = ( TCB_t * ) xTaskToNotify`)。

    进入临界区(`uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()`)。

    如果任务通知接收变量不为空(`pulPreviousNotificationValue != NULL`),设置当前的任务通知(`*pulPreviousNotificationValue = pxTCB->ulNotifiedValue`)。

    获取当前任务通知的标志(`ucOriginalNotifyState = pxTCB->ucNotifyState`)。
    根据动作类型选择具体操作(`switch ( eAction )`):
    {
        case eSetBits:设置对应的位图(`pxTCB->ulNotifiedValue |= ulValue`)
            break;
        case eIncrement:增加任务的通知值(`pxTCB->ulNotifiedValue ++`)。
            break;
        case eSetValueWithOverwrite:直接设置对应的通知值(`pxTCB->ulNotifiedValue = ulValue`)。
            break;
        case eSetValueWithoutOverWrite:
        {
            如果原本任务通知状态不是已接收(`ucOriginalNotifyState != taskNOTIFICATION_RECEIVED`),则更新值`pxTCB->ulNotifiedValue = ulValue`。
            否则,设置返回值为失败`xReturn = pdFAIL`。
        }
            break;
        case eNoAction:不做任何操作。
            break;
    }

    如果原本任务通知状态为等待(`ucOriginalNotifyState == taskWAITING_NOTIFICATION`);
    {
        条件校验:确保任务此时不在任何事件任务队列中(`configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL)`)。

        如果调度器未被挂起(`uxSchedulerSuspended == pdFALSE`):
        {
            将任务从Delayed任务队列中删除(`uxListRemove( &( pxTCB->xStateListItem ) )`)。
            将任务加入到Ready任务队列(`prvAddTaskToReadyList( pxTCB )`)。
        }
        否则
        {
            将任务将入到PendingReady队列(`vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) )`)。
        }

        该任务优先级较高(`pxTCB->uxPriority > pxCurrentTCB->uxPriority`),设置更高优先级任务唤醒标志(`*pxHigherPriorityTaskwoken = pdTRUE`),否则设置调度挂起标志(`xYieldPending = pdTRUE`)。
    }

    恢复临界区(`portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus )`)。

    返回`xReturn`。
}

3.2. 等待任务通知

等待通知的函数原型:需要设置configUSE_TASK_NOTIFICATIONS = 1

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

如果此时没有notification等待被捕获,调用者会进入Blocked状态;该函数的参数需要特别关注一下,因为使用不当会造成意外的结果。首先,因为一个任务可以有多个notification被挂起,因此我们无法预测当前任务的notification value是多少,这时需要一些特殊的前处理和后处理来帮我们确保这个事情。

  • 前处理:在设置pulNotificationValue,将当前的notification value与~ulBitsToClearOnEntry相与,也即清除notification value中ulBitsToClearOnEntry所指定的位。
  • 后处理:在设置pulNotificationValue,将当前的notification value与~ulBitsToClearOnExit相与,也即清除notification value中ulBitsToClearOnExit所指定的位。有了后处理,那就可以用notification实现类似于清理外部中断位一样的功能。

有的时候我们只是想看一下是不是有notification在挂起,并不希望无限等待事件的发生,此时,则可以通过xTicksToWait参数来申明调用者希望等待的最长时间。但由于xTicksToWait是以tick作为时间度量单位的,通常它还与pdMS_TO_TICKS( value_in_ms )一起使用,也就是将毫秒值转换成tick值。

如果在指定的时间内,收到了notification,函数则会返回pdPASS,否则返回pdFAIL

该函数实现的活动图如下:

等待任务通知

3.3. 用通知实现信号量功能

使用notification可以实现类似于信号量一样的功能,FreeRTOS还提供了这样的一个接口(宏):

#define xTaskNotifyGive( xTaskToNofity ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL)

可以看到,它只是对xTaskGenericNotify()的一个简单封装,用于实现信号量的归还操作。

Give函数还有中断上下文的版本:

void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;

其实现如下:

{
    参数校验:Assert `xTaskToNotify != NULL`。

    中断优先级条件检查(`portASSERT_IF_INTERRUPT_PRIORITY_INVALID()`)。

    获取任务TCB(`pxTCB = ( TCB_t *) xTaskToNotify`)。

    进入临界区(`uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR()`)。

    缓存当前的任务通知状态(`ucOriginalNotifyState = pxTCB->ucNotifyState`)。

    设置任务通知状态为已接收(`pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED`)。

    增加任务通知的值(`pxTCB->ulNotifiedValue++`)。

    如果原本任务通知状态为等待(`ucOriginalNotifyState = taskWAITING_NOTIFICATION`):
    {
        确保任务不在事件队列中(`configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL`)。

        如果调度器未挂起(`uxSchedulerSuspended == pdFALSE`):
        {
            将任务从Delayed任务队列中删除(`uxListRemove( &( pxTCB->xStateListItem ) )`)。
            将任务加入到Ready任务队列(`prvAddTaskToReadyList( pxTCB )`)。
        }
        否则,即任务调度器处于挂起状态,则讲任务加入到PendingReady任务队列(`vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) )`)。

        如果被唤醒的任务优先级较高(`pxTCB->uxPriority > pxCurrentTCB->uxPriority`):
        {
            如果希望获取更高优先级标志(`pxHigherPriorityTaskWoken != NULL`),设置标志(`*pxHigherPriorityTaskWoken = pdTRUE`)。
            否则,直接设置调度挂起标志(`xYieldPending = pdTRUE`)。
        }
    }

    恢复临界区(`portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus )`)。
}

而与之对应的获取信号量的函数是:

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

该函数的后处理操作与xTaskNotifyWait()不太一样。这里取决于使用者希望将notification当做二值信号量或者是计数信号量使用。当xClearCountOnExitpdTRUE时,则作为二值信号量使用,因为后处理会将notification value清零;反之,作为计数信号量使用,后处理会将notification value减一。

返回值为notification value的值(执行后处理之前的值)。

下面看看其具体的实现:

{
    进入临界区(`taskENTER_CRITICAL()`)。

    如果当前的任务通知为0(`pxCurrentTCB->ulNotifiedValue == 0`):
    {
        设置任务通知的状态为等待(`pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION`)。

        如果调用者愿意等待(`xTicksToWait > 0`):
        {
            将任务将入到Delayed任务队列(`prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE )`)。

            发起一次调度(`portYIELD_WITHIN_API()`)。
        }
    }

    退出临界区(`taskEXIT_CRITICAL()`)。

    进入临界区(`taskENTER_CRITICAL()`)。

    暂存当前任务通知用于返回(`ulReturn = pxCurrentTCB->ulNotifiedValue`)。

    如果当前任务通知值不为0(`ulReturn != 0`):
    {
        如果设置了退出清除标志(`xClearCountOnExit != pdFALSE`),则清除任务通知(`pxCurrentTCB->ulNotifiedValue = 0`);
        否则,仅将任务通知减一(`pxCurrentTCB->ulNotifiedValue = ulReturn - 1`)。

        重置任务通知状态(`pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION`)。
    }

    退出临界区(`taskEXIT_CRITICAL()`)。

    返回通知信息`ulReturn`。
}

3.4. 复位通知信息

清理notification的接收状态的函数原型:

BaseType_t xTaskNotifyStateClear( TaskHandle_t xTask);

需要说明一下该函数的返回值:如果调用该函数时,任务已经接收到了notification,该函数会将任务的状态设置为默认状态,且该函数则返回pdTRUE;否则函数将直接返回pdFALSE

其具体实现如下:

{
    获取任务TCB(`pxTCB = prvGetTCBFromHandle( xTask )`)。

    进入临界区(`taskENTER_CRITICAL()`)。

    如果任务通知状态为已接收(`pxTCB->ucNotifyState == taskNOTIFICATION_RECEIVED`):
    {
        重置任务通知状态(`pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION`)。
        设置返回值(`xReturn = pdPASS`)。
    }
    否则,设置返回值(`xReturn = pdFAIL`)。

    退出临界区(`taskEXIT_CRITICAL()`)。

    返回`xReturn`。
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值