本文首发于稀土掘金。该平台的作者 逐光而行 也是本人。
处理器工作模式
模式介绍
支持两种模式(线程模式、Handler模式)+两个特权等级(特权级,用户级),共能组成三种组合。如下图所示:
箭头表示复位后首先进入的状态
模式切换
为什么要有多种模式?控制对一些资源的访问
处理器工作状态
(内核最核心的两部分)
- (Thumb-2)指令工作状态
- 调试工作状态
中断
NVIC
- 支持高优先级中断抢先进行。
- 支持响应多种不同外部事件。
- 程序可在运行时对中断的优先级进行动态调整。
- 缩短了中断延迟
中断可屏蔽
- 设置BASEPRI寄存器,屏蔽优先级低于某阈值的中断;
- 设置PRIMASK和FAULTMASK寄存器进行全体封杀。
NVIC支持总共256种异常和中断(芯片设计商通过修改源代码真正生产出来的可能没这么多,常不到240个)
- 1-15对应系统异常,大于等于16为外部中断。
- 有一个非屏蔽中断
如何设置STM32中断的优先级
Cortex-M3定义了8个二进制位描述优先级,STM32只使用了4位。
| 优先级组别 | 抢占式优先级| 副优先级 |
| -------- | -----: | :----: |
| 4 | 4位/16级 | 0位/0级 |
| 3 | 3位/8级 | 1位/2级 |
| 2 | 2位/4级 | 2位/4级 |
| 1 | 1位/2级 | 3位/8级 |
| 0 | 0位/0级 | 4位/16级 |
即如下图所示:
向量表
在发生中断并作出响应时,可从表中查询与中断对应的处理例程的入口地址向量。
向量表的起始处须包含:
- MSP的初始值
- 复位向量
- NMI
- 硬fault服务例程
几点注意事项
- 地址0的值不能在运行时改变,因为存储的是引导代码
- 起始地址有要求,必须先求出系统共有多少个向量,再把该数字向上增大到2的整次幂,而起始地址必须对齐到后者的边界上。
- 允许从其他地址处开始定位各异常向量(即向量表重定位)。
这些区域可以是代码区或RAM区
- 在RAM区就可修改向量的入口地址(通过NVIC地址0xE000_ED08处的值)
中断的具体行为
前两种如图标记的:
第三种:特权handler本身的如下状态也会引起异常(中断):
- 嵌套
- 非抢占
- 两者的结合情况,如咬尾中断(后到优先级低)、迟到中断(后到优先级高)
合法的EXEC_RETURN值及功能
| EXEC_RETURN |功能|
| -------- | -----: |
| 0xFFFF_FFF1| 返回handler模式 |
| 0xFFFF_FFF9| 返回特权线程模式,并使用主堆栈 |
| 0xFFFF_FFFD | 返回用户线程模式,并使用线程堆栈 |
中断嵌套控制
一些原则:
- 系统正在处理某异常时,优先级不高于它的异常都不能抢占它,且它自己也不能抢占自己。
- 自动入栈和出栈
注意事项
- 堆栈溢出
所有服务例程都只使用主堆栈。每嵌套一级,至少再需要8个字的堆栈空间。
如果嵌套层次太深~
- 相同异常不可重入。
对于同一优先级的多个异常,只有上个实例的服务例程执行完毕后,方可继续响应新的请求。
高级中断操作
咬尾中断
- 问题背景:
处理器响应a异常时,若又发生b异常,但b的优先级不够高,则被阻塞。在中断返回过程中,POP和PUSH涉及的系统现场一致,浪费了CPU的时间。
- 解决思路:“后一个异常把前一个的尾巴咬了”
继续使用上一个异常已经push好的系统现场,本次异常完成后才恢复现场。
前后只执行一次入栈/出栈操作
晚到中断
- 时机
对某异常的相应序列还在入栈阶段,尚未执行其服务例程时,本次入栈后即执行高优先级的服务例程。