1. ARMv7 架构与 Cortex-M 系列处理器概述
做 RTOS 移植和任务调度,第一关就是吃透处理器架构。ARMv7 是个大家族,咱们课程主要关注 Cortex-M 系列——这是跑 RTOS 的主力。我个人习惯先把寄存器模型、特权模式和异常处理这三块啃透,后面写调度器心里才有底。
1.1 寄存器模型:你手头有多少“工具”
Cortex-M3/M4 有 16 个 32 位通用寄存器(R0-R15),外加几个特殊寄存器。我刚开始学的时候总觉得寄存器太多记不住,后来发现只要分三组理解就简单了:
- R0-R12:通用寄存器,随便用。但注意 R4-R11 在函数调用里必须保存,这是 AAPCS 规定的。
- R13 (SP):栈指针。Cortex-M 有两个栈——主栈(MSP)和进程栈(PSP)。RTOS 里任务跑在 PSP,异常处理用 MSP,这个切换是调度的关键。
- R14 (LR):链接寄存器。异常返回时 LR 会被赋一个特殊值(EXC_RETURN),指示返回模式和栈选择。
- R15 (PC):程序计数器。指向当前指令地址。
核心寄存器速查表
| 寄存器 | 别名 | RTOS 中的关键作用 |
|---|---|---|
| R0-R3 | 参数/返回值 | 函数调用传参,任务入口参数 |
| R4-R11 | 高组寄存器 | 上下文切换时必须保存 |
| R13 | SP | MSP/PSP 切换,任务栈管理 |
| R14 | LR | 异常返回模式控制 |
| R15 | PC | 任务入口地址,恢复执行点 |
特殊寄存器里,xPSR(程序状态寄存器)存了条件标志、中断状态和处理器模式。我踩过一个坑:在 PendSV 里修改了 xPSR 的 T 位(Thumb 状态位),结果任务一恢复就 HardFault。嗯,ARMv7-M 强制 Thumb-2 指令集,T 位必须为 1,千万别手贱去改它。
1.2 特权模式与处理器模式
Cortex-M 只有两种模式:Handler 模式和Thread 模式。Handler 模式永远有特权,Thread 模式可以选特权或非特权。说白了,异常/中断处理跑在 Handler 模式,普通任务代码跑在 Thread 模式。
为什么 RTOS 要用非特权 Thread 模式?我做过一个产品,客户要求任务不能互相搞破坏。把任务放在非特权级,访问系统控制寄存器、操作 MPU 这些敏感操作必须通过 SVC 异常来“系统调用”,安全多了。
我的习惯:任务栈初始化时,把 CONTROL 寄存器的 nPRIV 位置 1,让任务跑在非特权级。内核代码(调度器、IPC)跑在特权级。这样任务再野也翻不了天。
1.3 异常处理与中断向量表
Cortex-M 的异常系统是硬件自动压栈的,这点比 ARM7TDMI 时代舒服太多。异常发生时,硬件自动把 R0-R3、R12、LR、PC、xPSR 压入当前栈,然后从向量表取出入口地址跳转。
向量表放在起始地址(默认 0x00000000),第一个字是 MSP 初始值,第二个字是 Reset_Handler 地址。后面依次是各种异常和中断。RTOS 里最常用的几个:
- SVCall (11 号):系统服务调用,任务主动请求内核服务
- PendSV (14 号):可挂起系统调用,上下文切换的主力
- SysTick (15 号):系统滴答定时器,时间片轮转的节拍源
我曾经踩过的坑:在 STM32F4 上做移植时,忘了把向量表重定位到 SRAM。程序在 Flash 里跑得好好的,一开中断就 HardFault。查了两天才发现——中断向量表还在 0x00000000,而我把代码搬到了 0x08000000。用 SCB->VTOR 重定位一下就好了。
1.4 异常优先级与抢占行为
Cortex-M3/M4 支持最多 256 级中断优先级(实际芯片通常只实现 16 或 32 级)。优先级数值越小,优先级越高。RTOS 里通常把 PendSV 设为最低优先级,SysTick 设为中等优先级。为什么?
你想想看:SysTick 触发时如果正在处理一个高优先级中断,PendSV 因为优先级最低会被阻塞。等所有中断处理完了,PendSV 才执行上下文切换。这样中断响应延迟最小,不会因为调度而耽误紧急事件。
典型 RTOS 优先级分配
| 异常/中断 | 优先级 | 说明 |
|---|---|---|
| PendSV | 最低(如 0xFF) | 所有中断处理完才切换 |
| SysTick | 较低(如 0xF0) | 可被高优先级中断抢占 |
| 外设中断 | 按需设置 | 实时性要求高的设高优先级 |
1.5 知识体系总览
下面这张图把本章的核心知识点串起来了。我建议你把它存下来,后面写调度器代码时对照着看。
这张图把寄存器、模式、异常三大块串起来了。你注意看底部那个优先级设计原则——这是 RTOS 调度器能正确工作的基石。PendSV 优先级最低,意味着它永远在“空闲”时才执行切换;SysTick 中等优先级,保证时间片轮转不会被完全阻塞。
避坑指南:我曾经在一个项目里把 PendSV 优先级设得比某个外设中断还高,结果那个中断处理函数里调用了 RTOS 的延时函数,直接死锁。记住:PendSV 必须是最低优先级,任何中断里调用 RTOS API 都要小心。
好了,这一章的内容就是这些。寄存器模型是你写上下文切换的“工具箱”,特权模式决定了任务的安全边界,异常处理是调度器的“发动机”。这三样吃透了,后面写调度器代码就是水到渠成的事。