FreeRTOS --(7)任务管理之任务切换

2023-05-16

现在创建任务(xTaskCreate)、启动调度器(vTaskStartScheduler),任务控制(xTaskDelay),以及Tick 中断(xPortSysTickHandler),都分析完成了,SysTick,PendSV 中断已经使能,接下来第一个任务便可以自由的奔跑;等待下一次 SysTick 来临(1ms 后),调度器工作;

 

1、xPortSysTickHandler

SysTick 触发后,会调用到它的 ISR 函数 xPortSysTickHandler,这个函数的实现和处理器体系架构相关,定义在 port.c:


   
   
  1. void xPortSysTickHandler( void )
  2. {
  3. /* The SysTick runs at the lowest interrupt priority, so when this interrupt
  4. executes all interrupts must be unmasked. There is therefore no need to
  5. save and then restore the interrupt mask value as its value is already
  6. known - therefore the slightly faster vPortRaiseBASEPRI() function is used
  7. in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
  8. vPortRaiseBASEPRI();
  9. {
  10. /* Increment the RTOS tick. */
  11. if( xTaskIncrementTick() != pdFALSE )
  12. {
  13. /* A context switch is required. Context switching is performed in
  14. the PendSV interrupt. Pend the PendSV interrupt. */
  15. portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
  16. }
  17. }
  18. vPortClearBASEPRIFromISR();
  19. }

 

由于之前配置的 SysTick 的优先级为最低,所以此刻便有可能其他中断介入打断,所以这里配置一下 BASEPRI 寄存器,来防止被打断;在最后调用 vPortClearBASEPRIFromISR() 来恢复;

SysTick Handler 中调用 xTaskIncrementTick 来判断是否需要进行上下文切换,如果需要进行上下文切换(也就是返回 pdTRUE)的话,那么通过调用:

portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT

往 NVIC 中手动拉起一个 PendSV 中断;

先看看 xTaskIncrementTick 的内部逻辑在《FreeRTOS --(11)任务管理之系统节拍》中已经详细描述,它返回的是一个是否需要调度的标志,如果返回了 pdTRUE,则代表需要调度,拉起 PendSV;

当然,不仅仅是 SysTick 会引发上下文切换,主动调用 portYIELD 也会拉起 PendSV 使得上下文切换:


   
   
  1. /* Scheduler utilities. */
  2. #define portYIELD() \
  3. { \
  4. /* Set a PendSV to request a context switch. */ \
  5. portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
  6. \
  7. /* Barriers are normally not required but do ensure the code is completely \
  8. within the specified behaviour for the architecture. */ \
  9. __dsb( portSY_FULL_READ_WRITE ); \
  10. __isb( portSY_FULL_READ_WRITE ); \
  11. }
 

2、xPortPendSVHandler

在 PendSV 被拉起来后,如果当前没有其他中断正在执行的话,就会走到 xPortPendSVHandler,这个 ISR 在 port.c 中,我们以 Cortex-M3 为例,是一段汇编代码:


    
    
  1. __ asm void xPortPendSVHandler( void )
  2. {
  3. extern uxCriticalNesting;
  4. extern pxCurrentTCB;
  5. extern vTaskSwitchContext;
  6. PRESERVE8
  7. mrs r0, psp /* 获取进入PendSV的ISR之前的任务的 PSP. */
  8. isb /* 刷指令流水线. */
  9. ldr r3, =pxCurrentTCB /* 获取进入PendSV的ISR之前的任务的 pxCurrentTCB */
  10. ldr r2, [r3]
  11. stmdb r0!, {r4-r11} /* Save the remaining registers. */
  12. str r0, [r2] /* Save the new top of stack into the first member of the TCB. */
  13. stmdb sp!, {r3, r14}
  14. mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
  15. msr basepri, r0
  16. dsb
  17. isb
  18. bl vTaskSwitchContext
  19. mov r0, #0
  20. msr basepri, r0
  21. ldmia sp!, {r3, r14}
  22. ldr r1, [r3]
  23. ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */
  24. ldmia r0!, {r4-r11} /* Pop the registers and the critical nesting count. */
  25. msr psp, r0
  26. isb
  27. bx r14
  28. nop
  29. }

