FreeRTOS_时间管理

这篇具有很好参考价值的文章主要介绍了FreeRTOS_时间管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1. FreeRTOS 延时函数

1.1 函数 vTaskDelay()

1.2 函数 prvAddCurrentTaskToDelayedList()

1.3 函数 vTaskDelayUntil()

2. FreeRTOS 系统时钟节拍


        在使用 FreeRTOS 的过程中我们通常会在一个任务函数中使用延时函数对这个任务延时,当执行延时函数的时候就会进行任务切换(这里我们要明白为什么其他的延时函数不会发生任务调度,而 FreeRTOS 延时函数会发生系统调用;),并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。延时函数属于 FreeRTOS 的时间管理,这里我们就来学习 FreeRTOS 的时间管理过程,看一下调用延时函数以后究竟发生了什么?任务是如何进入阻塞态的,在延时完成以后任务又是如何从阻塞态恢复到就绪态的?

1. FreeRTOS 延时函数

1.1 函数 vTaskDelay()

        在 FreeRTOS 中延时函数可以设置为两种不同的模式:相对模式绝对模式。并且 FreeRTOS 中不同模式用的函数不同,其中函数 vTaskDelay() 是相对模式(相对延时函数)函数 vTaskDelayUntil() 是绝对模式(绝对延时函数)

相对延时:指每次延时都是从执行函数 vTaskDelay() 开始,直到延时指定的时间结束;

绝对延时:指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务;

FreeRTOS_时间管理,FreeRTOS,stm32,单片机,嵌入式硬件,数据结构

        下图就是一个绝对延时函数的图,延时时间是整个 xTimeIncrement ;

        (1) 为任务主体,也就是任务真正要做的工作

        (2) 是任务函数中调用 vTaskDelayUntil() 对任务进行延时

        (3) 为其他任务在运行 (高优先级) ,意思就是该任务难免被其他更高优先级的任务抢占; 

FreeRTOS_时间管理,FreeRTOS,stm32,单片机,嵌入式硬件,数据结构

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

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

(1)延时函数由参数 xTicksToDelay 来确定,为了有延时的时间节拍数,延时时间肯定要大于 0 ,否则相当于没有延时。否则的话就相当于直接调用函数 portYIELD() 进行任务切换。

(2)调用函数 vTaskSuspendAll() 挂起任务调度器。

(3)调用函数 prvAddCurrentTaskToDelayedList() 将要延时的任务添加到延时(阻塞)列表 pxDelayedTaskList 或者 pxOverflowDelayedTaskList() 中。(这里就说明了为什么延时函数会产生系统调用,因为延时函数会将目前运行的任务添加到延时阻塞队列; )

(4)调用函数 xTaskResumeAll() 恢复任务调度器。

(5)如果函数 xTaskResumeAll() 没有进行任务调度的话那么在这里就得进行任务调度。

(6)调用函数 portYIELD_WITHIN_API 进行一次任务调度。

1.2 函数 prvAddCurrentTaskToDelayedList()

        函数 prvAddCurrentTaskToDelayedList() 用于将当前任务添加到等待列表中,函数在文件 tasks.c 中有定义,缩减后的函数如下:

