• FreeRTOS个人笔记-任务延时列表的实现


    根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。

    配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!

    任务延时列表的实现

    在本节之前,为了实现任务的堵塞延时,在任务控制块中内置了一个延时变量,xTickToDelay。

    每当任务需要延时时,就初始化xTickToDelay需要延时的时间,然后将任务挂起(只是将任务在优先级位图表uxTopReadyPriority中对应的位清零),并不会将任务从就绪列表中删除。

    当每次时基中断(SysTick中断)来临时,就扫描就绪列表中每个任务的xTickToDelay。如果xTickToDelay大于0就递减一次,然后判断xTickToDelay是否为0,如果为0则表示延时时间到,将该任务就绪(将任务在优先级位图表uxTopReadyPriority中对应的位置1),然后进行任务切换。

    这种延时的缺点是,在每个时基中断(SysTick中断)中需要对所有任务都扫描一遍,费时。优点是容易理解。

    任务延时列表的工作原理

    在FreeRTOS中,有两个任务延时列表。

    当任务需要延时时,则先将任务挂起,即先将任务从就绪列表删除,然后插入到任务延时列表,同时更新下一个任务的解锁时刻变量:xNextTaskUnblockTime的值。

    xNextTaskUnblockTime的值等于系统时基计数器的值xTickCount加上任务需要延时的值xTicksToDelay。即xNextTaskUnblockTime=xTickCount+xTicksToDelay

    当系统时基计数器xTickCount=xNextTaskUnblockTime时,就表示有任务延时到期了,需要将该任务就绪。与RT-Thread和uC/OS在解锁延时任务时要扫描定时器列表这种时间不确定性的方法相比,FreeRTOS这个xNextTaskUnblockTime全局变量设计的非常巧妙。

    任务延时列表维护着一条双向链表,每个节点代表了正在延时的任务,节点按照延时时间大小做升序排列。当每次时基中断(SysTick中断)来临时,就拿系统时基计数器的值xTickCount与下一个任务的解锁时刻变量xNextTaskUnblockTime的值相比较,如果相等,则表示有任务延时到期,需要将该任务就绪,否则只是单纯地更新xTickCount的值,然后进行任务切换。

    实现任务延时列表

    任务延时列表及其指针

    1. //当系统时基计数器xTickCount没有溢出时,用一条列表。当xTickCount溢出时,用另一条列表。
    2. static List_t xDelayedTaskList1; //任务延时列表1
    3. static List_t xDelayedTaskList2; //任务延时列表2
    4. static List_t * volatile pxDelayedTaskList; //任务延时列表指针,指向xTickCount没有溢出时使用的那条列表
    5. static List_t * volatile pxOverflowDelayedTaskList; //任务延时列表指针,指向xTickCount溢出时使用的那条列表

    任务延时列表属于任务列表的一种(目前已经学了就绪列表,延时列表),在prvInitialiseTaskLists()函数中初始化。

    1. /* 初始化任务相关的列表 */
    2. void prvInitialiseTaskLists( void )
    3. {
    4. UBaseType_t uxPriority;
    5. /* 初始化就绪列表 */
    6. for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
    7. {
    8. vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
    9. }
    10. vListInitialise( &xDelayedTaskList1 );
    11. vListInitialise( &xDelayedTaskList2 );
    12. pxDelayedTaskList = &xDelayedTaskList1;
    13. pxOverflowDelayedTaskList = &xDelayedTaskList2;
    14. }

    这里的xDelayedTaskList1作为溢出前,xDelayedTaskList2 作为溢出后。

    前面说到的xNextTaskUnblockTime,下一个任务的解锁时刻变量。默认定义为0。

    static volatile TickType_t xNextTaskUnblockTime		= ( TickType_t ) 0U;

    xNextTaskUnblockTime在vTaskStartScheduler()函数中初始化为portMAX_DELAY。

    portMAX_DELAY这个类型不知道大家还有没有印象,附上图look look。

    vTaskStartScheduler()函数如下。

    1. void vTaskStartScheduler( void )
    2. {
    3. /*======================================创建空闲任务start==============================================*/
    4. TCB_t *pxIdleTaskTCBBuffer = NULL;
    5. StackType_t *pxIdleTaskStackBuffer = NULL;
    6. uint32_t ulIdleTaskStackSize;
    7. /* 获取空闲任务的内存:任务栈和任务TCB */
    8. vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer,
    9. &pxIdleTaskStackBuffer,
    10. &ulIdleTaskStackSize );
    11. xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask, /* 任务入口 */
    12. (char *)"IDLE", /* 任务名称,字符串形式 */
    13. (uint32_t)ulIdleTaskStackSize , /* 任务栈大小,单位为字 */
    14. (void *) NULL, /* 任务形参 */
    15. (UBaseType_t) tskIDLE_PRIORITY, /* 任务优先级,数值越大,优先级越高 */
    16. (StackType_t *)pxIdleTaskStackBuffer, /* 任务栈起始地址 */
    17. (TCB_t *)pxIdleTaskTCBBuffer ); /* 任务控制块 */
    18. /*======================================创建空闲任务end================================================*/
    19. xNextTaskUnblockTime = portMAX_DELAY; //此时xNextTaskUnblockTime为最大值0xffff ffff
    20. xTickCount = ( TickType_t ) 0U;
    21. /* 启动调度器 */
    22. if( xPortStartScheduler() != pdFALSE )
    23. {
    24. /* 调度器启动成功,则不会返回,即不会来到这里 */
    25. }
    26. }

    持续迭代修改更新。

    vTaskDelay()函数

    1. void vTaskDelay( const TickType_t xTicksToDelay )
    2. {
    3. TCB_t *pxTCB = NULL;
    4. /* 获取当前任务的TCB */
    5. pxTCB = pxCurrentTCB;
    6. /* 设置延时时间 */
    7. //pxTCB->xTicksToDelay = xTicksToDelay;
    8. //不再依赖TCB结构体的延时变量xTicksToDelay
    9. /* 将任务插入到延时列表 */
    10. prvAddCurrentTaskToDelayedList( xTicksToDelay );
    11. /* 任务切换 */
    12. taskYIELD();
    13. }

    上面函数用到了prvAddCurrentTaskToDelayedList()函数。

    要清楚这么一个过程,prvAddCurrentTaskToDelayedList()函数是任务需要延时,在就绪列表中移除,加入到延时列表中。

    prvAddCurrentTaskToDelayedList()函数如下

    1. static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait )
    2. {
    3. TickType_t xTimeToWake;
    4. /* 获取系统时基计数器xTickCount的值,xTickCount即SysTick的中断次数 */
    5. const TickType_t xConstTickCount = xTickCount;
    6. /* uxListRemove函数返回链表中剩余节点的个数 */
    7. /* 将任务从就绪列表中移除 */
    8. if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
    9. {
    10. //当前链表下没有任务就绪
    11. /* 将任务在优先级位图中对应的位清除 */
    12. portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
    13. }
    14. /* 计算延时到期时,系统时基计数器xTickCount的值是多少 */
    15. xTimeToWake = xConstTickCount + xTicksToWait;
    16. /* 此时,任务已经移除就绪列表并已经计算了下一个任务解锁时刻,即当前任务的延时时间xTimeToWake */
    17. /* 初始化节点排序辅助值 */
    18. /* 将延时到期的值设置为节点的排序值 */
    19. /* 最先延时到期的任务排在最前面 */
    20. listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
    21. /* 假设xTickCount为0xffff fffdUL,xTicksToWait为0x03 */
    22. /* xTimeToWake = 0xffff fffdUL + 0x03 =1 ,则溢出了 */
    23. if( xTimeToWake < xConstTickCount ) /* 溢出 */
    24. {
    25. vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
    26. }
    27. else /* 没有溢出 */
    28. {
    29. vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
    30. /* 更新下一个任务解锁时刻变量xNextTaskUnblockTime的值 */
    31. if( xNextTaskUnblockTime > xTimeToWake )
    32. {
    33. xNextTaskUnblockTime = xTimeToWake;
    34. }
    35. }
    36. }

    因为 FreeRTOS 支持同一个优先级下可以有多个任务,所以在清除优先级位图表 uxTopReadyPriority 中对应的位时要判断下该优先级下的就绪列表是否还有其它的任务。 

    更新下一个任务解锁时刻变量xNextTaskUnblockTime的值,这一步很重要。在后续的xTaskIncrementTick()函数中,我们只需让系统时基计数器xTickCount与xNextTaskUnblockTime的值比较,就知道延时最快结束的任务是否到期。

    xTaskIncrementTick()函数产生大变动。xTaskIncrementTick()函数意在延时结束时移除延时列表,回到就绪列表。 

    1. void xTaskIncrementTick( void )
    2. {
    3. TCB_t * pxTCB;
    4. TickType_t xItemValue;
    5. const TickType_t xConstTickCount = xTickCount + 1;
    6. xTickCount = xConstTickCount;
    7. /* 如果xConstTickCount溢出,则切换延时列表 */
    8. if( xConstTickCount == ( TickType_t ) 0U )
    9. {
    10. taskSWITCH_DELAYED_LISTS();
    11. }
    12. /* xConstTickCount记录xTickCount计时,当大于等于下一个任务解锁时刻,说明延时结束。移除延时列表,回到就绪列表 */
    13. /* 最近的延时任务延时到期 */
    14. if( xConstTickCount >= xNextTaskUnblockTime )
    15. {
    16. for( ;; ) //依次将这些延时到期的任务从延时列表移除
    17. {
    18. if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
    19. {
    20. /* 延时列表为空,设置xNextTaskUnblockTime为可能的最大值 */
    21. xNextTaskUnblockTime = portMAX_DELAY;
    22. break; //退出for循环
    23. }
    24. else /* 延时列表不为空,则有任务在延时 */
    25. {
    26. //获取当前列表下第一个节点的TCB
    27. pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
    28. //获取当前列表下第一个节点的排序值
    29. xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
    30. if( xConstTickCount < xItemValue )
    31. {
    32. xNextTaskUnblockTime = xItemValue;
    33. break; /* 直到将延时列表中所有延时到期的任务移除才跳出for循环 */
    34. }
    35. /* 将延时到期的任务从延时列表移除,消除等待状态 */
    36. ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
    37. /* 将解除等待的任务添加到就绪列表 */
    38. prvAddTaskToReadyList( pxTCB );
    39. }
    40. }
    41. }/* xConstTickCount >= xNextTaskUnblockTime */
    42. /* 任务切换 */
    43. portYIELD();
    44. }

    在函数开头,有一个taskSWITCH_DELAYED_LISTS()函数,用于切换延时列表。

    1. //当系统时基计数器溢出的时候,延时列表pxDelayedTaskList 和 pxOverflowDelayedTaskList要互相切换
    2. #define taskSWITCH_DELAYED_LISTS()\
    3. {\
    4. List_t *pxTemp;\
    5. pxTemp = pxDelayedTaskList;\
    6. pxDelayedTaskList = pxOverflowDelayedTaskList;\
    7. pxOverflowDelayedTaskList = pxTemp;\
    8. xNumOfOverflows++;\
    9. prvResetNextTaskUnblockTime();\
    10. }

    xNumOfOverflows为溢出标志位,也算是溢出后列表转换完成位的意思。初始值为0。

    static volatile BaseType_t xNumOfOverflows 			= ( BaseType_t ) 0;

    上面函数中,用到了prvResetNextTaskUnblockTime()函数

    1. static void prvResetNextTaskUnblockTime( void )
    2. {
    3. TCB_t *pxTCB;
    4. if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
    5. {
    6. /* 延时列表为空,设置xNextTaskUnblockTime为可能的最大值 */
    7. xNextTaskUnblockTime = portMAX_DELAY;
    8. }
    9. else
    10. {
    11. /* 获取延迟列表头部任务的TCB */
    12. ( pxTCB ) = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
    13. xNextTaskUnblockTime = listGET_LIST_ITEM_VALUE( &( ( pxTCB )->xStateListItem ) );
    14. }
    15. }

    位于延迟列表最前面的任务应该从阻塞状态中移除。

    在没有添加任务延时列表之前,只有就绪列表。无论任务在延时还是就绪都只能通过扫描就绪列表来找到任务TCB,从而实现系统调度。

    在上一节“支持多优先级”中,实现taskRESET_READY_PRIORITY()函数时,不用先判断当前优先级下就绪列表中的链表的节点是否为0,而是直接把任务在优先级位图表uxTopReadyPriority中对应位清零。因为当前优先级下就绪列表中的链表节点不可能为0。

    在本节中,我们额外添加了延时列表,当任务要延时时,将任务从就绪列表中移除,然后添加到延时列表 。同时将任务在优先级位图表uxTopReadyPriority中对应位清零。

    在清除任务在优先级位图表uxTopReadyPriority中对应位时,与上一节不同的是,需要判断就绪列表pxReadyTaskLists[]在当前优先级下对应的链表节点是否为0。

    只有当该链表下没有任务时才真正地将任务在优先级位图表uxTopReadyPriority中对应位清零。

  • 相关阅读:
    FPGA Quartus IP核 打开使用
    希尔排序:优化插入排序的精妙算法
    【教程】解决ngrok reconnecting外部网络无法访问
    java-ssm-jsp-大学社团管理系统
    华为eNSP配置专题-路由策略的配置
    代码随想录Day12 二叉树 LeetCode T102二叉树的层序遍历 T226 翻转二叉树 T101 对称二叉树
    【CKS】考试之TLS通信配置
    国产管理软件勒索病毒大爆发
    【Qt-23】基于QCharts绘制曲线图
    move_base代码阅读
  • 原文地址:https://blog.csdn.net/weixin_47077788/article/details/125859964