嵌入式操作系统之时钟节拍下的任务切换

嵌入式操作系统如FreeRTOS。FreeRTOS 中任务切换的过程, 提到触发任务切换的两种情况 : 高优先级任务就绪抢占和同优先级任务时间共享(包括提前挂起)。 系统中,时间延时和任务阻塞,时间片都以 Systick 为单位。通过设置文件 FreeRTOSConfig.h 中 configTICK_RATE_HZ 设置任务节拍中断频率, 在启动任务调度器时,系统会根据另一个变量, CPU 的频率configCPU_CLOCK_HZ 计算对应写入节拍计数器的值,启动定时器中断。

系统在每一次节拍计数器中断服务程序xPortSysTickHandler(平台实现 port.c 中) 中调用处理函数 xTaskIncrementTick, 依据该函数返回值判断是否需要触发 PendSV 异常, 进行任务切换。 
涉及任务时间片轮循, 任务阻塞超时, 以及结束以此实现的延时函数。

分析的源码版本是 v9.0.0

xTaskIncrementTick()

系统每次节拍中断服务程序中主要任务由函数 xTaskIncrementTick 完成。 
在任务调度器没有挂起的情况下( xTaskIncrementTick != pdFALSE ),该函数主要完成 : 
* 判断节拍计数器xTickCount 是否溢出, 溢出轮换延时函数队列 
* 判断是否有阻塞任务超时,取出插入就绪链表 
* 同优先级任务时间片轮

而当任务调度器被挂起时, 该函数累加挂起时间计数器 uxPendedTicks, 调用用户钩子函数, 此时,正在运行的任务不会被切换, 一直运行。 
当恢复调度时, 系统会先重复调用 xTaskIncrementTick 补偿 (uxPendedTicks次)。

不管, 系统调度器是否挂起, 每次节拍中断都会调用用户的钩子函数 vApplicationTickHook。 由于函数是中断中调用,不要在里面处理太复杂的事情!!

节拍计数器溢出

涉及的变量, 定义在 task.c开头。

  1. PRIVILEGED_DATA static List_t xDelayedTaskList1;

  2. PRIVILEGED_DATA static List_t xDelayedTaskList2;

  3. PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;

  4. PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;

初始化时, pxDelayedTaskList 指向 xDelayedTaskList1, pxOverflowDelayedTaskList 指向 pxOverflowDelayedTaskList,一开始我还在郁闷延时链表为什么要两个,到这里才明白。

当任务由于等待事件(延时,消息队列什么的堵塞)时,会设置一个时间,这时候,响应的任务会被挂到延时链表中,如果超过设置时间没有事件响应,则系统会从延时链表中取出任务恢复就绪。 
系统任务延时参考系统节拍计数器 xTickCount, 加入链表前依据当前计数器的值计算出超时的值 ( xTickCount+ xTicksToDelay ), 顺序插入到延时链表中。

对不同平台xTickCount 表示的位数不同,但是每次节拍中断加一,总会溢出。 上述计算任务延时时间,如果系统发现计算出来的时间已经溢出,则会将该任务加入到 pxOverflowDelayedTaskList 这个链表中。 
在系统节拍中断时, 节拍计数器每次加一, 系统判断是否溢出,如果溢出, 调用宏 taskSWITCH_DELAYED_LISTS()切换上述的链表指针。 
宏主要实现如下 :

  1. pxTemp = pxDelayedTaskList;

  2. pxDelayedTaskList = pxOverflowDelayedTaskList;

  3. pxOverflowDelayedTaskList = pxTemp;

  4. xNumOfOverflows++;

  5. prvResetNextTaskUnblockTime();

这就是设置两个链表的原因,轮流倒应对计数器的溢出。

唤醒超时任务

全局变量 xNextTaskUnblockTime 记录下一个需要退出延时链表的任务时间, 因此, 接下来判断当前时间,延时链表中是否有任务需要推出阻塞状态。

  1. if( xConstTickCount >= xNextTaskUnblockTime )

  2. {

  3. for ( ;; ) {

  4. // 取出唤醒任务, 推进就绪链表

  5. }

  6. }

对应的, 把所有阻塞时间达到的任务取出, 推入到就绪链表,更新下一个任务解除时间给变量 xNextTaskUnblockTime

任务时间片轮循

