Tasks Switch

目录

任务栈的基本结构

RTOS中每个任务都有独立的栈空间,栈底位于高地址,栈向低地址增长:

典型任务栈内存布局:
+------------------+ ← 栈起始地址 (高地址)
|   栈保护区域     |
|   (魔数填充)     |
+------------------+
|   未使用栈空间   |
|                 |
+------------------+
|   函数调用帧     |
|   局部变量       |
+------------------+
|   任务上下文     |
|   (保存的寄存器) |
+------------------+ ← 当前栈指针SP位置 (低地址)

任务上下文

任务上下文包含处理器完整状态,分为硬件自动保存和软件手动保存两部分:

硬件自动保存的寄存器

  • R0-R3:参数寄存器
  • R12:临时寄存器
  • LR(R14):链接寄存器
  • PC(R15):程序计数器
  • xPSR:程序状态寄存器

软件手动保存的寄存器

  • R4-R11:通用寄存器
  • 可选:浮点寄存器、特殊功能寄存器

任务切换触发机制

任务切换由多种条件触发,完整的切换流程如下:

任务切换完整流程:
+------------------+
|  任务A正常运行   |
|  SP_A指向任务A栈 |
+------------------+
         |
         ↓ (中断/系统调用触发)
+------------------+
|  硬件自动保存    |
|  R0-R3, R12,     |
|  LR, PC, xPSR    |
+------------------+
         |
         ↓ (进入PendSV异常)
+------------------+
|  软件手动保存    |
|  R4-R11到任务A栈 |
+------------------+
         |

+------------------+
|  更新TCB_A中的   |
|  栈指针SP_A      |
+------------------+
         |

+------------------+
|  调度器选择      |
|  下一个任务B     |
+------------------+
         |

+------------------+
|  从TCB_B加载     |
|  栈指针SP_B      |
+------------------+
         |

+------------------+
|  软件手动恢复    |
|  R4-R11从任务B栈 |
+------------------+
         |

+------------------+
|  更新PSP为SP_B   |
+------------------+
         |
         ↓ (异常返回)
+------------------+
|  硬件自动恢复    |
|  R0-R3, R12,     |
|  LR, PC, xPSR    |
+------------------+
         |

+------------------+
|  任务B继续运行   |
|  SP_B指向任务B栈 |
+------------------+

完整的栈帧结构

任务切换时的完整栈帧结构:

完整任务栈帧布局:
+------------------+ ← 栈底 (高地址)
|   栈保护区域     |
|   (0xDEADBEEF等) |
+------------------+
|   历史栈数据     |
|   函数调用帧     |
|   局部变量等      |
+------------------+
|  手动保存寄存器   | ← 软件保存区域开始
|      R11        |
+------------------+
|      R10        |
+------------------+
|      R9         |
+------------------+
|      R8         |
+------------------+
|      R7         |
+------------------+
|      R6         |
+------------------+
|      R5         |
+------------------+
|      R4         |
+------------------+ ← 手动保存结束
|  自动保存寄存器   | ← 硬件自动保存区域开始
|      R0         |
+------------------+
|      R1         |
+------------------+
|      R2         |
+------------------+
|      R3         |
+------------------+
|      R12        |
+------------------+
|      LR         |
+------------------+
|      PC         |
+------------------+
|      xPSR       |
+------------------+ ← 当前PSP指向的位置 (栈顶)

上下文保存过程详解

硬件自动保存阶段

当中断或异常发生时,硬件自动保存部分寄存器:

硬件自动保存过程:
原始栈状态:
+------------------+ ← PSP
|   任务正常数据   |
|                 |
+------------------+

硬件自动保存后:
+------------------+ ← 原始PSP
|   任务正常数据   |
|                 |
+------------------+
|      xPSR       | ← 硬件自动保存开始
+------------------+
|       PC        |
+------------------+
|       LR        |
+------------------+
|      R12        |
+------------------+
|       R3        |
+------------------+
|       R2        |
+------------------+
|       R1        |
+------------------+
|       R0        |
+------------------+ ← 新的PSP (硬件保存后栈顶)