static void prvAddCurrentTaskToDelayedList( TickType_t x TicksToWait, 
                                            const BaseType_t xCanBlockIndefinitely ) 
{ 
    TickType_t xTimeToWake; 
    const TickType_t xConstTickCount = xTickCount;                             (1) 
 
    #if( INCLUDE_xTaskAbortDelay == 1 ) 
    { 
        //如果使能函数 xTaskAbortDelay()的话复位任务控制块的 ucDelayAborted 字段为 
        //pdFALSE。 
        pxCurrentTCB->ucDelayAborted = pdFALSE; 
    } 
    #endif 
 
    if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )     (2) 
    { 
        portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );     (3) 
    } 
    else 
    { 
        mtCOVERAGE_TEST_MARKER(); 
    } 
 
    #if ( INCLUDE_vTaskSuspend == 1 ) 
    { 
        if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )                            
                                                                                (4) 
        { 
            vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) ); (5) 
        } 
        else 
        { 
            xTimeToWake = xConstTickCount + xTicksToWait;                     (6) 
            listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), \         (7) 
                                        xTimeToWake ); 
            if( xTimeToWake < xConstTickCount )                                     (8) 
            { 
                vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->\         (9) 
                             xStateListItem ) ); 
            } 
            else 
            { 
                vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); (10) 
                if( xTimeToWake < xNextTaskUnblockTime )                         (11) 
                { 
                    xNextTaskUnblockTime = xTimeToWake;                             (12) 
                } 
                else 
                { 
                    mtCOVERAGE_TEST_MARKER(); 
                } 
            } 
        } 
    } 
    /************************************************************* **************/ 
    /****************************其他条件编译语句*******************************/ 
    /***************************************************************************/ 
}

(1)读取进入函数 prvAddCurrentTaskToDelayedList() 的时间点并保存在 xCountTickCount 中,后面计算任务唤醒时间点的时候要用到。xTickCount 是时钟节拍计数器,每个滴答定时器中断 xTickCount 都会加一

(2)要将当前正在运行的任务添加到延时列表中,肯定要先将当前任务从就绪列表中移除。

(3)将当前任务从就绪列表中移除以后还要取消任务在 uxTopReadyPriority 中的就绪标记。也就是将 uxTopReadyPriority 中对应的 bit 清零。

(4)延时时间为最大值 portMAX_DELAY,并且 xCanBlockIndefinitely 不为 pdFALSE(xCanBlockIndefinitely 不为 pdFALSE 的话表示允许阻塞任务)的话直接将当前任务添加到挂起列表中,任务就不用添加到延时列表中。

(5)将当前任务添加到挂起列表 xSuspendedTaskList 的末尾。

(6)计算任务唤醒时间点,也就是(1)中获取到的进入函数 prvAddCurrentTaskToDelayedList() 的时间值 xCountTickCount 加上延时时间值 xTicksToWait

(7)将计算到的任务唤醒时间点值 xTimeToWait 写入到任务列表中状态列表项的相应字段中。

(8)计算得到的任务唤醒时间点小于 xCountTickCount,说明发生了溢出。全局变量 xTickCount 是 TickType_t 类型的,这是个 32 位的数据类型,因此在用 xTickCount 计算任务唤醒时间点 xTimeToWake 的时候肯定会出现溢出的现象。

        FreeRTOS 针对此现象专门做了处理,在 FreeRTOS 中定义了两个延时列表 xDelayedTaskList1 和 xDelayedTaskList2,并且也定义了两个指针 pxDelayedTaskList 和 pxOverflowDelayedTaskList 来访问这两个列表,在初始化列表函数 prvInitialiseTaskLists() 中指针 pxDelayedTaskList 指向了列表 xDelayedTaskList1,指针 pxOverflowDelayedTaskList 指向了列表 xDelayedTaskList2。这样发生溢出的话就将任务添加到 pxOverflowDelayedTaskList 所指向的列表中,如果没有溢出的话就添加到 pxDelayedTaskList 所指向的列表中

(9)如果发生溢出的话就将当前任务添加到 pxOverflowDelayedTaskList 所指向的列表中。

(10)如果没有发生溢出的话就将当前任务添加到 pxDelayedTaskList 所指向的列表中。