处理完延时任务后, 开始判断当前运行任务, 对应优先级链表中是否有其他任务就绪, 如果有,需要保证每个任务都能获得运行时间, 标记需要任务切换, 作为函数返回。

完整函数

完整函数注释如下,

  1. BaseType_t xTaskIncrementTick( void )

  2. {

  3. TCB_t * pxTCB;

  4. TickType_t xItemValue;

  5. BaseType_t xSwitchRequired = pdFALSE;

  6. traceTASK_INCREMENT_TICK( xTickCount );

  7. // 调度器正在运行

  8. if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )

  9. {

  10. // 节拍计数器递增 1

  11. const TickType_t xConstTickCount = xTickCount + 1;

  12. xTickCount = xConstTickCount;

  13. if( xConstTickCount == ( TickType_t ) 0U )

  14. {

  15. // 节拍计数器溢出

  16. // 比如32位 0xFFFFFFFF + 1 -> 0

  17. // 延时链表切换

  18. taskSWITCH_DELAYED_LISTS();

  19. }

  20. else

  21. {

  22. mtCOVERAGE_TEST_MARKER();

  23. }

  24. if( xConstTickCount >= xNextTaskUnblockTime )

  25. {

  26. for( ;; )

  27. {

  28. if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )

  29. {

  30. // 没有任务延时, 时间设置"无穷大" 退出循环

  31. xNextTaskUnblockTime = portMAX_DELAY;

  32. break;

  33. }

  34. else

  35. {

  36. // 取出延时链表头任务 TCB

  37. pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );

  38. // 取该任务延时值

  39. xItemValue = listGET_LIST_ITEM_VALUE(&(pxTCB->xStateListItem));

  40. // 判断任务是否超时

  41. if( xConstTickCount < xItemValue )

  42. {

  43. // 任务还没到时间,更新全局变量

  44. // 直接退出

  45. xNextTaskUnblockTime = xItemValue;

  46. break;

  47. }

  48. else

  49. {

  50. mtCOVERAGE_TEST_MARKER();

  51. }

  52. // 任务恢复就绪, 从堵塞的链表中删除

  53. ( void ) uxListRemove( &( pxTCB->xStateListItem ) );

  54. if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )

  55. {

  56. ( void ) uxListRemove( &( pxTCB->xEventListItem ) );

  57. }

  58. else

  59. {

  60. mtCOVERAGE_TEST_MARKER();

  61. }

  62. // 插入就绪链表等待被执行

  63. prvAddTaskToReadyList( pxTCB );

  64. // 如果系统允许抢占

  65. #if ( configUSE_PREEMPTION == 1 )

  66. {

  67. // 如果新就绪任务优先级高于当前任务

  68. // 标记需要切换任务

  69. if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )

  70. {

  71. xSwitchRequired = pdTRUE;

  72. }

  73. else

  74. {

  75. mtCOVERAGE_TEST_MARKER();

  76. }

  77. }

  78. #endif /* configUSE_PREEMPTION */

  79. }

  80. }

  81. }

  82. // 同优先级任务 时间轮

  83. #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )

  84. {

  85. if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )

  86. {

  87. xSwitchRequired = pdTRUE;

  88. }

  89. else

  90. {

  91. mtCOVERAGE_TEST_MARKER();

  92. }

  93. }

  94. #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */

  95. // 用户钩子函数

  96. #if ( configUSE_TICK_HOOK == 1 )

  97. {

  98. if( uxPendedTicks == ( UBaseType_t ) 0U )

  99. {

  100. vApplicationTickHook();

  101. }

  102. else

  103. {

  104. mtCOVERAGE_TEST_MARKER();

  105. }

  106. }

  107. #endif /* configUSE_TICK_HOOK */

  108. }

  109. else

  110. {

  111. // 记录调度器被挂起器件节拍中断次数

  112. // 恢复后用于补偿, 执行本函数 uxPendedTicks 次先

  113. ++uxPendedTicks;

  114. #if ( configUSE_TICK_HOOK == 1 )

  115. {

  116. vApplicationTickHook();

  117. }

  118. #endif

  119. }

  120. // 函数返回值, 如果为 pdTRUE,

  121. // 则调用的系统节拍中断会触发 PendSV 异常, 任务切换

  122. #if ( configUSE_PREEMPTION == 1 ) // 允许抢占

  123. {

  124. // 其他地方标记需要执行一次任务切换

  125. // 所以不管前面需不需要 这里都会返回需要切换

  126. if( xYieldPending != pdFALSE )

  127. {

  128. xSwitchRequired = pdTRUE;

  129. }

  130. else

  131. {

  132. mtCOVERAGE_TEST_MARKER();

  133. }

  134. }

  135. #endif /* configUSE_PREEMPTION */

  136. return xSwitchRequired;

  137. }