还好,这里的汇编代码都是常用的指令,理解起来并不困难,只是有一些细节需要特别注意,那么接下来就逐行分析:

0、切记,这里已经是 PendSV 的 ISR,此刻,Cortex-M3 的硬件已经完成了对 xPSR、PC、LR、R12、R0-R3 的入栈;

1、首先还是 PRESERVE8 的 8字节对齐操作;

2、既然是 PendSV 的 ISR,那么所有的通用寄存器都可以被我们使用,首先使用 MRS 指令,获取到当前的 PSP;(注意,在 ISR 中使用的是 MSP 堆栈指针,此刻获得的 PSP 是什么样的呢?)

 ​

3、获取进入 ISR 之前的 pxCurrentTCB,并保存到 R2 中;

4、手动入栈 R4~R11,在异常发生(PendSV 的时候,硬件已经自动入栈了 xPSR、PC、LR、R12、R0-R3 ,不过此刻的 R4~R11 还未改变,我们通过 stmdb 指令,从 R0 开始,将 R4~R11 手动入栈,在上下文切换之前为任务保存完整的栈信息)

stmdb r0!, {r4-r11}
    
    

这里是以 R0 的位置开始(任务的 PSP),顺序保存 R4~R11,并增加 R0;所以在执行完这条指令后,任务的堆栈信息变为:

 ​

5、此刻 R0 以及被更新到了 R11 的位置,此刻的 R2 是 pxCurrentTCB,还记得么,pxCurrentTCB 的第一个指针叫做 pxTopOfStack 保存的是任务的堆栈栈顶的指针变量也就是 PSP,接下来通过 STR 指令,将之前的任务的 pxTopOfStack 更新到现在 R0 的位置,也就是 R11,完成了手动保存并更新了上一个任务的堆栈;

str r0, [r2]
    
    

 ​

6、上一个任务已经处理完毕,接下来便是选择下一个任务了:

stmdb sp!, {r3, r14}
   
   

使用 stmdb 将 R3 和 R14(也就是 LR)入栈(注意,此刻的 sp 指的是 MSP,主堆栈指针);为啥只对这两个寄存器入栈呢?因为即将调用 C 函数,此刻的 R3 保存了 pxCurrentTCB 指针的地址,这个值在函数调用后还要用到,而 R14 就是 LR,在函数调用的时候,将被重写覆盖;

7、通过配置 CM3 的 BASEPRI 来开启临界区:


   
   
  1. mov r0 , #configMAX_SYSCALL_INTERRUPT_PRIORITY
  2. msr basepri , r0

8、通过 bl 跳转指令来调用 vTaskSwitchContext 函数:


   
   
  1. void vTaskSwitchContext( void )
  2. {
  3. if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
  4. {
  5. /* The scheduler is currently suspended - do not allow a context
  6. switch. */
  7. xYieldPending = pdTRUE;
  8. }
  9. else
  10. {
  11. xYieldPending = pdFALSE;
  12. traceTASK_SWITCHED_OUT();
  13. #if ( configGENERATE_RUN_TIME_STATS == 1 )
  14. {
  15. #ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
  16. portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
  17. #else
  18. ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
  19. #endif
  20. /* Add the amount of time the task has been running to the
  21. accumulated time so far. The time the task started running was
  22. stored in ulTaskSwitchedInTime. Note that there is no overflow
  23. protection here so count values are only valid until the timer
  24. overflows. The guard against negative values is to protect
  25. against suspect run time stat counter implementations - which
  26. are provided by the application, not the kernel. */
  27. if( ulTotalRunTime > ulTaskSwitchedInTime )
  28. {
  29. pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
  30. }
  31. else
  32. {
  33. mtCOVERAGE_TEST_MARKER();
  34. }
  35. ulTaskSwitchedInTime = ulTotalRunTime;
  36. }
  37. #endif /* configGENERATE_RUN_TIME_STATS */
  38. /* Check for stack overflow, if configured. */
  39. taskCHECK_FOR_STACK_OVERFLOW();
  40. /* Before the currently running task is switched out, save its errno. */
  41. #if( configUSE_POSIX_ERRNO == 1 )
  42. {
  43. pxCurrentTCB->iTaskErrno = FreeRTOS_errno;
  44. }
  45. #endif
  46. /* Select a new task to run using either the generic C or port
  47. optimised asm code. */
  48. taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
  49. traceTASK_SWITCHED_IN();
  50. /* After the new task is switched in, update the global errno. */
  51. #if( configUSE_POSIX_ERRNO == 1 )
  52. {
  53. FreeRTOS_errno = pxCurrentTCB->iTaskErrno;
  54. }
  55. #endif
  56. #if ( configUSE_NEWLIB_REENTRANT == 1 )
  57. {
  58. /* Switch Newlib's _impure_ptr variable to point to the _reent
  59. structure specific to this task.
  60. See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
  61. for additional information. */
  62. _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
  63. }
  64. #endif /* configUSE_NEWLIB_REENTRANT */
  65. }
  66. }

