核心问题:
在FreeRTOS中sysTick和PendSV的中断优先级都一样,为什么不直接在sysTick中切换任务,而在sysTick中断中调用PendSV来切换函数
核心结论
在 FreeRTOS 中,SysTick 是"决策者" (判断是否该换人),而 PendSV 是"执行者" (真正动手搬运寄存器)。不在 SysTick 中直接切换,是为了利用 ARM Cortex-M 的 "双栈机制" 和 "中断优先级脱离" 来确保系统永不崩溃。
1. 硬件基础:双栈机制 (MSP 与 PSP)
这是理解整个问题的钥匙。ARM Cortex-M 处理器有两个堆栈指针:
-
MSP (Main Stack Pointer): 专门给中断服务程序 (ISR) 使用。所有的硬中断(包括 SysTick、串口、PendSV)运行期间都强制使用 MSP。
-
PSP (Process Stack Pointer): 专门给任务 (Task) 使用。
每个任务都有PSP,但MSP只有一个,归属于内核和所有中断(如 SysTick、串口中断、定时器等)。
2. 为什么不在 SysTick 中切换?(风险点)
如果在 SysTick 中断里直接通过 C 语言或简单的汇编切换任务,会面临以下致命风险:
A. 破坏中断嵌套的完整性
SysTick 可能在其他高优先级中断(如 UART)还没退出时就被触发(虽然它在排队)。如果 SysTick 强行修改了当前正在使用的堆栈环境,当它退出时,硬件 NVIC 逻辑会发现 EXC_RETURN(中断返回状态)与当前的堆栈内容对不上,直接导致 Usage Fault。
B. 污染 MSP 堆栈
SysTick 中运行的是内核 C 代码(处理延时、调度算法),编译器会自动在 MSP 上压入局部变量。如果在这一过程中你强行改动堆栈指针,一旦此时被更高级的中断抢占,高优先级中断会在一个"坏掉"的 MSP 基础上压栈,瞬间导致系统跑飞。
3. 为什么在 PendSV 中切换是安全的?
A. 优先级最低:确保"场子干净"
FreeRTOS 将 PendSV 设为最低优先级(15)。
-
SysTick 发现需要换任务时,只是"挂起"一个 PendSV 请求。
-
PendSV 会等到所有高优先级硬中断都处理完了,才在最后时刻被触发。此时,系统处于最稳定的状态。
B. 隔离操作:只动 PSP,不动 MSP
这是最精妙的地方!PendSV 的汇编代码逻辑如下:
-
进入 PendSV: CPU 自动切换到 MSP 运行。
-
保存旧任务: 汇编指令将 R4-R11 手动压入 PSP(当前任务的私有栈)。
-
修改指针: 将
pxCurrentTCB指向新任务。 -
加载新任务: 从新任务的 PSP 中弹出 R4-R11。
-
安全打断: 如果在 PendSV 搬运寄存器时,来了一个高优先级中断。
-
高优先级中断使用 MSP。
-
它会打断 PendSV,在 MSP 上压栈,运行完后再回到 PendSV。
-
重点: 它完全不碰 PendSV 正在修改的 PSP。这就实现了"双轨并行",互不干扰。
-
FreeRTOS任务切换整体流程
在 FreeRTOS 中,任务切换(Context Switch)是一个由硬件触发、软件执行、双栈配合 的精密过程。我们可以将其拆解为:触发、保护、切换、恢复四个阶段。
1. 触发阶段 (Triggering)
任务切换通常由以下两种情况触发:
-
时间片到期: SysTick 中断发生,内核发现当前任务时间片用完。
-
任务主动挂起: 任务调用了
vTaskDelay()或等待信号量,主动让出 CPU。
核心动作: 内核向中断控制寄存器(ICSR)写入位,悬起 PendSV 中断。由于 PendSV 优先级最低,它会等待所有高优先级中断处理完后再执行。
2. 现场保护阶段 (Saving Context)
当 CPU 进入 PendSV 异常处理程序时,双栈机制开始发挥作用:
-
硬件自动压栈: 一旦进入异常,CPU 硬件会自动将当前任务的
xPSR,PC,LR,R12,R3-R0压入当前任务的 PSP(任务栈)。 -
软件手动压栈: 进入
PendSV_Handler(汇编代码)后,由于硬件只帮我们存了一部分,剩下的寄存器R4-R11需要由汇编指令手动压入 PSP。此时: CPU 正在使用 MSP 运行切换代码,但操作的对象是 PSP 指向的任务 A 的私有栈。
3. 核心切换阶段 (Switching TCB)
这是"灵魂交换"的时刻,发生在两条汇编指令之间:
-
保存旧栈顶: 将当前的 PSP 数值保存到旧任务 A 的任务控制块(TCB)的第一个成员
pxTopOfStack中。 -
选择新任务: 调用内核函数
vTaskSwitchContext,找到当前优先级最高且处于就绪态的任务(假设是任务 B)。 -
加载新栈顶: 从任务 B 的 TCB 中取出之前保存的
pxTopOfStack数值,并将其写入 CPU 的 PSP 寄存器。从此: CPU 的 PSP 正式指向了任务 B 的地盘。
4. 现场恢复阶段 (Restoring Context)
现在的目标是将任务 B 之前"存档"的状态还原回 CPU:
-
软件手动出栈: 通过汇编指令,从**新的 PSP(任务 B 的栈)**中弹出
R4-R11到 CPU 寄存器。 -
触发硬件异常返回: 执行
BX LR指令。这里的LR是一个特殊值(EXC_RETURN),告诉硬件:"中断结束,请切回线程模式并使用 PSP"。 -
硬件自动出栈: 硬件根据新的 PSP 地址,自动弹出
R0-R3,R12,LR,PC,xPSR。