系统延时函数

任务执行过程中需要使用到延时函数进行延时, 使用系统提供的延时函数可以将当前任务挂起,让出CPU 使用时间,当时间到达的时候, 有系统恢复任务运行。 FreeRTOS 提供两种类型的延时函数

普通延时函数 vTaskDelay

一般情况下,需要延时一定时间,就调用此函数,将需要的延时时间转换为对应系统节拍数传递(如宏pdMS_TO_TICKS()), 之后,当前任务会从就绪链表移除, 加入到延时链表中,系统会在节拍中断中检查是否到达延时时间, 重新恢复任务就绪。

void vTaskDelay( const TickType_t xTicksToDelay );

该函数调用到另一个函数是 prvAddCurrentTaskToDelayedList, 将任务加入到延时链表中, 函数中会判断设定时间是否溢出, 选择加入到对应的延时链表, 同上提到计数器溢出的问题。

循环延时函数 vTaskDelayUntil

相比上面的普通延时函数, 这个函数适用于任务周期性执行的。 
举个例子说明下, 有一个任务, 需要周期性 500ms 读取一次传感器数据, 用上例子可以这么写 :

  1. void vTASKReadSensor(void *pvParameters)

  2. {

  3. // 500ms 转换为 节拍

  4. const portTickType xDelay = pdMS_TO_TICKS(500);

  5. for( ;; )

  6. {

  7. readSensor();

  8. vTaskDelay( xDelay );

  9. }

  10. }

看起来是周期性 500 ms 执行, 但是考虑, 如果任务由于优先级比较低之类的问题, 在延时返回就绪状态后没有及时被运行,那么实际时间就开始飘了。 
如果使用函数 vTaskDelayUntil

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )

多了一个参数 pxPreviousWakeTime, 就不会有这个问题了 
先看以下如何使用 :

  1. void vTASKReadSensor(void *pvParameters)

  2. {

  3. const portTickType xDelay = pdMS_TO_TICKS(500);

  4. static portTickType xLastWakeTime;

  5. // 记录第一次调用函数的时间 , 后续该变量由延时函数自己叠加

  6. xLastWakeTime = xTaskGetTickCount();

  7. for( ;; )

  8. {

  9. readSensor();

  10. vTaskDelayUntil( xDelay );

  11. }

  12. }

周前性执行前调用一个变量, 获取当前节拍计数器 ,简单认为是第一次调用的时间, 而后开始周期性执行, 传入的变量第一次由我们设置后, 后续会由函数自动更新。 
比如, 我们在SystickCount 为 0 开始延时, 在500 返回读取数据, 再延时, 和上一个例子一样, 当 500 延时后返回, 调度原因延迟, 等到 600 才读取数据并开始下一次延时, 这里, 这个函数不同地方在于, 他会考虑这延迟的 100, 而第二次延时的时间, 其实还是从 500 开始算的, 也就是, 1000 的时候, 任务延时第二次就结束了, 而不是等到 1100 。

由于涉及到任务调度, 所以, 理论上来说, 两个函数定时都是”不住确”的。 时间单位是系统节拍 !