(11)xNextTaskUnblockTime 是个全局变量,保存着距离下一个要取消阻塞的任务最小时间点值。当 xTimeToWake 小于 xNextTaskUnblockTime 的话说明有个更小的时间点来啦。(简单来说就是:xTimeToWake 保存的是任务唤醒时间,也就是当前任务还有多少时间会进入就绪态;xNextTaskUnblockTime 保存距离下一个取消阻塞的任务的最小时间点值,如果 xTimeToWake < xNextTaskUnblockTime,就说明此任务进入就绪态的时间比到下一个通过任务取消阻塞从而进入就绪态的时间还要小,那么就告诉操作系统有个更小的值来啦,从而进行赋值

(12)更新 xNextTaskUnblockTime 为 xTimeToWake。

1.3 函数 vTaskDelayUntil()

        函数 vTaskDelayUntil() 会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用函数 vTaskDelayUntil()。

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();                                     (1) 
    { 
        const TickType_t xConstTickCount = xTickCount;         (2) 
        xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;         (3) 
 
        if( xConstTickCount < *pxPreviousWakeTime )                         (4) 
        { 
            if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake >\     (5) 
                    xConstTickCount ) ) 
            { 
                xShouldDelay = pdTRUE;                                         (6) 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        else 
        { 
            if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > \         (7) 
                    xConstTickCount ) ) 
            { 
                xShouldDelay = pdTRUE;                                         (8) 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        *pxPreviousWakeTime = xTimeToWake;                                     (9) 
 
        if( xShouldDelay != pdFALSE )                                                (10) 
        { 
            traceTASK_DELAY_UNTIL( xTimeToWake ); 
            prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );  
                                                                                (11) 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    xAlreadyYielded = xTaskResumeAll();                                             (12) 
 
    if( xAlreadyYielded == pdFALSE ) 
    { 
        ortYIELD_WITHIN_API(); 
    } 
    else 
    { 
        mtCOVERAGE_TEST_MARKER(); 
    } 
} 

参数:

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

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

(1)挂起任务调度器。

(2)记录进入函数 vTaskDelayUntil() 的时间值,并保存在 xCountTickCount 中。所以 xCountTickCount 中保存的是进入函数 vTaskDelayUntil() 的时间值

(3)根据延时时间 xTimeIncrement 来计算任务下一次要唤醒的时间点,并保存在 xTimeToWake 中。可以看出这个延时时间是相对于 pxPreviousWakeTime 的,也就是上一次任务被唤醒的时间点。pxPrevious WakeTime、xTimeToWake、xTimeIncrement 和 xConstTickCount 的关系如下

FreeRTOS_时间管理,FreeRTOS,stm32,单片机,嵌入式硬件,数据结构

        其中,(1)为任务主体,也就是任务真正要做的工作,(2)是任务函数中调用 vTaskDelayUntil() 对任务进行延时,(3)为其他任务在运行。任务的延时时间是 xTimeIncrement,这个延时时间是相对于 pxPreviousWakeTime 的,可以看出任务总的执行时间一定要小于任务的延时时间 xTimeIncrement!也就是说如果使用 vTaskDelayUntil() 的话相当于任务的执行周期永远都是 xTimeIncrement,而任务一定要在这个时间内执行完成。这样就保证了任务永远按照一定的频率运行了,这个延时值就是绝对延时时间,因此函数 vTaskDelayUntil() 也叫做绝对延时函数。

(4)理论上 xCountTickCount 要大于 pxPreviousWakeTime 的,但是也有一种情况会导致 xConstTickCount 小于 pxPreviousWakeTime,那就是 xConstTickCount 溢出了!

(5)既然 xConstTickCount 都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且 xTimeToWake 肯定也是要大于 xConstTickCount 的。

FreeRTOS_时间管理,FreeRTOS,stm32,单片机,嵌入式硬件,数据结构

(6)满足(5)条件的话就将 pdTRUE 赋值给 xShouldDelay,标记允许延时。

(7)还有其他两种情况,一:只有 xTimeToWake 溢出,二:都没有溢出。

        只有 xTimeToWake 溢出的话如下图所示:

FreeRTOS_时间管理,FreeRTOS,stm32,单片机,嵌入式硬件,数据结构

(8)将 pdTRUE 赋值给 xShouldDelay,标记允许延时。

(9)更新 pxPreviousWakeTime 的值,更新为 xTimeToWake,为本函数的下一次执行做准备。

(10)经过前面的判断,允许进行任务延时。

(11)调用函数 prvAddCurrentTaskToDelayedList() 进行延时。函数的第一个参数就是设置任务的阻塞时间,在之前,我们已经计算出了任务的下一次唤醒时间点了,那么任务还需要的阻塞时间就是下一次唤醒时间点 xTimeToWake 减去当前的时间 xConstTickCount 。而在函数 vTaskDelay() 中只是简单的将这参数设置为 xTicksToDelay。

(12)调用函数 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); 
    } 
}