软件手动保存阶段

在PendSV异常处理程序中手动保存剩余寄存器:

PendSV_Handler:
    ; 获取当前任务栈指针
    MRS R0, PSP                   ; R0 = 当前PSP值

    ; 检查是否是第一次任务切换
    CBZ R0, skip_save             ; 如果PSP为0跳过保存

    ; 手动保存R4-R11到任务栈
    STMDB R0!, {R4-R11}           ; 递减存储向低地址保存

    ; 更新TCB中的栈指针
    LDR R1, =CurrentTCB
    LDR R2, [R1]
    STR R0, [R2]                  ; 保存新PSP到TCB

skip_save:
    ; 继续执行任务切换...

保存后的栈状态变化:

软件手动保存后的栈:
+------------------+ ← 原始PSP
|   任务正常数据   |
|                 |
+------------------+
|      xPSR       |
+------------------+
|       PC        |
+------------------+
|       LR        |
+------------------+
|      R12        |
+------------------+
|       R3        |
+------------------+
|       R2        |
+------------------+
|       R1        |
+------------------+
|       R0        |
+------------------+ ← 硬件保存后位置
|       R4        | ← 软件手动保存开始
+------------------+
|       R5        |
+------------------+
|       R6        |
+------------------+
|       R7        |
+------------------+
|       R8        |
+------------------+
|       R9        |
+------------------+
|      R10        |
+------------------+
|      R11        |
+------------------+ ← 新的PSP (软件保存后栈顶)

上下文恢复过程详解

上下文恢复是保存过程的精确逆序:

软件手动恢复阶段

PendSV_Handler_Restore:
    ; 切换到新任务
    LDR R1, =NextTCB
    LDR R2, [R1]
    LDR R0, [R2]                 ; 获取新任务的栈指针

    ; 手动恢复R4-R11
    LDMIA R0!, {R4-R11}          ; 递增加载从栈中恢复寄存器

    ; 更新PSP指向硬件自动保存区域
    MSR PSP, R0

    ; 准备异常返回
    ORR LR, LR, #0x04           ; 确保返回时使用PSP
    BX LR                        ; 异常返回触发硬件自动恢复

恢复过程中的栈状态变化:

上下文恢复过程:
恢复前的栈状态:
+------------------+ ← 栈底
|   任务B历史数据   |
|                 |
+------------------+
|      xPSR       |
+------------------+
|       PC        |
+------------------+
|       LR        |
+------------------+
|      R12        |
+------------------+
|       R3        |
+------------------+
|       R2        |
+------------------+
|       R1        |
+------------------+
|       R0        |
+------------------+
|       R4        |
+------------------+
|       R5        |
+------------------+
|       R6        |
+------------------+
|       R7        |
+------------------+
|       R8        |
+------------------+
|       R9        |
+------------------+
|      R10        |
+------------------+
|      R11        |
+------------------+ ← 恢复前PSP指向的位置

恢复R4-R11后:
+------------------+ ← 栈底
|   任务B历史数据   |
|                 |
+------------------+
|      xPSR       |
+------------------+
|       PC        |
+------------------+
|       LR        |
+------------------+
|      R12        |
+------------------+
|       R3        |
+------------------+
|       R2        |
+------------------+
|       R1        |
+------------------+
|       R0        |
+------------------+ ← 恢复后PSP指向的位置
|   已弹出的R4-R11  | (数据仍在栈中,但寄存器已恢复)
+------------------+

硬件自动恢复阶段

异常返回时,硬件自动恢复剩余寄存器:

硬件自动恢复过程:
异常返回时硬件自动:
1. 从PSP指向的位置开始弹出寄存器
2. 依次恢复R0, R1, R2, R3, R12, LR, PC, xPSR
3. PSP自动更新到原始位置

恢复后的栈状态:
+------------------+ ← 栈底
|   任务B历史数据   |
|                 |
+------------------+ ← 恢复后PSP指向的位置
|   已弹出的所有     |
|   上下文数据      |
+------------------+

完整的汇编实现

基于ARM Cortex-M架构的完整任务切换代码:

