Freertos中PendSV与sysTick

核心问题:

在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 的汇编代码逻辑如下:

  1. 进入 PendSV: CPU 自动切换到 MSP 运行。

  2. 保存旧任务: 汇编指令将 R4-R11 手动压入 PSP(当前任务的私有栈)。

  3. 修改指针:pxCurrentTCB 指向新任务。

  4. 加载新任务: 从新任务的 PSP 中弹出 R4-R11。

  5. 安全打断: 如果在 PendSV 搬运寄存器时,来了一个高优先级中断。

    • 高优先级中断使用 MSP。

    • 它会打断 PendSV,在 MSP 上压栈,运行完后再回到 PendSV。

    • 重点: 它完全不碰 PendSV 正在修改的 PSP。这就实现了"双轨并行",互不干扰。


FreeRTOS任务切换整体流程

在 FreeRTOS 中,任务切换(Context Switch)是一个由硬件触发、软件执行、双栈配合 的精密过程。我们可以将其拆解为:触发、保护、切换、恢复四个阶段。


1. 触发阶段 (Triggering)

任务切换通常由以下两种情况触发:

  • 时间片到期: SysTick 中断发生,内核发现当前任务时间片用完。

  • 任务主动挂起: 任务调用了 vTaskDelay() 或等待信号量,主动让出 CPU。

核心动作: 内核向中断控制寄存器(ICSR)写入位,悬起 PendSV 中断。由于 PendSV 优先级最低,它会等待所有高优先级中断处理完后再执行。


2. 现场保护阶段 (Saving Context)

当 CPU 进入 PendSV 异常处理程序时,双栈机制开始发挥作用:

  1. 硬件自动压栈: 一旦进入异常,CPU 硬件会自动将当前任务的 xPSR, PC, LR, R12, R3-R0 压入当前任务的 PSP(任务栈)

  2. 软件手动压栈: 进入 PendSV_Handler(汇编代码)后,由于硬件只帮我们存了一部分,剩下的寄存器 R4-R11 需要由汇编指令手动压入 PSP

    此时: CPU 正在使用 MSP 运行切换代码,但操作的对象是 PSP 指向的任务 A 的私有栈。


3. 核心切换阶段 (Switching TCB)

这是"灵魂交换"的时刻,发生在两条汇编指令之间:

  1. 保存旧栈顶: 将当前的 PSP 数值保存到旧任务 A 的任务控制块(TCB)的第一个成员 pxTopOfStack 中。

  2. 选择新任务: 调用内核函数 vTaskSwitchContext,找到当前优先级最高且处于就绪态的任务(假设是任务 B)。

  3. 加载新栈顶: 从任务 B 的 TCB 中取出之前保存的 pxTopOfStack 数值,并将其写入 CPU 的 PSP 寄存器

    从此: CPU 的 PSP 正式指向了任务 B 的地盘。


4. 现场恢复阶段 (Restoring Context)

现在的目标是将任务 B 之前"存档"的状态还原回 CPU:

  1. 软件手动出栈: 通过汇编指令,从**新的 PSP(任务 B 的栈)**中弹出 R4-R11 到 CPU 寄存器。

  2. 触发硬件异常返回: 执行 BX LR 指令。这里的 LR 是一个特殊值(EXC_RETURN),告诉硬件:"中断结束,请切回线程模式并使用 PSP"。

  3. 硬件自动出栈: 硬件根据新的 PSP 地址,自动弹出 R0-R3, R12, LR, PC, xPSR

相关推荐
jghhh012 小时前
带红外抄板和LCD显示的单相电能表设计
stm32·单片机·嵌入式硬件
wggmrlee3 小时前
GD32 vs STM32
单片机·嵌入式硬件
czhaii3 小时前
STM32 F103 Altium一键下载PCB图
stm32·单片机·嵌入式硬件
雾削木4 小时前
基于STM32F411RET6 + 双路MB85RS2MT的铁电U盘
stm32·单片机·嵌入式硬件
笨笨饿4 小时前
33_顺序表(待完善)
linux·服务器·c语言·嵌入式硬件·算法·学习方法
点灯小铭4 小时前
基于单片机的多路温湿度采集与WIFI智能报警控制系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
嵌入式×边缘AI:打怪升级日志4 小时前
MX6ULL 的 GPIO 操作方法(保姆级教程)
stm32·单片机·嵌入式硬件
点灯小铭4 小时前
基于单片机的球类比赛专用计分与暂停管理系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
自小吃多6 小时前
TMC220X芯片 串口工具连接交互
笔记·嵌入式硬件