• FreeRTOS 任务与调度器(2)


     在上一篇我们介绍了FreeRTOS任务的一些基本操作和功能,今天我们会介绍一个很好很强大的功能——任务通知

    任务通知可以在不同任务之间传递信息,它可以取代二值信号量、计数信号量、事件标志组、深度为1的消息队列等功能,因为它更快,占用RAM更少,是FreeRTOS自8.2以来推出的重大改进功能。

    一、任务通知相关变量

    1.1、TCB中的通知相关成员

    FreeRTOS每个任务都有一个通知指示值,和一个32位通知值:

    任务数据结构(TCB)中与队列通知相关的成员

    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue;
        volatile eNotifyValue eNotifyState;
    #endif
    • ulNotifiedValue就是任务中的通知值,任务通知实际上就是围绕这个变量作文章,下面会以“通知值”来代替这个成员变量,
    • eNotifyState用来标志任务是否在等待通知,它有以下3种情况
    eNotified 任务已经被通知 带发送通知功能的函数都会首先把eNotifyState设置为eNotified,表示任务已经被通知
    eWaitingNotification 任务正在等待通知 接收通知功能的函数会首先把eNotifyState设置为eWaitingNotification,表示任务已经阻塞了正在等待通知
    eNotWaitingNotification 空状态 表示任务即没有收到新的通知,也没有正在等待通知,接收通知功能函数在接收到通知处理后,会把eNotifyState设置为eWaitingNotification

    根据上一节中的TCB我们的精简,我们现在为TCB补上接下来会用到新的成员:

    typedef struct tskTaskControlBlock
    {
        volatile StackType_t    *pxTopOfStack; /*任务堆栈栈顶*/
    
        ListItem_t    xGenericListItem;    /*任务状态列表项项引用的列表,指示任务状态(准备态、阻塞态、挂起态)*/ 
        ListItem_t    xEventListItem;    /*状态列表项*/
        UBaseType_t    uxPriority;    /*任务优先级*/
        StackType_t    *pxStack;    /*任务堆栈起始地址*/
        char    pcTaskName[ configMAX_TASK_NAME_LEN ];/*任务名字*/
    
        volatile uint32_t ulNotifiedValue; /*任务通知值*/
        volatile eNotifyValue eNotifyState; /*通知状态标志*/
    
    } tskTCB;

    二、任务通知API函数

    2.1、发送通知

    xTaskNotifyGive() 发送通知,通知值设定为自增方式(每次调用通知值加1)
    vTaskNotifyGiveFromISR() 上面函数的中断版本
    xTaskNotify()  发送通知,可以自定义通知方式
     xTaskNotifyFromISR()  上面函数的中断版本
     xTaskNotifyAndQuery()  发送通知,自定义通知方式,并且读出上一次的通知值
     xTaskNotifyAndQueryFromISR()  上面函数的中断版本

      其中有5个API是宏,它们关系如下:

      非中断版本:

    #define xTaskNotify( xTaskToNotify, ulValue, eAction )  xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
    #define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue )  xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
    #define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )

      中断版本:

    #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 ) )

      

      可以发现,实际上他们底层只有两个函数,

      •   xTaskGenericNotify()
      •   xTaskGenericNotifyFromISR()

      再加上一个直接指向顶层的函数

      •   vTaskNotifyGiveFromISR()


      接下来我们直接看这3个函数,会更便于理解队列通知以及使用

     

    2.1.1生成通知信号函数xTaskGenericNotify():

    函数原型:

      BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction,   uint32_t *pulPreviousNotificationValue )

     输入参数:

    xTaskToNotify 被通知的任务的句柄
    ulValue 输入值
    eAction 进行通知方式
    *pulPreviousNotificationValue 这个任务上一次的通知值

      

      其中参数eAction最为重要,它决定了我们以什么方式发送通知,我们查看它是一个eNotifyAction类型的结构体,查看这个结构体,可以留意到发送通知的方式分为以下5种:

    typedef enum
    {
        eNoAction = 0,    
        eSetBits,
        eIncrement,
        eSetValueWithOverwrite,
        eSetValueWithoutOverwrite
    } eNotifyAction;

    现在我们来截取xTaskGenericNotifyFromISR()一段操作通知值的代码,看看这5种信号是怎么改变通知值的:

                switch( eAction )
                {
                    case eSetBits    :
                        pxTCB->ulNotifiedValue |= ulValue;
                        break;
    
                    case eIncrement    :
                        ( pxTCB->ulNotifiedValue )++;
                        break;
    
                    case eSetValueWithOverwrite    :
                        pxTCB->ulNotifiedValue = ulValue;
                        break;
    
                    case eSetValueWithoutOverwrite :
                        if( eOriginalNotifyState != eNotified )
                        {
                            pxTCB->ulNotifiedValue = ulValue;
                        }
                        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;
                }

    源码分析(展开折叠查看)

    这个函数主要做了3件事:

    1. 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
    2. 根据需求更新TCB中的通知值ulNotifiedValue
    3. 解除目标任务的阻塞状态
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
    
        BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
        {
            /* 
            xTaskToNotify:被通知的任务句柄
            ulValue: 更新的通知值
            eAction: 枚举类型,指明更新通知值的方法,
            *pulPreviousNotificationValue:用于获取上次的通知值
            */
        TCB_t * pxTCB;
        eNotifyValue eOriginalNotifyState;
        BaseType_t xReturn = pdPASS;
    
            configASSERT( xTaskToNotify );
            pxTCB = ( TCB_t * ) xTaskToNotify;
    
            taskENTER_CRITICAL();
            {
                if( pulPreviousNotificationValue != NULL )
                {
                    *pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
                }
                /*获取上一次的通知状态,保存在局部变量eOriginalNotifyState中,稍后覆盖方法会用到*/
                eOriginalNotifyState = pxTCB->eNotifyState;
                
                /*更新TCB的通知状态为eNotified(已通知状态)*/                    /*【1】*/
                pxTCB->eNotifyState = eNotified;
                
                /*更新TCB的通知值(pxTCB->ulNotifiedValue),根据eAction的不同,达到传输不同种类通知的目的*/            /*【2】*/
                switch( eAction )
                {
                    case eSetBits    :
                        pxTCB->ulNotifiedValue |= ulValue;
                        break;
    
                    case eIncrement    :
                        ( pxTCB->ulNotifiedValue )++;
                        break;
    
                    case eSetValueWithOverwrite    :
                        pxTCB->ulNotifiedValue = ulValue;
                        break;
    
                    case eSetValueWithoutOverwrite :
                        if( eOriginalNotifyState != eNotified )
                        {
                            pxTCB->ulNotifiedValue = ulValue;
                        }
                        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;
                }
    
                traceTASK_NOTIFY();
    
                /* If the task is in the blocked state specifically to wait for a
                notification then unblock it now. */
                /* 如果目标任务是阻塞状态,特别是如果在等待通知,那么解除阻塞 */
                if( eOriginalNotifyState == eWaitingNotification )                  /*【3】*/
                {
                    ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
                    prvAddTaskToReadyList( pxTCB );
    
                    /* The task should not have been on an event list. */
                    /* 任务不应该已经添加进事件列表 */
                    configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
    
                    #if( configUSE_TICKLESS_IDLE != 0 )
                    {
                        /* If a task is blocked waiting for a notification then
                        xNextTaskUnblockTime might be set to the blocked task's time
                        out time.  If the task is unblocked for a reason other than
                        a timeout xNextTaskUnblockTime is normally left unchanged,
                        because it will automatically get reset to a new value when
                        the tick count equals xNextTaskUnblockTime.  However if
                        tickless idling is used it might be more important to enter
                        sleep mode at the earliest possible time - so reset
                        xNextTaskUnblockTime here to ensure it is updated at the
                        earliest possible time. */
                        /* 如果一个任务阻塞等待通知,那么xNextTaskUnblockTime应该设置为阻塞任务时间外的时间。
                        如果任务因为一些原因(除了一个超时)被解除阻塞,xNextTaskUnblockTime通常保持不变。
                        因为当滴答计数器等于xNextTaskUnblockTime的时候,它会被重置为一个新的值。
                        无论如何,如果tickless idling 被使用,它可能是首先进入睡眠模式,
                        所以在这里重置xNextTaskUnblockTime来确保它被更新*/
                        prvResetNextTaskUnblockTime();
                    }
                    #endif
    
                    if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
                    {
                        /* The notified task has a priority above the currently
                        executing task so a yield is required. */
                        /* 被通知的任务优先级超过了当前运行中的任务,所以需要进行切换(切换到被通知的任务) */
                        taskYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            taskEXIT_CRITICAL();
    
            return xReturn;
        }
    
    #endif /* configUSE_TASK_NOTIFICATIONS */
    View Code

     

    2.1.2、生成通知信号函数中断版本 xTaskGenericNotifyFromISR()

     函数原型:

      BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )

    作为xTaskGenericNotify()的中断版本,xTaskGenericNotifyFromISR()中加了一些中段相关处理,我们可以看到,他们输入参数都相同(增加一个pxHigherPriorityTaskWoken参数用于指示执行期间是否有任务切换发生)。
    所以这个函数实际上和xTaskGenericNotify()的操作相同。

    1. 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
    2. 根据需求更新TCB中的通知值ulNotifiedValue
    3. 解除目标任务的阻塞状态

    2.1.3、一个自增通知的中断定制版 vTaskNotifyGiveFromISR():

    函数原型:

      void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )

    这个函数是xTaskGenericNotifyFromISR()的压缩定制版,去掉了3个输入参数ulValue、eAction、pulPreviousNotificationValue和对应这3个参数相关的处理程序,因为它是特别为自增型通知定制的,与xTaskGenericNotifyFromISR()这个函数提高了效率,大概FreeRTOS作者认为自增通知是常用的通知类型,所以特意写了这个优化版本的函数。

    所以这个函数实际上也和xTaskGenericNotify()的操作相同

    源码分析:

    1. 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
    2. 根据需求更新TCB中的通知值ulNotifiedValue(自增)
    3. 解除目标任务的阻塞状态
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
    
        void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
        {
        TCB_t * pxTCB;
        eNotifyValue eOriginalNotifyState;
        UBaseType_t uxSavedInterruptStatus;
    
            configASSERT( xTaskToNotify );
    
            /* RTOS ports that support interrupt nesting have the concept of a
            maximum    system call (or maximum API call) interrupt priority.
            Interrupts that are    above the maximum system call priority are keep
            permanently enabled, even when the RTOS kernel is in a critical section,
            but cannot make any calls to FreeRTOS API functions.  If configASSERT()
            is defined in FreeRTOSConfig.h then
            portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
            failure if a FreeRTOS API function is called from an interrupt that has
            been assigned a priority above the configured maximum system call
            priority.  Only FreeRTOS functions that end in FromISR can be called
            from interrupts    that have been assigned a priority at or (logically)
            below the maximum system call interrupt priority.  FreeRTOS maintains a
            separate interrupt safe API to ensure interrupt entry is as fast and as
            simple as possible.  More information (albeit Cortex-M specific) is
            provided on the following link:
            http://www.freertos.org/RTOS-Cortex-M3-M4.html */
            
            portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
    
            pxTCB = ( TCB_t * ) xTaskToNotify;
    
            uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
            {
                eOriginalNotifyState = pxTCB->eNotifyState;
                pxTCB->eNotifyState = eNotified;                            /*【1】*/
    
                /* 'Giving' is equivalent to incrementing a count in a counting
                semaphore. */
                /* 给予是等价于在计数信号量中增加一个计数 */
                ( pxTCB->ulNotifiedValue )++;                               /*【2】*/
                
                traceTASK_NOTIFY_GIVE_FROM_ISR();
    
                /* If the task is in the blocked state specifically to wait for a
                notification then unblock it now. */
                /* 如果任务在阻塞状态,明确地等待一个通知,然后马上解除阻塞*/
                if( eOriginalNotifyState == eWaitingNotification )          /*【3】*/
                {
                    /* The task should not have been on an event list. */
                    /* 任务不应该已经加入事件列表了 */
                    configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
    
                    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
                    {
                        ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
                        prvAddTaskToReadyList( pxTCB );
                    }
                    else
                    {
                        /* The delayed and ready lists cannot be accessed, so hold
                        this task pending until the scheduler is resumed. */
                        /* 延时和准备列表无法被访问,所以保持这个任务挂起,知道调度器被恢复 */
                        vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
                    }
                    
                    if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
                    {
                        /* The notified task has a priority above the currently
                        executing task so a yield is required. */
                        /* 通知任务已经比当前执行任务更高,所以需要进行切换 */
                        if( pxHigherPriorityTaskWoken != NULL )
                        {
                            *pxHigherPriorityTaskWoken = pdTRUE;
                        }
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
            }
            portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
        }
    
    #endif /* configUSE_TASK_NOTIFICATIONS */
    View Code

     接下来我们回到API函数,看看这6个API的功能,以及它们调用的是哪个底层函数:

     2.2接收通知

    ulTaskNotifyTake()  提取通知  

    适用于二值通知(eNoAction)和自增通知
    (eIncrement)

     xTaskNotifyWait()  等待通知  适用所有通知,但不附带自增通知的自减功能

     

    2.2.1、ulTaskNotifyTake()

    函数原型:

      uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )


    输入参数:

    BaseType_t xClearCountOnExit 退出时是否清除
    TickType_t xTicksToWait 最大等待时间
    • xClearCountOnExit:这个参数可以传入pdTRUE或者pdFALSE

      

    • xTicksToWait:在等待通知的时候,任务会进入阻塞状态,任务进入阻塞状态的最大时间(以Tick为单位,可以用pdMS_TO_TICKS()将毫秒换为tick)

    源码分析:(展开折叠查看)

      这个函数主要做了4件事:

    1.  改变TCB中的eNotifyState为正在等待通知状态
    2. 让任务进入阻塞或挂起状态等待通知
    3. 收到通知后,对通知值ulNotifiedValue进行操作(删除或自减)
    4. 改变TCB中的eNotifyState为空状态,因为读取通知的操作已经完成了
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
    
        uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
        {
        TickType_t xTimeToWake;
        uint32_t ulReturn;
    
            taskENTER_CRITICAL();
            {
                /* Only block if the notification count is not already non-zero. */
                /* 仅仅当通知值为0的时候, 才进行阻塞操作 */
                if( pxCurrentTCB->ulNotifiedValue == 0UL )
                {
                    /* Mark this task as waiting for a notification. */     
                    /* 屏蔽这个任务来等待通知 */
                    pxCurrentTCB->eNotifyState = eWaitingNotification;   /*改变TCB中的eNotifyState 为 eWaitingNotification*/   /*【1】*/
    
                    if( xTicksToWait > ( TickType_t ) 0 )                                  
                    {
                        /* The task is going to block.  First it must be removed
                        from the ready list. */
                        /* 任务将会阻塞。 但首先必须从准备列表移除 */
                        if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
                        {
                            /* The current task must be in a ready list, so there is
                            no need to check, and the port reset macro can be called
                            directly. */
                            /* 当前任务必须在准备列表,所以没有必要再检查,接口重置宏可以被直接调用 */
                            portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
    
                        #if ( INCLUDE_vTaskSuspend == 1 )                    /*【2】*/
                        {
                            /* 如果设置了 xTicksToWait 为 portMAX_DELAY。任务会直接挂起*/
                            if( xTicksToWait == portMAX_DELAY )
                            {
                                /* Add the task to the suspended task list instead
                                of a delayed task list to ensure the task is not
                                woken by a timing event.  It will block
                                indefinitely. */
                                /* 把任务添加到挂起任务列表,而不是延时任务列表(阻塞状态会倒计时)。这是为了确认任务没有被时间事件唤醒。任务会被无限期的阻塞(直接挂起) */
                                vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
                            }
                            else
                            {
                                /* Calculate the time at which the task should be
                                woken if no notification events occur.  This may
                                overflow but this doesn't matter, the scheduler will
                                handle it. */
                                /* 如果没有通知事件发生,计算任务应该被唤醒的时间
                                这个可能溢出,但是没有关系,调度器会处理它的*/
                                
                                xTimeToWake = xTickCount + xTicksToWait;        /*计算唤醒时间*/
                                prvAddCurrentTaskToDelayedList( xTimeToWake );  /*把计算好的时间添加到延时列表。交给调度器处理*/
                            }
                        }
                        #else /* INCLUDE_vTaskSuspend */
                        {
                                /* Calculate the time at which the task should be
                                woken if the event does not occur.  This may
                                overflow but this doesn't matter, the scheduler will
                                handle it. */
                                /* 如果没有通知事件发生,计算任务应该被唤醒的时间
                                    这个可能溢出,但是没有关系,调度器会处理它的*/
                                xTimeToWake = xTickCount + xTicksToWait;
                                prvAddCurrentTaskToDelayedList( xTimeToWake );
                        }
                        #endif /* INCLUDE_vTaskSuspend */
    
                        traceTASK_NOTIFY_TAKE_BLOCK();
    
                        /* All ports are written to allow a yield in a critical
                        section (some will yield immediately, others wait until the
                        critical section exits) - but it is not something that
                        application code should ever do. */
                        /* 在临界区,所有接口被写入,来立刻允许一次切换(有一些会马上切换,其他会等待知道重要部分退出) , 但它不是一些应用代码应该做的重要的事*/
                        portYIELD_WITHIN_API();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            taskEXIT_CRITICAL();
    
            taskENTER_CRITICAL();
            {
                traceTASK_NOTIFY_TAKE();
                ulReturn = pxCurrentTCB->ulNotifiedValue;/*设置返回值为收到的通知值*/  
    
                if( ulReturn != 0UL )                                        /*【3】*/
                {
                    if( xClearCountOnExit != pdFALSE )
                    {
                        pxCurrentTCB->ulNotifiedValue = 0UL;/*清零通知值*/
                    }
                    else
                    {
                        ( pxCurrentTCB->ulNotifiedValue )--;/*减少通知值(对自增通知的特殊处理方法)*/
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
    
                pxCurrentTCB->eNotifyState = eNotWaitingNotification;/* 清除等待/接收通知状态 */ /*【4】*/
            }
            taskEXIT_CRITICAL();
    
            return ulReturn;
        }
    
    #endif /* configUSE_TASK_NOTIFICATIONS */
    View Code

      *在我们查看源码的时候,我们可以留意到,当我们设置输入参数为xTicksToWait为portMAX_DELAY的时候,而且INCLUDE_vTaskSuspend(激活挂起状态)宏定义为1的时候任务不是阻塞,而是直接挂起

                        #if ( INCLUDE_vTaskSuspend == 1 )                    /*【2】*/
                        {
                            /* 如果设置了 xTicksToWait 为 portMAX_DELAY。任务会直接挂起*/
                            if( xTicksToWait == portMAX_DELAY )
                            {
                                /* Add the task to the suspended task list instead
                                of a delayed task list to ensure the task is not
                                woken by a timing event.  It will block
                                indefinitely. */
                                /* 把任务添加到挂起任务列表,而不是延时任务列表(阻塞状态会倒计时)。这是为了确认任务没有被时间事件唤醒。任务会被无限期的阻塞(直接挂起) */
                                vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
                            }
                            else
                            {
                                /* Calculate the time at which the task should be
                                woken if no notification events occur.  This may
                                overflow but this doesn't matter, the scheduler will
                                handle it. */
                                /* 如果没有通知事件发生,计算任务应该被唤醒的时间
                                这个可能溢出,但是没有关系,调度器会处理它的*/
                                
                                xTimeToWake = xTickCount + xTicksToWait;        /*计算唤醒时间*/
                                prvAddCurrentTaskToDelayedList( xTimeToWake );  /*把计算好的时间添加到延时列表。交给调度器处理*/
                            }
                        }
    View Code

    官方例子:

     

    2.2.2、xTaskNotifyWait()

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

    输入参数:

      

    • ulBitsToClearOnEntry:在进入阻塞之前把指定的比特清除
    • ulBitsToClearOnExit:在接收到通知并处理后把指定的比特位清除
    • *pulNotificationValue:用于储存新到的通知值
    • xTicksToWait:最大阻塞时间

    返回值:

      两种情况

      

    源码分析:(展开折叠查看)

    这个函数和ulTaskNotifyTake很像,主要做了5件事:

    1. 根据ulBitsToClearOnEntry先清除一下通知值相应的位
    2. 改变TCB中的eNotifyState为正在等待通知状态
    3. 让任务进入阻塞或挂起状态等待通知
    4. 收到通知后,对通知值ulNotifiedValue进行操作(根据ulBitsToClearOnExit清除相应位)
    5. 改变TCB中的eNotifyState为空状态,因为读取通知的操作已经完成了
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        
        BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
        {
        TickType_t xTimeToWake;
        BaseType_t xReturn;
            
            /* */
            taskENTER_CRITICAL();
            {
                /* Only block if a notification is not already pending. */
                /* 只有没收到通知,才会阻塞任务(换句话说,如果现在已经收到通知了就不需要阻塞任务了,直接处理就好了) */
                if( pxCurrentTCB->eNotifyState != eNotified )
                {
                    /* Clear bits in the task's notification value as bits may get
                    set    by the notifying task or interrupt.  This can be used to
                    clear the value to zero. */
                    /* 清除任务通知值的比特位,因为这些比特肯能被通知任务或者中断置位了。
                        这个可以用于把值清0*/
                    pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry;                         /*【1】*/
    
                    /* Mark this task as waiting for a notification. */
                    /* 记录这个任务为等待通知的状态*/
                    pxCurrentTCB->eNotifyState = eWaitingNotification;                              /*【2】*/
    
                    if( xTicksToWait > ( TickType_t ) 0 )
                    {
                        /* The task is going to block.  First it must be removed
                        from the    ready list. */
                        /* 任务马上就要阻塞了,首先要把它移出准备列表 */
                        if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
                        {
                            /* The current task must be in a ready list, so there is
                            no need to check, and the port reset macro can be called
                            directly. */
                            /* 当前任务肯定在准备列表中 ,所以没有必要检查了,接口重置宏可以被直接调用 */
                            portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
    
                        #if ( INCLUDE_vTaskSuspend == 1 )                                            /*【3】*/
                        {
                            if( xTicksToWait == portMAX_DELAY )
                            {
                                /* Add the task to the suspended task list instead
                                of a delayed task list to ensure the task is not
                                woken by a timing event.  It will block
                                indefinitely. */
                                /* 把任务加入挂起任务列表,而不是延时任务列表。这是为了确保任务没有被时间事件唤醒
                                    这个任务会无限阻塞 */
                                vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
                            }
                            else
                            {
                                /* Calculate the time at which the task should be
                                woken if no notification events occur.  This may
                                overflow but this doesn't matter, the scheduler will
                                handle it. */
                                /* 计算在如果没有通知事件,发生任务应该被唤醒的时间 。
                                这个可能会导致溢出,但是没有关系。调度器会处理它的*/
                                xTimeToWake = xTickCount + xTicksToWait;
                                prvAddCurrentTaskToDelayedList( xTimeToWake );
                            }
                        }
                        #else /* INCLUDE_vTaskSuspend */
                        {
                                /* Calculate the time at which the task should be
                                woken if the event does not occur.  This may
                                overflow but this doesn't matter, the scheduler will
                                handle it. */
                                /* 计算在如果没有通知事件,发生任务应该被唤醒的时间 。
                                这个可能会导致溢出,但是没有关系。调度器会处理它的*/
                                xTimeToWake = xTickCount + xTicksToWait;
                                prvAddCurrentTaskToDelayedList( xTimeToWake );
                        }
                        #endif /* INCLUDE_vTaskSuspend */
    
                        traceTASK_NOTIFY_WAIT_BLOCK();
                        
                        /* All ports are written to allow a yield in a critical
                        section (some will yield immediately, others wait until the
                        critical section exits) - but it is not something that
                        application code should ever do. */
                        /* 写入所有接口,允许在临界区进行一次切换*/
                        portYIELD_WITHIN_API();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            taskEXIT_CRITICAL();
    
            taskENTER_CRITICAL();
            {
                traceTASK_NOTIFY_WAIT();
                
                if( pulNotificationValue != NULL )
                {
                    /* Output the current notification value, which may or may not
                    have changed. */
                    /* 设置pulNotificationValue参数为当前通知值,不管它有没有改变*/
                    *pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
                }
    
                /* If eNotifyValue is set then either the task never entered the
                blocked state (because a notification was already pending) or the
                task unblocked because of a notification.  Otherwise the task
                unblocked because of a timeout. */
                /* 如果通知值*/
                if( pxCurrentTCB->eNotifyState == eWaitingNotification )                    
                {
                    /* A notification was not received. */
                    /*没收到通知(超时)*/
                    xReturn = pdFALSE;
                }
                else
                {
                    /* A notification was already pending or a notification was
                    received while the task was waiting. */
                    /*在阻塞期间收到通知值,或者在调用这个函数之前就已经收到通知值 ,清除对应的通知值的位*/   
                    pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit;                            /*【4】*/
                    xReturn = pdTRUE;
                }
                
                /*接收通知完成,修改TCB的eNotifyState为空状态*/
                pxCurrentTCB->eNotifyState = eNotWaitingNotification;                                 /*【5】*/
            }
            taskEXIT_CRITICAL();
    
            return xReturn;
        }
    
    #endif /* configUSE_TASK_NOTIFICATIONS */
    View Code

    官方使用例子:

      这个例子是使用通知代替事件组,也就是置位通知

    1.   等待通知信号,任务会进入阻塞/挂起状态。
    2.   判断通知信号的第几位被置位了

    3、最后笔者写了四个例子,提供大家参考

    使用不带数据的二值任务通知(可以代替二进制信号量)

    //Notify is used for binary semaphore
    void vDemoTaskA(void *Parammenters)
    {
        for(;;)
        {
            //xTaskNotifyGive( HandleOfTaskB );
            xTaskNotify( HandleOfTaskB, 0, eNoAction );
            
            vTaskDelay( pdMS_TO_TICKS(5) );
        }
    }
    
    void vDemoTaskB(void *Parammenters)
    {
        for(;;)
        {
            ulTaskNotifyTake( pdTRUE , portMAX_DELAY );
            //add your codes here        
        }   
    }
    
    
    void vTaskCreate(void )
    {    
        xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL );                                            
        xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
    }
    
    /*******************************************************************************
    * Function Name  : main
    * Description    : 
    * Input          : None
    * Output         : None
    * Return         : None
    *******************************************************************************/
    int main( void )
    {
    
        vTaskCreate();
        
        vTaskStartScheduler( ); 
             
        while(1)
        {
            //should not be here
        }    
        
    }
    View Code

     

    使用自增的任务通知(可以代替计数信号量)

    //Notify is used for counting semaphore
    void vDemoTaskA(void *Parammenters)
    {
        for(;;)
        {
            xTaskNotifyGive( HandleOfTaskB );
            xTaskNotifyGive( HandleOfTaskB );
            xTaskNotifyGive( HandleOfTaskB );
            
            vTaskDelay( pdMS_TO_TICKS(5) );
        }
    }
    void vDemoTaskB(void *Parammenters)
    {
        uint32_t notifyCNT;
        
        for(;;)
        {
            
            if( notifyCNT = ulTaskNotifyTake( pdFALSE , portMAX_DELAY ) )
            {
                //should come in here for three times each turn
                //add your codes here
            }
            
        }   
    }
    
    
    void vTaskCreate(void )
    {    
        xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL );                                            
        xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
    }
    
    /*******************************************************************************
    * Function Name  : main
    * Description    : 
    * Input          : None
    * Output         : None
    * Return         : None
    *******************************************************************************/
    int main( void )
    {
    
        vTaskCreate();
        
        vTaskStartScheduler( ); 
             
        while(1)
        {
            //should not be here
        }    
        
    }
    View Code

    使用置位任务通知(可以代替事件标志组)

    //Notify is used for event group
    void vDemoTaskA(void *Parammenters)
    {
        for(;;)
        {
            xTaskNotify( HandleOfTaskB, (uint32_t)1 , eSetBits );        
            xTaskNotify( HandleOfTaskB, (uint32_t)1<<1 , eSetBits );
            xTaskNotify( HandleOfTaskB, (uint32_t)1<<3 , eSetBits );
            
            vTaskDelay( pdMS_TO_TICKS(5) );
        }
    }
    void vDemoTaskB(void *Parammenters)
    {
        #define ULONG_MAX  0xffffffff 
        uint32_t ulNotifiedValue=0;
        for(;;)
        {
            xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY );
            
            if( ulNotifiedValue & (1) )
            {
                //add your codes here            
            }
            if( ulNotifiedValue & (1<<1) )
            {
                //add your codes here            
            }
            if( ulNotifiedValue & (1<<2) )
            {
                //add your codes here            
            }
            if( ulNotifiedValue & (1<<3) )
            {
                //add your codes here            
            }
            //you can add more events below if you need them 
        }   
    }
    
    
    
    void vTaskCreate(void )
    {    
        xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL );                                            
        xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
    }
    
    /*******************************************************************************
    * Function Name  : main
    * Description    : 
    * Input          : None
    * Output         : None
    * Return         : None
    *******************************************************************************/
    int main( void )
    {
    
        vTaskCreate();
        
        vTaskStartScheduler( ); 
             
        while(1)
        {
            //should not be here
        }    
        
    }
    View Code

    使用传递消息任务通知(可以代替深度为一的消息队列)

    //Notify is used for sending message
    void vDemoTaskA(void *Parammenters)
    {
        uint32_t pTestBuff[5] = { 0,1,2,3,4 };
        int i;
        
        for(;;)
        {    
            for(i=0;i<5;i++)
            {
                //xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithOverwrite ); //The notify can not be overwrite
                xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithoutOverwrite );//The notify can be overwrite
                
                vTaskDelay( pdMS_TO_TICKS(5) );
            }
        }
    }
    void vDemoTaskB(void *Parammenters)
    {
        #define ULONG_MAX  0xffffffff 
        uint32_t ulNotifiedValue=0;
        uint32_t pRecBuff[5]={0};
        int i;
        
        for(;;)
        {
            for(i=0;i<5;i++)
            {
                xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY );
                pRecBuff[i] = ulNotifiedValue;
                //add your codes here            
            }
        }   
    }
    
    void vTaskCreate(void )
    {    
        xTaskCreate( vDemoTaskA, "vDemoTaskA", 100, NULL, 1, NULL );                                            
        xTaskCreate( vDemoTaskB, "vDemoTaskB", 100, NULL, 0, &HandleOfTaskB ); 
    }
    
    /*******************************************************************************
    * Function Name  : main
    * Description    : 
    * Input          : None
    * Output         : None
    * Return         : None
    *******************************************************************************/
    int main( void )
    {
    
        vTaskCreate();
        
        vTaskStartScheduler( ); 
             
        while(1)
        {
            //should not be here
        }    
        
    }
    View Code
     
  • 相关阅读:
    捕获mssqlservice 修改表后的数据,统一存储到特定的表中,之后通过代码同步两个库的数据
    有关求第n位xxx 的算法的问题
    C#获取枚举的特性描述工具方法
    wpf中嵌入另一个子进程exe像本地的一个页面那样
    emit 实现动态类,动态实现接口
    EF 支持泛型动态加载类访问数据库
    C# 通过 参数返回 C++ 指针
    C# 接收C++ dll 可变长字节或者 字符指针 char*
    健身篇
    Ubuntu 16.04安装Docker-Compose 与 Can't connect to docker from docker-compose
  • 原文地址:https://www.cnblogs.com/HongYi-Liang/p/9939734.html
Copyright © 2020-2023  润新知