FreeRTOS(七)----时间管理

目录

FreeRTOS系统延时函数

FreeRTOS系统时钟节拍 


 

  • FreeRTOS 延时函数

在 FreeRTOS 中延时函数有相对模式和绝对模式,FreeRTOS 中不同的模式用的函数不同,其中函数 vTaskDelay()是相对模式(相对延时函数), 函数 vTaskDelayUntil()是绝对模式(绝对延时函数)。

 

1.函数 vTaskDelay()在文件 tasks.c 中有定义, 要使用此函数的话宏 INCLUDE_vTaskDelay 必须为 1,函数代码如下:

void vTaskDelay( const TickType_t xTicksToDelay )
{
    BaseType_t xAlreadyYielded = pdFALSE;
    //延时时间要大于 0
    if( xTicksToDelay > ( TickType_t ) 0U )
    {
        configASSERT( uxSchedulerSuspended == 0 ); 
        vTaskSuspendAll();
        {
            traceTASK_DELAY();
            prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
        }
        xAlreadyYielded = xTaskResumeAll();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    if( xAlreadyYielded == pdFALSE )
    {
        portYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
  • 延时时间由参数 xTicksToDelay 来确定,为要延时的时间节拍数,延时时间肯定要大于 0。否则的话相当于直接调用函数 portYIELD()进行任务切换。
  • 调用函数 vTaskSuspendAll()挂起任务调度器。
  • 调用函数 prvAddCurrentTaskToDelayedList() 将要延时的任务添加到延时列表pxDelayedTaskList 或 者 pxOverflowDelayedTaskList() 中 。 后 面 会 具 体 分 析 函 数prvAddCurrentTaskToDelayedList()
  • 调用函数 xTaskResumeAll()恢复任务调度器。
  • 如果函数 xTaskResumeAll()没有进行任务调度的话那么在这里就得进行任务调度。
  • 调用函数 portYIELD_WITHIN_API()进行一次任务调度。

 

2.函数 vTaskDelayUntil()会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用函数 vTaskDelayUntil()。此函数再文件 tasks.c 中有如下定义:

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
                                   const TickType_t xTimeIncrement )
{
    TickType_t xTimeToWake;
    BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

    configASSERT( pxPreviousWakeTime ); 
    configASSERT( ( xTimeIncrement > 0U ) ); 
    configASSERT( uxSchedulerSuspended == 0 );

    vTaskSuspendAll();
    {
        const TickType_t xConstTickCount = xTickCount; 
        xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

        if( xConstTickCount < *pxPreviousWakeTime )
        {
            if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake >\
                xConstTickCount ) )
               {
                    xShouldDelay = pdTRUE;
                }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        *pxPreviousWakeTime = xTimeToWake;

        if( xShouldDelay != pdFALSE )
        {    
            traceTASK_DELAY_UNTIL( xTimeToWake );
            prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    xAlreadyYielded = xTaskResumeAll();	

    if( xAlreadyYielded == pdFALSE )
    {
        ortYIELD_WITHIN_API();
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

pxPreviousWakeTime上一次任务延时结束被唤醒的时间点,任务中第一次调用函数vTaskDelayUntil 的话需要将                                                                    pxPreviousWakeTime 初始化进入任务的 while()循环体的时间点值。在以后的运行中函数                                                                    vTaskDelayUntil()会自动更新 pxPreviousWakeTime。

xTimeIncrement任务需要延时的时间节拍数(相对于 pxPreviousWakeTime 本次延时的节拍数)。

  • 挂起任务调度器。
  • 记录进入函数 vTaskDelayUntil()的时间点值,并保存在 xConstTickCount 中。
  • 根据延时时间 xTimeIncrement 来计算任务下一次要唤醒的时间点, 并保存在xTimeToWake 中。可以看出这个延时时间是相对于 pxPreviousWakeTime 的,也就是上一次任务被唤醒的时间点。pxPreviousWakeTime、xTimeToWake、xTimeIncrement 和xConstTickCount 的关系如下所示。

 

  • 根据上图 可以看出,理论上 xConstTickCount 要大于 pxPreviousWakeTime 的,但是也有一种情况会导致 xConstTickCount 小于 pxPreviousWakeTime,那就是 xConstTickCount 出了!
  • 既然 xConstTickCount 都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且 xTimeToWake 肯定也是要大于 xConstTickCount 的。这种情况如图 下

如果满足条件的话就将 pdTRUE 赋值给 xShouldDelay,标记允许延时。

还有其他两种情况,一:只有 xTimeToWake 溢出,二:都没有溢出。只有 xTimeToWake溢出的话如图下

都不溢出的话就如图 1所示,这两种情况都允许进行延时。

  • 将 pdTRUE 赋值给 xShouldDelay,标记允许延时。
  • 更新 pxPreviousWakeTime 的值,更新为 xTimeToWake,为本函数的下一次执行做准备。
  • 经过前面的判断,允许进行任务延时。
  • 调用函数 prvAddCurrentTaskToDelayedList()进行延时。函数的第一个参数是设置任务的阻塞时间,前面我们已经计算出了任务下一次唤醒时间点了,那么任务还需要阻塞的时间就是下一次唤醒时间点 xTimeToWake 减去当前的时间 xConstTickCount。而在函数 vTaskDelay()中只是简单的将这参数设置为 xTicksToDelay。
  • 调用函数 xTaskResumeAll()恢复任务调度器。函数 vTaskDelayUntil()的使用方法如下:
void TestTask( void * pvParameters )
{
    TickType_t	PreviousWakeTime;
    //延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接
    //设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数。
    
    const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );
    PreviousWakeTime = xTaskGetTickCount();	//获取当前的系统节拍值for( ;; )
    {
        /******************************************************************/
        /*************************任务主体*********************************/
        /******************************************************************/

        //调用函数vTaskDelayUntil 进行延时
        vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);
    }
}

 

  • FreeRTOS 系统时钟节拍

不管是什么系统,运行都需要有个系统时钟节拍,前面已经提到多次了,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中 xTickCount 就会加一,xTickCount 的具体操作过程是在函数 xTaskIncrementTick()中进行的,此函数在文件 tasks.c 中有定义,如下:

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB; TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    //每个时钟节拍中断(滴答定时器中断)调用一次本函数,增加时钟节拍计数器 xTickCount 的
    //值,并且检查是否有任务需要取消阻塞。
    traceTASK_INCREMENT_TICK( xTickCount );
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
        const TickType_t xConstTickCount = xTickCount + 1;
        //增加系统节拍计数器 xTickCount 的值,当为 0,也就是溢出的话就交换延时和溢出列
        //表指针值。
        xTickCount = xConstTickCount;
        if( xConstTickCount == ( TickType_t ) 0U )
        {
            taskSWITCH_DELAYED_LISTS();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        //判断是否有任务延时时间到了,任务都会根据唤醒时间点值按照顺序(由小到大的升
        //序排列)添加到延时列表中,这就意味这如果延时列表中第一个列表项对应的任务的
        //延时时间都没有到的话后面的任务就不用看了,肯定也没有到。
        if( xConstTickCount >= xNextTaskUnblockTime )
        {
            for( ;; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                {
                    //延时列表为空,设置 xNextTaskUnblockTime 为最大值。
                    xNextTaskUnblockTime = portMAX_DELAY; 
                    break;
                }
                else
                {
                    //延时列表不为空,获取延时列表的第一个列表项的值,根据判断这个值
                    //判断任务延时时间是否到了, 如果到了的话就将任务移除延时列表。
                    pxTCB = ( TCB_t * )\	
                    listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); xItemValue =\	
                    listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

                    if( xConstTickCount < xItemValue )	
                    {

                    }
                    else
                    {

                    }

                    //任务延时时间还没到,但是 xItemValue 保存着下一个即将解除
                    //阻塞态的任务对应的解除时间点,所以需要用 xItemValue 来更新
                    //变量xNextTaskUnblockTime
                    xNextTaskUnblockTime = xItemValue;	
                    break;

                    mtCOVERAGE_TEST_MARKER();

                    //将任务从延时列表中移除
                    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );	

                    //任务是否还在等待其他事件?如信号量、队列等,如果是的话就将这些
                    //任务从相应的事件列表中移除。相当于等待事件超时退出!
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !\ = NULL )                        
                    {

                    }
                    else
                    {

                    }

                    ( void ) uxListRemove( &( pxTCB->xEventListItem ) );	
                    mtCOVERAGE_TEST_MARKER();


                    //将任务添加到就绪列表中
                    prvAddTaskToReadyList( pxTCB );	

                    #if (	configUSE_PREEMPTION == 1 )
                    {
                        //使用抢占式内核,判断解除阻塞的任务优先级是否高于当前正在
                        //运行的任务优先级,如果是的话就需要进行一次任务切换!
                        if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )	
                        {

                        }
                        else
                        {

                        }
                    }

                xSwitchRequired = pdTRUE;
                mtCOVERAGE_TEST_MARKER();
                #endif /* configUSE_PREEMPTION */
            }
        }
    }

    //如果使能了时间片的话还需要处理同优先级下任务之间的调度
    #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )(17)
    {
        if( listCURRENT_LIST_LENGTH( &( \
        pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
        {

        }
        else
        {

        }
    }

    xSwitchRequired = pdTRUE;
    mtCOVERAGE_TEST_MARKER();
    #endif

    //使用时钟节拍钩子函数
    #if ( configUSE_TICK_HOOK == 1 )
    {
        if( uxPendedTicks == ( UBaseType_t ) 0U )
        {

        }
        else
        {

        }
    }    
    vApplicationTickHook();	
    mtCOVERAGE_TEST_MARKER();
    #endif /* configUSE_TICK_HOOK */
}

  1. 判断任务调度器是否被挂起。
  2. 将时钟节拍计数器 xTickCount 加一,并将结果保存在 xConstTickCount 中,下一行程序会将 xConstTickCount 赋值给 xTickCount,相当于给 xTickCount 加一。
  3. xConstTickCount 为 0,说明发生了溢出!
  4. 如果发生了溢出的话使用函数 taskSWITCH_DELAYED_LISTS 将延时列表指针pxDelayedTaskList 和溢出列表指针 pxOverflowDelayedTaskList 所指向的列表进行交换,函数taskSWITCH_DELAYED_LISTS()本质上是个宏,在文件 tasks.c 中有定义,将这两个指针所指向的列表交换以后还需要更新 xNextTaskUnblockTime 的值。
  5. 变量 xNextTaskUnblockTime 保存着下一个要解除阻塞的任务的时间点值,如果xConstTickCount 大于 xNextTaskUnblockTime 的话就说明有任务需要解除阻塞了。
  6. 判断延时列表是否为空。
  7. 如果延时列表为空的话就将 xNextTaskUnblockTime 设置为 portMAX_DELAY。
  8. 延时列表不为空,获取延时列表第一个列表项对应的任务控制块。
  9. 获取(8)中获取到的任务控制块中的壮态列表项值。
  10. 任务控制块中的壮态列表项值保存了任务的唤醒时间点,如果这个唤醒时间点值大于当前的系统时钟(时钟节拍计数器值),说明任务的延时时间还未到。
  11. 任务延时时间还未到,而且 xItemValue 已经保存了下一个要唤醒的任务的唤醒时间点,所以需要用 xItemValue 来更新 xNextTaskUnblockTime。
  12. 任务延时时间到了,所以将任务先从延时列表中移除。
  13. 检查任务是否还等待某个事件,比如等待信号量、队列等。如果还在等待的话就任务从相应的事件列表中移除。因为超时时间到了!
  14. 将任务从相应的事件列表中移除。
  15. 任务延时时间到了,并且任务已经从延时列表或者事件列表中已经移除。所以这里需要将任务添加到就绪列表中。
  16. 延时时间到的任务优先级高于正在运行的任务优先级,所以需要进行任务切换了,标记 xSwitchRequired 为pdTRUE,表示需要进行任务切换。
  17. 如果使能了时间片调度的话,还要处理跟时间片调度有关的工作
  18. 如果使能了时间片钩子函数的话就执行时间片钩子函数 vApplicationTickHook(),函数的具体内容由用户自行编写。
  19. 如果调用函数 vTaskSuspendAll()挂起了任务调度器的话在每个滴答定时器中断就不不会更新 xTickCount 了。取而代之的是用 uxPendedTicks 来记录调度器挂起过程中的时钟节拍数。这样在调用函数 xTaskResumeAll()恢复任务调度器的时候就会调用 uxPendedTicks 次函数xTaskIncrementTick(),这样 xTickCount 就会恢复,并且那些应该取消阻塞的任务都会取消阻塞。函数 xTaskResumeAll()中相应的处理代码如下:
BaseType_t xTaskResumeAll( void )
{
    TCB_t *pxTCB = NULL;
    BaseType_t xAlreadyYielded = pdFALSE; configASSERT( uxSchedulerSuspended );

    taskENTER_CRITICAL();

    /************************************************************************/
    /****************************省略部分代码********************************/
    /************************************************************************/

    UBaseType_t uxPendedCounts = uxPendedTicks; if( uxPendedCounts > ( UBaseType_t ) 0U )
    {
        //do-while()循环体,循环次数为 uxPendedTicks do
        {
            if( xTaskIncrementTick() != pdFALSE )	//调用函数xTaskIncrementTick
            {

            }
            else
            {
                xYieldPending = pdTRUE;	//标记需要进行任务调度。
                mtCOVERAGE_TEST_MARKER();
            }
            --uxPendedCounts;
        } while( uxPendedCounts > ( UBaseType_t ) 0U );
        uxPendedTicks = 0;	//循环执行完毕,uxPendedTicks 清零
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    /************************************************************************/
    /****************************省略部分代码********************************/
    /************************************************************************/

    taskEXIT_CRITICAL();
    return xAlreadyYielded;
}
  1. uxPendedTicks 是个全局变量,在文件 tasks.c 中有定义,任务调度器挂起以后此变量用来记录时钟节拍数。
  2. 、有时候调用其他的 API 函数会使用变量 xYieldPending 来标记是否需要进行上下文切换,后面具体遇到具体分析。
  3. 、返回 xSwitchRequired 的值,xSwitchRequired 保存了是否进行任务切换的信息,如果为 pdTRUE 的话就需要进行任务切换, pdFALSE 的话就不需要进行任务切换。函数xPortSysTickHandler()中调用 xTaskIncrementTick()的时候就会判断返回值,并且根据返回值决定是否进行任务切换。
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值