RTOS ---嵌入式操作系统之时钟节拍下的任务切换相关推荐

  1. 从0到1写嵌入式操作系统---------------------------4尝试两个任务的切换

    上次我们创建了两个任务task1与task2,这次我们来实现tsak1与task2两个任务的切换.先了解下CM3内核的一些常用指令,不一定非要记住,只需要熟能生巧!先来看MSR和MRS指令. MRS  ...

  2. 嵌入式实时操作系统10——系统时钟节拍

    1.系统节拍是什么 时间管理在操作系统内核中占有非常重要的地位,操作系统内核中有大量基于时间驱动的功能.有些任务是需要周期执行,比如一个软件定时器需要一秒钟周期性运行100次:有些功能任务需要延时一段 ...

  3. 主流嵌入式操作系统(RTOS)有哪些?看看这14种

    满足实时控制要求的嵌入式操作系统(RTOS)操作系统,以下介绍14种主流的RTOS,分别为μClinux.μC/OS-II.eCos.FreeRTOS.mbed OS.RTX.Vxworks.QNX. ...

  4. 嵌入式操作系统和RTOS(实时操作系统)介绍。

    目录 嵌入式操作系统 RTOS(实时操作系统) 实时操作系统: 什么是嵌入式操作系统 一μClinux 二μC/OS-II 三eCos 四 FreeRTOS 五 mbed OS 六 RTX 七 VxW ...

  5. 详解目前主流的嵌入式操作系统(RTOS)操作系统

    满足实时控制要求的嵌入式操作系统(RTOS)操作系统,以下介绍14种主流的RTOS,分别为μClinux.μC/OS-II.eCos.FreeRTOS.mbed OS.RTX.Vxworks.QNX. ...

  6. Linux·主流嵌入式操作系统(RTOS)

    满足实时控制要求的嵌入式操作系统(RTOS)操作系统,以下介绍14种主流的RTOS,分别为μClinux.μC/OS-II.eCos.FreeRTOS.mbed OS.RTX.Vxworks.QNX. ...

  7. 嵌入式操作系统多任务调度原理分析与RUST参考实现

    操作系统多任务调度原理分析与RUST参考实现 作为一名在软件领域工程师,在职业生涯的尽头能有幸接触到一部分硬件产品是我莫大的荣幸.秉承我一贯刨根问底,不搞清楚问题本质不罢休的作风和态度,结合基本的计算 ...

  8. 嵌入式操作系统风云录:历史演进与物联网未来第2章 Chapter2

    第2章 Chapter2 嵌入式操作系统的历史 20世纪70年代末,嵌入式操作系统商业产品开始在北美出现,20世纪90年代末嵌入式OS的数量呈井喷式增加,最鼎盛的时候有数百种之多,即使经过30多年的发 ...

  9. 自制嵌入式操作系统 DAY1

    遥想当年刚学习操作系统的时候,很难理解教科书中关于线程/进程的描述.原因还是在于操作系统书上的内容太过抽象,对于一个没有看过内核代码的初学者来说,很难理解各种数据结构的调度.后来自己也买了一些造轮子的 ...

最新文章

  1. 查看python安装路径以及pip安装的包
  2. 值得收藏!脑科学、脑机接口领域白皮书、研究报告汇总
  3. Fragment回调Activity的事件分发
  4. python字典可以切片吗_7.map(感觉跟python中的字典有点像)数组,切片(make)傻傻分不清楚,多多指点...
  5. 学历是铜牌,能力是银牌,人脉是金牌,思维是王牌——有感
  6. 策略(strategy)模式
  7. 千位分隔符转换为数字
  8. 从国际站 - M 站建设谈开发者产品思维
  9. JAVA minaio模型_分布式系统之Java IO模型
  10. 数据结构思维 第十三章 二叉搜索树
  11. 大话Synchronized及锁升级
  12. 前端跳转页面 添加request headers_前端需要了解的 CORS 知识
  13. 基于JAX-WS的Web Service服务端/客户端 ;JAX-WS + Spring 开发webservice
  14. C语言编译器之四,Turbo C等
  15. OSChina 周四乱弹 —— 你从小继承了程序员基因
  16. 遍历JSON的三种方法
  17. ISP中的Lens shading整理不易
  18. Python小技 不到100行代码制作各种证件照
  19. Java集合框架全解
  20. 前端学习从入门到高级全程记录之8 (PS基本使用综合案例)

热门文章

  1. 数值计算方法(二)——复化求积公式
  2. 总结-Linux基础指令
  3. 基于socket的C语言编程,C语言实现的Socket编程
  4. linux用户命令解释器,Linux下的命令解释器 ash.exe
  5. Hash哈希(hashCode、HashSet 、HashMap)
  6. 解决Mybatis的配置文件标签属性自动提示
  7. windows下使用DOS命令删除大文件
  8. Java编程思想 4th 第5章 初始化与清理
  9. Bug,昂首走进2005
  10. BTA 常问的 Java基础40道常见面试题及详细答案,java初级面试笔试题