; 常量定义
CurrentTCB    DCD 0    ; 当前任务TCB指针
NextTCB       DCD 0    ; 下一个任务TCB指针

; PendSV异常处理程序 - 任务切换核心
PendSV_Handler:
    ; 禁用中断确保原子操作
    CPSID I

    ; 检查是否需要保存当前任务上下文
    MRS R0, PSP                   ; 获取当前任务栈指针
    CBZ R0, PendSV_Restore        ; 如果PSP为0是第一次切换跳过保存

    ; 保存当前任务上下文
    ; 硬件已自动保存: xPSR, PC, LR, R12, R0-R3
    ; 需要手动保存: R4-R11
    STMDB R0!, {R4-R11}           ; 保存R4-R11到任务栈

    ; 更新当前任务的栈指针到TCB
    LDR R1, =CurrentTCB
    LDR R2, [R1]
    STR R0, [R2]                  ; 保存更新后的PSP到TCB

PendSV_Restore:
    ; 恢复下一个任务的上下文
    LDR R1, =NextTCB
    LDR R2, [R1]
    LDR R0, [R2]                  ; 获取新任务的栈指针

    ; 手动恢复R4-R11
    LDMIA R0!, {R4-R11}           ; 从栈中恢复R4-R11

    ; 更新PSP为新任务的栈指针
    MSR PSP, R0

    ; 更新CurrentTCB为NextTCB
    LDR R1, =CurrentTCB
    LDR R2, =NextTCB
    LDR R3, [R2]
    STR R3, [R1]

    ; 启用中断
    CPSIE I

    ; 异常返回硬件将自动恢复: xPSR, PC, LR, R12, R0-R3
    ORR LR, LR, #0x04             ; 确保返回时使用PSP
    BX LR                         ; 返回到新任务

栈指针管理机制

任务控制块(TCB)与任务栈的关系:

任务管理数据结构:
TCB结构:
+------------------+
|  任务ID         |
|  任务状态       |
|  优先级         |
|  栈起始地址     | ---→ +------------------+ ← 栈底
|  栈大小         |      |   任务栈空间     |
|  当前栈指针SP   | ---→ |                 |
|  等待事件       |      |                 |
|  时间片计数器   |      |                 |
+------------------+      +------------------+ ← SP指向的位置

任务切换时的栈指针更新:

栈指针切换过程:
任务A → 任务B切换:

任务A运行中:
TCB_A.SP → +------------------+ ← PSP
           |   任务A栈数据     |
           |                 |
           +------------------+

保存上下文后:
TCB_A.SP → +------------------+ ← 新PSP (保存后)
           |   任务A上下文     |
           |                 |
           +------------------+

切换到任务B:
TCB_B.SP → +------------------+ ← 新PSP (恢复前)
           |   任务B上下文     |
           |                 |
           +------------------+

恢复上下文后:
TCB_B.SP → +------------------+ ← PSP (恢复后)
           |   任务B栈数据     |
           |                 |
           +------------------+

不同架构的差异处理

各架构上下文保存的差异:

特性 ARM Cortex-M x86 RISC-V
自动保存寄存器 xPSR, PC, LR, R12, R0-R3 SS, SP, FLAGS, CS, IP PC, 状态寄存器
手动保存寄存器 R4-R11 通用寄存器 调用者保存寄存器
栈增长方向 满递减 满递减 可配置
异常返回指令 BX LR IRET MRET

栈溢出检测与优化

栈使用监控和优化策略:

栈使用分析:
+------------------+ ← 栈底
|   栈保护区域     |
|   (魔数检测)     |
+------------------+ ← 栈边界
|   未使用空间     |
|                 |
+------------------+ ← 最大历史使用水位
|   已使用空间     |
|                 |
+------------------+ ← 当前SP位置
|   安全边际       |
+------------------+ ← 栈顶

优化策略:
• 根据最大使用水位调整栈大小
• 设置栈保护区域检测溢出
• 避免深度递归和大型局部变量
• 使用静态分析工具确定合适栈大小

这样的设计确保了任务切换时上下文的完整保存和精确恢复,是RTOS可靠运行的基础。