FreeRTOS_时间管理

news2025/1/10 12:01:39

目录

1. FreeRTOS 延时函数

1.1 函数 vTaskDelay()

1.2 函数 prvAddCurrentTaskToDelayedList()

1.3 函数 vTaskDelayUntil()

2. FreeRTOS 系统时钟节拍


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

1. FreeRTOS 延时函数

1.1 函数 vTaskDelay()

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

        函数 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 的关系如下

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

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

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

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

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

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

(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() 的时候就会判断返回值,并且根据返回值决定是否进行任务切换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/717317.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

php对接小鹅通API开发高级实战案例解析:小鹅通实战开发之合并用户user_id批量同步

小鹅通实战开发 ChatGPT工作提效之小鹅通二次开发批量API对接解决方案&#xff08;学习记录同步、用户注册同步、权益订购同步、开发文档)小鹅通学习记录大批量队列同步小鹅通云服务PHP-API二维数组传参解决方案 合并用户user_id批量同步 小鹅通实战开发前言一、账号发生合并带…

LabVIEW评估儿童的运动认知技能

LabVIEW评估儿童的运动认知技能 以前测量认知运动功能的技术范围从基本和耗时的笔和纸技术&#xff0c;到使用准确但复杂和昂贵的实验室设备。Kinelab的主要要求是提供一个易于配置、坚固且便携的平台&#xff0c;以便在向4-12岁的儿童展示交互式视觉刺激期间快速收集运动学测…

第三章 搜索与图论(三)——最小生成树与二分图

文章目录 最小生成树PrimKruskal 二分图染色法匈牙利算法 最小生成树练习题858. Prim算法求最小生成树859. Kruskal算法求最小生成树 二分图练习题860. 染色法判定二分图861. 二分图的最大匹配 最小生成树 最小生成树针对无向图&#xff0c;有向图不会用到 Prim 求解稠密图的最…

Error in parsing ‘.arclint‘ file, in key ‘bin‘ for linter ‘pylint‘

背景&#xff1a; Run arc diff --preview to create code revision on remote terminal, but exception happened. nnhhh:~/ppp$ arc diff --preview Linting...Exception Error in parsing .arclint file, in key bin for linter pylint. None of the configured binaries …

剑指offer28.对称的二叉树

我一开始想到的是用之前的镜像二叉树方法把树转换成他的镜像树放进队列&#xff0c;在这之前把树自己放进队列。然后比较这两个队列。但这样是有问题的&#xff0c;比如题目给的[1,2,2,null,3,null,3] 这个示例就不能通过&#xff0c;于是看了题解。豁然开朗&#xff0c;其实只…

服务器上安装虚拟机以及编译FastDDS以及ShapesDemo开源项目

&#x1f941;作者&#xff1a; 华丞臧 &#x1f4d5;​​​​专栏&#xff1a;【C】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449;LeetCode 文章目录 前言一、…

namecheap 域名服务器 设置为Cloudflare

Namecheap 设置 自定义 域名服务器 登录Namecheap 帐户。进入后&#xff0c;将鼠标悬停在页面右上角的“帐户”选项上&#xff0c;然后选择“域列表”或选择左侧边栏中的“域列表” 参考 如何在 Cloudflare 帐户中域设置 DNS 记录

Simulink中Selector的使用

文章目录 0.prolog1 Starting and ending indices (port)2. Starting index (port)3. Starting index (dialog)4. Index vector (dialog)5. Index vector (port)Reference 0.prolog Index mode有两种&#xff0c;[one-based, zero-based]&#xff0c;分别是从1开始计数&#x…

波函数:描述量子世界的数学工具

亲爱的读者&#xff0c; 欢迎回到我们的量子力学系列文章。在前两篇文章中&#xff0c;我们介绍了量子力学的起源和基本概念。今天&#xff0c;我们将深入探讨量子力学的核心数学工具——波函数。 波函数是量子力学中的关键概念&#xff0c;它描述了一个量子系统的状态。波函…

Java转Go:java开发者转学go语言,请给我一些建议和学习推荐

在做开发时遇到最无理的需求就是部门没了&#x1f602; 目录 做开发时你遇到最无理的需求是什么&#xff1f;方向一&#xff1a;分享那些你遇到的无理需求方向二&#xff1a;面对这些无理需求时你是怎么做的&#xff1f;方向三&#xff1a;怎么避免遇见这些无理需求 java开发者…

赛效:怎么在线给Word文档加图片水印

1&#xff1a;在电脑网页上打开云组件&#xff0c;点击“Word转换”菜单里的“Word加水印&#xff08;图片&#xff09;”。 2&#xff1a;点击选择文件添加Word文档。 3&#xff1a;点击“选择水印图片”上传做水印的图片。 4&#xff1a;水印图片添加成功后可以选择水印角度&…

电商小程序开发指南:吸引并留住用户的秘诀

电商小程序作为微信生态内的新产品&#xff0c;有许多开发方面的内容需要学习&#xff0c;比如电商小程序的定位、功能、设计等。电商小程序是由商家开发并在微信平台上运行的小程序。它可以与微信公众号一起使用&#xff0c;也可以单独使用。 从传统电商到社交电商&#xff0…

24-正则表达式,应用场景

一、是什么 是一种用来匹配字符串的强有力的武器 它的设计思想是用一种描述性的语言定义一个规则&#xff0c;凡是符合规则的字符串&#xff0c;我们就认为它“匹配”了&#xff0c;否则&#xff0c;该字符串就是不合法的 在 JavaScript中&#xff0c;正则表达式也是对象&…

Spring Boot 缓存应用实践

缓存是最直接有效提升系统性能的手段之一。个人认为用好用对缓存是优秀程序员的必备基本素质。本文结合实际开发经验&#xff0c;从简单概念原理和代码入手&#xff0c;一步一步搭建一个简单的二级缓存系统。 一、通用缓存接口 1、缓存基础算法 FIFO&#xff08;First In Fir…

LVS负载均衡群集与LVS-NAT部署实战配置

文章目录 一.什么是集群1.群集的含义 二.集群使用在那个场景三.集群的分类1.负载均衡器群集2.高可用群集3.高性能运算群集 四.负载集群的架构1.第一层&#xff0c;负载调度器2.第二层&#xff0c;服务器池3.第三层&#xff0c;共享存储 五.负载均衡集群的工作模式1.地址转换 &a…

STM32中static和extern的用法

static&#xff1a; A. static变量 称为静态变量。根据变量的类型可以分为静态局部变量和静态全程变量。 1. 静态局部变量 它与局部变量的区别在于: 在函数退出时, 这个变量始终存在, 但不能被其它 函数使用, 当再次进入该函数时, 将保存上次的结果。其它与局部变量一样。…

记一次自建靶场三层代理内网渗透过程

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 向日葵RCE外网突破02 Frp内网隧道搭建03 获取域内出网主机权限04 三层隧道搭建访问内网不出网主机 01 向日葵RCE外网突破 端口扫描探测存活端口&#xff0c;发现存在172.16.16.128:49773端口 访问…

【RPC】—Protobuf编码原理

Protobuf编码原理 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ Spring专栏&#x1f449;https://blog.csdn.net/weixin_53580595/category_12279588.html SpringMVC专…

【跨域认证】详解JWT,JWT是什么?

JSON Web Token&#xff08;缩写 JWT&#xff09;是目前最流行的跨域认证解决方案&#xff0c;本文介绍它的原理和用法。 一、跨域认证的问题 互联网服务离不开用户认证。一般流程是下面这样。 1、用户向服务器发送用户名和密码。 2、服务器验证通过后&#xff0c;在当前对话&…