在调度器没被挂起的情况下,这个函数中,主要通过调用 taskSELECT_HIGHEST_PRIORITY_TASK 来选取最高优先级的任务,这个函数有两个实现,主要是看是否有定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏


   
   
  1. #if ( configUSE_PORT_OPTIMISED_TASK_SELECTION == 0 )
  2. #define taskSELECT_HIGHEST_PRIORITY_TASK() \
  3. { \
  4. UBaseType_t uxTopPriority = uxTopReadyPriority; \
  5. \
  6. /* Find the highest priority queue that contains ready tasks. */ \
  7. while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
  8. { \
  9. configASSERT( uxTopPriority ); \
  10. --uxTopPriority; \
  11. } \
  12. \
  13. /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \
  14. the same priority get an equal share of the processor time. */ \
  15. listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
  16. uxTopReadyPriority = uxTopPriority; \
  17. } /* taskSELECT_HIGHEST_PRIORITY_TASK */
  18. /*-----------------------------------------------------------*/
  19. #else /* configUSE_PORT_OPTIMISED_TASK_SELECTION */
  20. #define taskSELECT_HIGHEST_PRIORITY_TASK() \
  21. { \
  22. UBaseType_t uxTopPriority; \
  23. \
  24. /* Find the highest priority list that contains ready tasks. */ \
  25. portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
  26. configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
  27. listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
  28. }
  29. #endif /* configUSE_PORT_OPTIMISED_TASK_SELECTION */

在没有定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏的时候,选择最高优先级任务的方式是通过遍历的方式来获取最高优先级的任务;

