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可靠运行的基础。