参数:

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

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

        函数的第一个参数是上一次任务延时结束被唤醒的时间点,通过调用函数 xTaskGetTickCount 来获取即可。

        函数的第二个参数是任务需要延时的时间节拍数(注意:延时50ms,不能直接设置延时时间,需要转换成延时节拍数,通过调用函数 pdMS_TO_TICKS 转换即可)

        其实使用函数 vTaskDelayUntil() 延时的任务也不一定就能周期性的运行,使用函数 vTaskDelayUntil() 只能保证你按照一定的周期取消阻塞,进入就绪态。如果你有更高优先级或者中断的话还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于 vTaskDelay() 这个简单的延时函数而言的

2. FreeRTOS 系统时钟节拍

        不管是什么系统,运行都需要有个系统时钟节拍,xTickCount 就是 FreeRTOS 的系统时钟节拍计数器每个滴答定时器中断中 xTickCount 就会加一,xTickCount 的具体操作过程是在函数 xTaskIncrementTick() 中进行的,此函数在文件 task.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 )             (1) 
    { 
        const TickType_t xConstTickCount = xTickCount + 1;                 (2) 
 
        //增加系统节拍计数器 xTickCount 的值,当为 0,也就是溢出的话就交换延时和溢出列 
        //表指针值。 
        xTickCount = xConstTickCount; 
        if( xConstTickCount == ( TickType_t ) 0U )                     (3) 
        { 
            taskSWITCH_DELAYED_LISTS();                                     (4) 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
 
        //判断是否有任务延时时间到了,任务都会根据唤醒时间点值按照顺序(由小到大的升 
        //序排列)添加到延时列表中,这就意味这如果延时列表中第一个列表项对应的任务的 
        //延时时间都没有到的话后面的任务就不用看了,肯定也没有到。 
        if( xConstTickCount >= xNextTaskUnblockTime )                     (5) 
        { 
            for( ;; ) 
            { 
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )     (6) 
                { 
                    //延时列表为空,设置 xNextTaskUnblockTime 为最大值。 
                    xNextTaskUnblockTime = portMAX_DELAY;                 (7) 
                    break;
                } 
                else 
                { 
                    //延时列表不为空,获取延时列表的第一个列表项的值,根据判断这个值 
                    //判断任务延时时间是否到了, 如果到了的话就将任务移除延时列表。 
                    pxTCB = ( TCB_t * )\                                     (8) 
                    listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); 
                    xItemValue =\                                         (9) 
                    listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) ); 
 
                    if( xConstTickCount < xItemValue )                     (10) 
                    { 
                        //任务延时时间还没到,但是 xItemValue 保存着下一个即将解除 
                        //阻塞态的任务对应的解除时间点,所以需要用 xItemValue 来更新 
                        //变量 xNextTaskUnblockTime 
                        xNextTaskUnblockTime = xItemValue;                 (11) 
                        break; 
                    } 
                    else 
                    { 
                        mtCOVERAGE_TEST_MARKER(); 
                    } 
                    //将任务从延时列表中移除 
                    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );     (12) 
 
                    //任务是否还在等待其他事件?如信号量、队列等,如果是的话就将这些 
                    //任务从相应的事件列表中移除。相当于等待事件超时退出! 
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !\ (13) 
                        = NULL ) 
                    { 
                        ( void ) uxListRemove( &( pxTCB->xEventListItem ) );     (14) 
                    } 
                    else 
                    { 
                        mtCOVERAGE_TEST_MARKER(); 
                    } 
 
                    //将任务添加到就绪列表中 
                    prvAddTaskToReadyList( pxTCB );                             (15) 
 
                    #if ( configUSE_PREEMPTION == 1 ) 
                    { 
                        //使用抢占式内核,判断解除阻塞的任务优先级是否高于当前正在
                        //运行的任务优先级,如果是的话就需要进行一次任务切换! 
                        if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )     (16) 
                        { 
                            xSwitchRequired = pdTRUE; 
                        } 
                        else 
                        { 
                            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 ) 
        { 
            xSwitchRequired = pdTRUE; 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    #endif 
 
    //使用时钟节拍钩子函数 
    #if ( configUSE_TICK_HOOK == 1 ) 
    { 
        if( uxPendedTicks == ( UBaseType_t ) 0U ) 
        { 
            vApplicationTickHook();                                             (18) 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    #endif /* configUSE_TICK_HOOK */ 
    } 
    else //任务调度器挂起                                                 (19) 
    { 
    ++uxPendedTicks;                                                     (20) 
    #if ( configUSE_TICK_HOOK == 1 ) 
    { 
        vApplicationTickHook(); 
    } 
    #endif 
} 
 
    #if ( configUSE_PREEMPTION == 1 ) 
    { 
        if( xYieldPending != pdFALSE )                                 (21) 
        { 
            xSwitchRequired = pdTRUE; 
        } 
        else 
        { 
            mtCOVERAGE_TEST_MARKER(); 
        } 
    } 
    #endif /* configUSE_PREEMPTION */ 
 
    return xSwitchRequired;                                                 (22) 
} 

(1)判断任务调度器是否被挂起。

(2)将时钟节拍计数器 xTickCount 加一,并将结果保存在 xConstTickCount 中,下一行程会将 xConstTickCount 赋值给 xTickCount,相当于给 xTickCount 加一。

(3)xCountTickCount 为 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 
            { 
                xYieldPending = pdTRUE; //标记需要进行任务调度。 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER();
            } 
            --uxPendedCounts; //变量减一 
        } while( uxPendedCounts > ( UBaseType_t ) 0U ); 
        uxPendedTicks = 0; //循环执行完毕,uxPendedTicks 清零 
    } 
    else 
    { 
        mtCOVERAGE_TEST_MARKER(); 
    } 
 
    /************************************************************************/ 
    /****************************省略部分代码********************************/ 
    /************************************************************************/ 
 
    taskEXIT_CRITICAL(); 
    return xAlreadyYielded; 
} 

