FreeRTOS小记
栈管理寄存器
MSP:主栈指针,特权模式专用(RTOS 内核、中断服务程序)
- RTOS 的内核:代码运行在 “特权模式”,所有内核操作的上下文(如调度器计算任务优先级、修改任务控制块 TCB)都存储在主栈中。
- 中断服务程序(ISR):无论当前运行的是用户任务(用 PSP)还是内核代码(用 MSP),一旦发生中断,Cortex-M 内核会自动切换到 MSP,用主栈存储中断上下文(如断点处的寄存器值、中断服务程序的局部变量)。(避免中断处理污染任务栈导致任务崩溃)
- 系统启动初始化:RTOS 启动前(如复位后),内核默认使用 MSP,主栈会先完成 “内核初始化”(如初始化 TCB 链表、堆内存、中断向量表),之后才创建第一个用户任务并切换到 PSP。
PSP:进程栈指针,用户模式专用:RTOS 用户任务的 “独立栈”
- RTOS 用户任务运行:每个用户任务创建时,RTOS 会为其分配独立的进程栈空间(大小由用户在创建任务时指定),PSP 则指向 “当前正在运行的用户任务的栈顶”
- 用户任务的上下文存储:用户任务中的函数调用、局部变量、任务级数据,均存储在该任务的进程栈中,与其他任务的栈完全独立。
- 生命周期与任务一致
- 权限受限:用户模式下使用 PSP 时,无法直接访问内核的特权资源(如内核私有寄存器、主栈数据),需通过 “系统调用(SVC 指令)” 切换到特权模式才能访问,提升系统安全性。
ARM Cortex-M寄存器组:
Cortex-M内存架构包含16个通用寄存器,其中R0-R12是13个32位的通用寄存器,另外三个寄存器是特殊用途,分别是R13(SP栈指针),R14(LR链接寄存器:保存函数或者子程序调用返回的地址),R15(PC程序计数器:下一个执行指令的地址)
任务调度
任务调度实现依赖任务状态管理、调度策略、上下文切换三大核心机制
任务控制块
每个任务都有自己的任务控制块TCB
typedef struct tskTaskControlBlock {StackType_t *pxTopOfStack; // 任务栈顶指针(PSP值,关键!)ListItem_t xStateListItem; // 用于将任务挂载到“状态列表”(就绪/阻塞等)UBaseType_t uxPriority; // 任务优先级(0~ configMAX_PRIORITIES-1)StackType_t *pxStack; // 任务栈基地址(栈空间起始位置)eTaskState eCurrentState; // 任务当前状态(就绪/运行/阻塞/挂起)TickType_t xTicksToDelay; // 阻塞延迟的时钟节拍数(用于延时唤醒)// 其他字段:任务名称、事件标志、内存管理块等
} tskTCB;
- pxTopOfStack:最关键的字段,存储任务的栈指针(PSP 值)。任务切换时,调度器通过保存 / 加载该值,实现任务上下文的切换(见下文 “上下文切换”)。
- xStateListItem:通过链表节点,将任务挂载到不同 “状态列表”(如就绪列表、阻塞列表),调度器通过遍历这些列表选择下一个运行的任务。
- eCurrentState:标记任务状态(就绪:可被调度;运行:正在占用 CPU;阻塞:等待事件 / 延时;挂起:被强制暂停),调度器只从 “就绪列表” 中选择任务。
调度策略
- 抢占式调度(任务优先级):高优先级抢占低优先级
- 时间片轮转:同优先级任务按 “时间片” 轮流运行(每个任务运行固定时间后切换)
- 合作式调度:任务主动调用 “阻塞函数”(如
vTaskDelay()
)才会释放 CPU,否则一直运行
当调度器决定切换任务时,需要完成上下文切换(保存当前任务的上下文;加载新任务的上下文)
- 保存当前任务(被切换出的任务)的上下文:将 CPU 寄存器(R0-R15、PSR 等)、栈指针(PSP)等数据存入该任务的 TCB(
pxTopOfStack
指向的栈空间)。 - 加载新任务(被切换入的任务)的上下文:从新任务的 TCB 中读取之前保存的寄存器和 PSP 值,恢复到 CPU 中,让新任务从上次暂停的位置继续运行。
Cortex-M 内核通过PendSV(可悬起系统调用)异常实现上下文切换,原因是:PendSV 的优先级可配置为最低,确保在其他中断处理完成后再执行切换,避免打断紧急操作。
- 触发 PendSV 异常
- 进入 PendSV 异常服务程序(ISR):CPU 自动进入特权模式,切换到 MSP(主栈),开始执行 PendSV 的处理函数。
- 保存当前任务的上下文:
- 手动将 CPU 寄存器(R4-R11,因为 R0-R3 等已被硬件自动入栈)压入当前任务的栈(PSP 指向的栈空间)。
- 将当前 PSP 值更新到当前任务的 TCB。
- 查找下一个要运行的任务:调度器从就绪列表中找到最高优先级的就绪任务,获取其 TCB。
- 加载新任务的上下文:
- 从TCB中读取 PSP 值,更新到 PSP 寄存器。
- 从新任务的栈中弹出之前保存的 R4-R11 寄存器值,恢复到 CPU 中。
- 退出 PendSV 异常:CPU 自动从新任务的栈中恢复 R0-R3、LR、PC 等寄存器,切换到用户模式,新任务开始运行。
关键:栈指针(PSP)的作用
- 每个任务的栈(由 PSP 指向)是 “上下文仓库”,保存了任务暂停时的所有寄存器状态和运行位置(PC 值)。
- 上下文切换的本质是 “PSP 的切换”:从当前任务的 PSP 切换到新任务的 PSP,让 CPU 认为 “新任务一直在运行”。
调度器不会 “一直运行”,而是在特定时机被触发,主要包括:
-
定时中断(系统滴答定时器,SysTick):
- RTOS 的 “心跳”(如每 1ms 触发一次),用于任务延时管理(
vTaskDelay()
)和时间片轮转。 - 中断中会检查 “是否有任务延时到期” 或 “时间片用完”,若需要切换则触发调度。
- RTOS 的 “心跳”(如每 1ms 触发一次),用于任务延时管理(
-
任务主动放弃 CPU(调用
vTaskDelay()
、xSemaphoreTake()
) -
高优先级任务就绪
-
任务优先级动态变化
RTOS的启动流程
单片机上电之后,内核复位,获取MSP的值(获取PC值);复位中断函数;启动文件:初始化MSP,初始化PC(栈顶指针),设置堆栈大小;初始化中断向量表,调用_main函数;main函数种先执行相应的硬件初始化然后调用OS初始化函数进行rtos系统初始化;接着创建开始任务,完成后,调用多任务调度函数,启动多任务函数,开始运行(优先级/时间片)进行
为什么要用RTOS
RTOS是为了管理多任务执行,提高系统的实时性和稳定性;相较于裸机开发,RTOS的核心在于CPU资源的调度,把任务拆解多个独立模块,看起来并发运行;当某一个任务卡住时整个系统不至瘫痪。
互斥量优先级翻转优先级继承
优先级翻转
高优先级任务H和低优先级任务L通过 信号量机制,共享资源。目前低优先级任务L占有资源,锁定了信号量,高优先级任务 H运行后将被阻塞,直到低优先级任务 L释放信号量后,Task H才能够退出阻塞状态继续运行。但是高优先级任务 H在等待低优先级 L释放信号量的过程中,中等优先级任务M抢占了低优先级任务L,从而延迟了信号量的释放时间,导致高优先级 H阻塞了更长时 间,这种现象称为优先级倒置或优先级反转(翻转)。
优先级继承:
当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任 务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务 会将低优先级任务的优先级提升到与自己相同的优先级。