如果定义了 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个宏的时候,就调用 portGET_HIGHEST_PRIORITY 来获得最高优先级任务链表,他是和处理器架构体系相关的,也就是说,你的硬件如果支持捷径,那么就用你的方法获取最高优先级;在 Cortex-M3 中,是有一个 CLZ 指令,这个指令用来计算一个变量从最高位开始的连续零的个数,比如,uxTopReadyPriority为 0x09(二进制为:0000 0000 0000 0000 0000 0000 0000 1001),即bit3和bit0为1,表示存在优先级为0和3的就绪任务。则__clz( (uxTopReadyPriority)的值为28,uxTopPriority =31-28=3,即优先级为3的任务是就绪态最高优先级任务。下面的代码跟通用方法一样,调用宏listGET_OWNER_OF_NEXT_ENTRY获取最高优先级列表中的下一个列表项,并从该列表项中获取任务TCB指针赋给变量pxCurrentTCB。

所以在 CM3 的 portmacro.h 中:


   
   
  1. #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority =
  2. ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

但是定义 configUSE_PORT_OPTIMISED_TASK_SELECTION 这个需要注意一点,它不能够支持优先级的个数超过 32 个,因为 CLZ 最大就处理 32 bit,所以这个表示优先级的 bitmap 最多 32,也就是优先级限定在 32 个以内;

9、获取到最大优先级的任务后,将其赋值给了 pxCurrentTCB,返回汇编代码;

10、离开临界区


   
   
  1. mov r0 , #0
  2. msr basepri , r0

为何此时能够离开临界区呢?因为前一个任务已经完好的保存,下一个任务也已经选择出来;此刻被高优先级中断嵌套,其实不在有影响,只是上下文切换的时间被延时;

11、将 R3 和 R14 出栈,R3 存储了 pxCurrentTCB 地址,R14 存储了进入 ISR 那刻的 LR(0xFFFF_FFFD);

12、此刻 R3 指向的 pxCurrentTCB 地址,已经是更新到最高优先级的 TCB 的地址,TCB 的第一个元素是这个任务的栈顶指针 pxTopOfStack,获取该任务的栈顶指针到 R0:


   
   
  1. ldr r1, [r3]
  2. ldr r0, [r1]

13、既然获得到了下一个即将被调度的任务的栈顶指针,那么首先是将它的 R4~R11 出栈(这部分要手动做,因为硬件只会去做xPSR、PC、LR、R12、R0-R3);

ldmia r0!, {r4-r11}
   
   

此刻便可以更新 PSP了,更新 PSP 后,如果返回 ISR 的话,硬件会自动出栈 xPSR、PC、LR、R12、R0-R3;


   
   
  1. msr psp, r0
  2. isb

最后调用 bx R14 返回中断服务程序,完成整个调度:

bx r14
   
   

此刻硬件便会将 xPSR、PC、LR、R12、R0-R3 自动出栈,下一个任务跑起来了;

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS --(7)任务管理之任务切换 的相关文章

  • 【FreeRtos学习笔记】STM32 CubeMx——Timers(定时器)

    目录 1 软件定时器 2 示例程序 2 1 例程功能 2 2 步骤 2 3 实验结果 2 4 函数讲解 1 软件定时器 定时器是MCU常用的外设 我们在学习各种单片机时必然会学习它的硬件定时器 但是 MCU自带的硬件定时器资源是有限的 而且
  • FreeRTOS内核配置说明---FreeRTOS Kernel V10.2.1

    FreeRTOS内核是高度可定制的 使用配置文件FreeRTOSConfig h进行定制 每个FreeRTOS应用都必须包含这个头文件 用户根据实际应用来裁剪定制FreeRTOS内核 这个配置文件是针对用户程序的 而非内核 因此配置文件一般
  • FreeRTOS快速上手

    FreeRTOS使用 一 源码下载和移植文件提取 1 1 源码下载 在网站https sourceforge net projects freertos 可以找到freertos最新的源码 1 2 移植文件提取 根据第一步 我们会得到一个f
  • STM32F103移植FreeRTOS必须搞明白的系列知识---3(堆栈)

    STM32F103移植FreeRTOS必须搞明白的系列知识 1 Cortex CM3中断优先级 STM32F103移植FreeRTOS必须搞明白的系列知识 2 FreeRTOS任务优先级 STM32F103移植FreeRTOS必须搞明白的系
  • FreeRTOS-内核控制函数

    FreeRTOS 内核控制函数 FreeRTOS中有一些内核函数 一般来说这些内核函数在应用层不会使用 但是内核控制函数是理解FreeRTOS中断的基础 接下来我们逐一分析这些内核函数 taskYIELD 该函数的作用是进行任务切换 这是一
  • FreeRTOS系列

    1 RTOS简介 RTOS全称为 Real Time Operation System 即实时操作系统 RTOS强调的是实时性 又分为硬实时和软实时 硬实时要求在规定的时间内必须完成操作 不允许超时 而软实时里对处理过程超时的要求则没有很严
  • FreeRTOS ------- 任务(task)

    在学习RTOS的时候 个人觉得带着问题去学习 会了解到更多 1 什么是任务 在FreeRTOS中 每个执行线程都被称为 任务 每个任务都是在自己权限范围内的一个小程序 其具有程序入口每个任务都是在自己权限范围内的一个小程序 其具有程序入口通
  • freeRTOS手册 第六章 . 中断管理

    如果我对本翻译内容享有所有权 允许任何人复制使用本文章 不会收取任何费用 如有平台向你收取费用与本人无任何关系 第六章 中断管理 章节介绍和范围 事件 嵌入式实时系统必需对环境中的事件做出响应 比如 外部网络设备收到一个发送给TCP IP栈
  • 基于HAL库的FREERTOS-----------三.队列

    一 队列简介 在实际的应用中 常常会遇到一个任务或者中断服务需要和另外一个任务进行 沟通交流 这个 沟通交流 的过程其实就是消息传递的过程 在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式 但是如果在使用操作系统的应用中用
  • FreeRTOS笔记(十)中断

    中断 当CPU在执行某一事件A时 发生另外一个更重要紧急的事件B请求CPU去处理 产生了中断 于是CPU暂时中断当前正在执行的事件A任务而对对事件B进行处理 CPU处理完事件B后再返回之前中断的位置继续执行原来的事件A 这一过程统称为中断
  • FreeRTOS死机原因

    1 中断回调函数中没有使用中断级API xxFromISR 函数 xSemaphoreGiveFromISR uart busy HighterTask 正确 xSemaphoreGive uart busy 错误 2 比configMAX
  • FreeRTOS学习---“定时器”篇

    总目录 FreeRTOS学习 任务 篇 FreeRTOS学习 消息队列 篇 FreeRTOS学习 信号量 篇 FreeRTOS学习 事件组 篇 FreeRTOS学习 定时器 篇 FreeRTOS提供了一种软件定时器 用来快速实现一些周期性的
  • freeRTOS出现任务卡死的情况。

    最近在做一个产品二代升级的项目 代码是上一任工程师留下的 很多BUG 而且融合了HAL库和LL库 以及github上下载的GSM源码 很不好用 我这边是将2G模块换成了4G 且添加了单独的BLE模块 因此只在源码的基础上 去除2G和BLE代
  • 13-FreeRTOS任务创建与删除

    任务创建和删除API函数位于文件task c中 需要包含task h头文件 task h里面包函数任务的类型函数 例如 对xTaskCreate的调用 通过指针方式 返回一个TaskHandle t 变量 然后可将该变量用vTaskDele
  • FreeRTOS之系统配置

    1 FreeRTOS的系统配置文件为FreeRTOSConfig h 在此配置文件中可以完成FreeRTOS的裁剪和配置 在官方的demo中 每个工程都有一个该文件 2 先说一下 INCLUDE 开始的宏 使用 INCLUDE 开头的宏用来
  • FreeRTOS多任务调度器基础

    Cortex M4中SysTick调度器核心 Cortex M4中的中断管理 Cortex M4中影子栈指针 Cortex M4中SVC和PendSV异常 1 Cortex M4中SysTick调度器核心 systick每一次中断都会触发内
  • FreeRTOSConfig.h 配置优化及深入

    本篇目标 基于上一篇的移植freertos stm32f4 freertos 上 修改 FreeRTOSConfig h 文件的相关配置来优化辅助 FreeRtos 的使用 并且建立一些基本功能 信号量 消息地列等 的简单应用位于 stm3
  • FreeRTOS 配置TICK_RATE_HZ

    我使用的是带有 5 4 版 FreeRTOS 的 MSP430f5438 我有一个有趣的问题 我无法弄清楚 基本上 当我将 configTICK RATE HZ 设置为不同的值时 LED 闪烁得更快或更慢 它应该保持相同的速率 我将 con
  • GNU Arm Cortex m4 上的 C++ 异常处理程序与 freertos

    2016 年 12 月更新现在还有一个关于此行为的最小示例 https community nxp com message 862676 https community nxp com message 862676 我正在使用带有 free
  • 有关 CMake 错误的问题:没有为目标提供源

    我正在尝试使用 cmake 和 eclipse 将 FreeRtos 添加到我的项目中 但出现错误 我运行的是 debian 10 我的 cmake 版本是 3 13 4 cmake 的文件可以在以下位置找到这个 git 仓库 https

随机推荐