(20)uxPendedTicks 是个全局变量,在文件 task.c 中定义,任务调度器挂起以后此变量用来记录时钟节拍数。

(21)有时候调用其他的 API 函数会使用变量 xYieldPending 来标记是否需要进行上下文切换。

(22)返回 xSwitchRequired 的值,xSwitchRequired 保存了是否进行任务切换的信息,如果为 pdTRUE 的话就需要进行任务切换,pdFALSE 的话就不需要进行任务切换了。函数 xPortSysTickHandler() 中调用 xTaskIncrementTick() 的时候就会判断返回值,并且根据返回值决定是否进行任务切换。

FreeRTOS_时间管理,FreeRTOS,stm32,单片机,嵌入式硬件,数据结构

        FreeRTOS 中的延时函数会产生系统调用,进行任务切换,其本质如上图所示:

        延时时间必须大于 0 才有效,将调度器挂起,保证此时不再调用新的任务上 CPU 运行,然后将此时正在运行的任务加入到阻塞队列,通过滴答定时器记录时间,当时间到达延时的时间后,进行任务切换,将原本运行的任务从阻塞队列中调入就绪队列;

        这就是延时函数运行的本质;文章来源地址https://www.toymoban.com/news/detail-519591.html

到了这里,关于FreeRTOS_时间管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • (第48-59讲)STM32F4单片机,FreeRTOS【事件标志、任务通知、软件定时器、Tickless低功耗】【纯文字讲解】【】

    【吐血总结】FreeRTOS难点、Systick中断-滴答定时器、PendSV中断-任务切换、SVC中断-系统底层、时间片调度-时钟节拍【已完结】 (第1-8讲)STM32F4单片机,FreeRTOS基础知识总结【视频笔记、代码讲解】【正点原子】【原创】 (第9-10讲)STM32F4单片机,FreeRTOS任务创建和删除(动态方

    2024年02月01日
    浏览(49)
  • STM32F4单片机内部FLASH编程时间

    单片机内部的flash除了存储固件以外,经常将其分为多个区域,用来存储一些参数或存储OTA升级等待更新的固件,这时就会涉及单片机内部flash的编程和擦除操作。STM32同系列的单片机内部flash特性和扇区大小都不太一样,以下基于STM32F407VET6此型号进行简单介绍。 STM32F4xx中文参

    2024年02月03日
    浏览(43)
  • STM32单片机示例:64位全局时间戳发生器

    STM32H743 / H750 系列的芯片有一个64位的全局时间戳发生器( Global timestamp generator ),这篇文章将对它的使用做个记录。 全局时间戳发生器相关的内容可以参考官方参考手册: TGS时钟来源与APB总线时钟,这就是TGS计数器时钟了,并且用于TGS计数时没法对其进行分频操作。我们使

    2024年02月10日
    浏览(32)
  • STM32系列单片机“中断触发时间、最小中断周期、指令周期、平均执行速度、和单条指令执行时间”的问题研究

    查阅相关资料书籍和博客总结了一下知识点,以便学习巩固复习。 在学习《ARM Cortex-M3与Cortex-M4权威指南(第3版)》 这本书具体说明了触发中断需要多长时间。 在权威指南的第74页提到, Cortex-M3和Cortex-M4的中断等待非常小,只有12个周期 。也就是说触发中断后,需要12个时钟

    2024年04月10日
    浏览(33)
  • 32单片机RTC时间接续,掉电时间保存

    前提:首先要实现RTC掉电之后时间还能继续走,RTC电池是必要的 说明:设备第一次启动需要初始化配置RTC,但当二次启动再重新配置RTC会导致RTC计数器置零,所以传统的程序流程是不行的,我们需要知道设备是第一次启动还是二次启动,来判断是否需要重新初始化配置RTC。另

    2024年01月17日
    浏览(36)
  • STM32单片机(一)STM32简介

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月10日
    浏览(45)
  • STM32单片机开发-01 STM32介绍

    通过野火开发板学习单片机 从内核上分有Cortex-M0、M3、M4 和M7 F1 代表了基础型,基于Cortex-M3 内核,主频为72MHZ F4 代表了高性能,基于Cortex-M4 内核,主频180M。 数据手册:用于芯片选型和设计原理图 参考手册:用于编程时查阅 Icode总线 – 该总线讲M3内核的指令总线与闪存指令

    2024年01月21日
    浏览(44)
  • STM32单片机(二)STM32环境搭建

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月10日
    浏览(48)
  • FreeRTOS内存管理 基于STM32

    目录 一、内存管理的基本概念 二、内存管理的应用场景 三、heap_4.c 1.内存申请函数 pvPortMalloc() 2.内存释放函数 vPortFree()  四、内存管理的实验 五、内存管理的实验现象       在计算系统中,变量、中间数据一般存放在系统存储空间中,只有在实际使用时才将 它们从存储空

    2024年02月14日
    浏览(43)
  • GD32单片机和STM32单片机的对比分析

    GD32单片机和STM32单片机都是基于Arm Cortex-M3/M4内核的32位通用微控制器,广泛应用于各种嵌入式系统和物联网领域。两者之间有很多相似之处,但也有一些不同之处,本文将从以下几个方面对比分析两者的特点、优势和开发成本。 GD32单片机采用的是二代的M3/M4内核,而STM32单片

    2024年02月16日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包