Cotex-M3系统异常管理机制


layout: post

title: "异常"

date: 2024-1-16 15:39:08 +0800

tags: Cotex-M3 Cotex-M3权威指南


异常

编号为1-15的对应系统异常,大于等于16的则全是外部中断。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。

所有能打断正常执行流的事件都称为异常。

在NVIC的中断控制及状态寄存器中,有一个VECTACTIVE位段;另外,还有一个特殊功能寄存器IPSR。在它们二者的里面,都记录了当前正服务的异常,给出了它的编号。

如果一个发生的异常不能被即刻响应,就称它被"悬起"(pending)。CM3则由NVIC的悬起状态寄存器来解决这个问题。于是,哪怕设备在后来已经释放了请求信号,曾经的中断请求也不会错失。

优先级定义

优先级的数值越小,则优先级越高。CM3支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常

原则上,CM3支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢 占(128的来历请见下文分解------译注)。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级等。

  • 使用MSB的原因

通过让优先级以MSB对齐,可以简化程序的跨器件移植。比如,如果一个程序早先在支持4位优先级的器件上运行,在移植到只支持3位优先级的器件后,其功能不受影响。但若是对齐到LSB,则会使MSB丢失,导致数值大于7的低优先级一下子升高了,甚至会发生"优先级反转":使它高于小于等于7的优先级。如,8号优先级因为损失了MSB,现在反而变成0号了;而15号优先级则变成7号优先级,它则不会影响0-6号优先级,使得这个问题更隐蔽。

子优先级和抢占优先级

MSB所在的位段(左边的)对应抢占优先级,而LSB所在的位段(右边的)对应子优先级

NVIC中有一个寄存器是"应用程序中断及复位控制寄存器"

0xE000_ED0C

子优先级至少是1个位。因此抢占优先级最多是7个位,这就造成了最多只有128级抢占的现象

CM3允许从比特7处分组,此时所有的位都表达子优先级,没有任何位表达抢占优先级,因而所有优先级可编程的异常之间就不会发生抢占------相当于在它们之中除能了CM3的中断嵌套机制。当然还有凌驾于法律之上的三位老大:复位,NMI和硬fault。它们无论何时出现,都立即无条件抢占所有优先级可编程的"平民异常"。

虽然[4:0]未使用,却允许从它们中分组。例如,如果优先级组为1,则所有可用的8个优先级都是抢占优先级

向量表

CM3需要定位其服务例程的入口地址。这些入口地址存储在所谓的"(异常)向量表"中。缺省情况下,CM3认为该表位于零地址处,且各向量占用4字节。

NVIC中有一个寄存器,称为"向量表偏移量寄存器"(在地址0xE000_ED08处),通过修改它的值就能重定位向量表。

向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上"圆整"到2的整次幂,而起始地址必须对齐到后者的边界上。

中断悬起

当中断输入脚被置为有效(asser)t后,该中断就被悬起。即使后来中断源撤消了中断请求,已经被标记成悬起的中断也被记录下来。到了系统中它的优先级最高的时候,就会得到响应。

但是,如果在某个中断得到响应之前,其悬起状态被清除了(例如,在PRIMASK或FAULTMASK置位的时候软件清除了悬起状态标志),则中断被取消

在一个中断活跃后,直到其服务例程执行完毕,并且返回(亦称为中断退出)后,才能对该中断的新请求予以响应(单实例)。

新请求在得到响应时,亦是由硬件自动清零其悬起标志位。

Fault类型的异常

  • 总线Fault
  • 存储器Fault
  • 用法Fault
  • 硬Fault

总线Fault

AHB接口传输数据的时候如果回复了一个错误信号, 会产生一个Fault

欲使能总线fault服务例程,需要在NVIC的"系统Handler控制及状态寄存器"中置位BUSFAULTENA位。要注意的是:在使能之前,总线fault服务例程的入口地址必须已经在向量表中配置好,否则就成了作法自毙------程序可能跑飞。

如果总线fault被除能,或者总线fault是被某同级或更高优先级异常的服务例程引发的,则总线fault被迫成为"硬伤"------上访成硬fault,使得最后执行的是硬fault的服务例程

对于精确的总线fault(见下框说明),肇事指令的地址被压在堆栈中。如果BFSR中的BFARVALID位为1,还可以找出是在访问哪块存储器时产生该总线fault的------该存储器的地址被放到"总线fault地址寄存器(BFAR)"中

在不精确的总线faults中,导致此fault的指令早已完成了。例如,缓冲区写入。启动缓冲区写入的指令不知何时已经执行了,但是写到中途时触发了总线fault。此时,肇事指令早已"逃逸"------在若干个时钟周期就执行过了,而且不能确定是具体几个周期之前,CM3也不会记录这期间的程序跳转动作。因此无法确认"肇事者",故而该fault是不精确的。精确的总线fault则不同,它是被最后一个完成的操作触发的。例如,一个存储器读取导致的fault总是精确的,因为该指令必须等全部读完时才算执行完成。这样,任何在读取过程中发生的fault总能落在该指令的头上。

BFSR寄存器的程序员模型如下所示:它是一个8位的寄存器,并且可以使用字传送和字节传送来读取它。如果以字方式访问,地址是0xE000_ED28,并且第2个字节有效;如果以字节方式访问,则地址直接就是0xE000_ED29,

存储器管理Fault

其诱因常常是某次访问触犯了MPU设置的保护规范。在不可执行的存储器区域试图取指,也会触发一个MemManage fault

如果MemMange fault是被同级或高优先级异常的服务例程引发的,或者MemManage fault被除能,则和总线fault一样:上访成硬fault,最终执行的是硬fault的服务例程

如果硬fault服务例程或NMI服务例程的执行也导致了MemManage fault,那就不可救要了------内核将被锁定。

在NVIC"系统handler控制及状态寄存器"中的使能位是MEMFAULTENA

用法Fault

如果需要严格要求程序的质量,还可以让CM3在遇到除数为零的时候,以及遇到未对齐访问的时候也产生用法fault。在NVIC中有两个控制位分别与它们对应。通过设置这两个控制位,就可以激活它们。

没有使能的时候会导致结果和上面一样

在NVIC"系统handler控制及状态寄存器"中的使能位是USGFAULTENA。

NVIC中有一个"用法fault状态寄存器(UFSR)

硬Fault

是上文讨论的总线fault、存储器管理fault以及用法fault上访的结果

另外,在取向量(异常处理时对异常向量表的读取)时产生的总线fault也按硬fault处理。

fault状态寄存器(HFSR),它指出产生硬fault的原因。

应对Fault

复位。这也是最后一招。通过设置NVIC"应用程序中断及复位控制寄存器"中的VECTRESET位,将只复位处理器内核而不复位其它片上设施。取决于芯片的复位设计,有些CM3芯片可以使用该寄存器的SYSRESETREQ位来复位。这种只限于内核中的复位不会殃及其它的系统部件。

恢复:在一些场合下,还是有希望解决产生fault的问题的。例如,如果程序尝试访问了协处理器,可以通过一个协处理器的软件模拟器来解决此问题------当然是以牺牲性能为代价的,要不然还要硬件加速干啥。

中止相关任务:如果系统运行了一个RTOS,则相关的任务可以被终结或者重新开始。

各个fault状态寄存器(FSRs)都保持住它们的状态,直到手工清除。Fault服务例程在处理了相应的fault后不要忘记清除这些状态,否则如果下次又有新的fault发生,服务例程在检视fault源时,又将看到早先已经处理的fault遗留下来的状态标志。此时,将无法判断哪个fault是新发生的。FSRs采用一个写时清除机制(写1时清除)。芯片厂商也可以再添加自己的FSR,以表示其它fault情况。

SVC和PendSV

SVC: 系统服务中断

PendSV: 可悬起系统调用

SVC函数

SVC用于产生系统函数的调用请求。例如,操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用SVC发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。

使用户程序从控制硬件的繁文缛节中解脱出来,而是由OS负责控制具体的硬件

OS的代码可以经过充分的测试,从而能使系统更加健壮和可靠

它使用户程序无需在特权级下执行,用户程序无需承担因误操作而瘫痪整个系统的风险

通过SVC的机制,还让用户程序变得与硬件无关,因此在开发应用程序时无需了解硬件的操作细节,从而简化了开发的难度和繁琐度,并且使应用程序跨硬件平台移植成为可能

SVC异常通过执行"SVC "指令来产生。该指令需要一个立即数,充当系统调用代号。SVC异常服务例程稍后会提取出此代号,从而获知本次调用的具体要求,再调用相应的服务函数

assembly 复制代码
SVC       0x3   ; 调用3号系统服务

上次执行的SVC指令地址可以根据自动入栈的返回地址计算出。找到了SVC指令后,就可以读取该SVC指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。

如果用户程序使用的是PSP,服务例程还需要先执行MRS Rn, PSP指令来获取应用程序的堆栈指针。通过分析LR的值,可以获知在SVC指令执行时,正在使用哪个堆栈

我们不能在SVC服务例程中嵌套使用SVC指令, 这种作法会产生一个用法fault。同理,在NMI服务例程中也不得使用SVC,否则将触发硬fault。

对于SVC异常来说,若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬fault

PendSV函数

PendSV则不同,它是可以像普通的中断一样被悬起的(不像SVC那样会上访)。

OS可以利用它"缓期执行"一个异常------直到其它重要的任务完成后才执行动作。悬起PendSV 的方法是:手工往NVIC的PendSV悬起寄存器中写1。悬起后,如果优先级不够高,则将缓期等待执行。

如果OS在某中断活跃时尝试切入线程模式,将触犯用法fault异常。

期的OS大多会检测当前是否有中断在活跃中,只有在无任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了IRQ,则本次SysTick在执行后不得作上下文切换,只能等待下一次SysTick异常),尤其是当某中断源的频率和SysTick异常的频率比较接近时,会发生"共振",使上下文切换迟迟不能进行

PendSV异常会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行。为实现这个机制,需要把PendSV编程为最低优先级的异常。

相关推荐
亦世凡华、15 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
落樱坠入星野1 小时前
拿下阿里云之后如何在本地运行镜像进行分析
经验分享·安全·网络安全·阿里云·云计算
weixin_518285053 小时前
深度学习笔记11-神经网络
笔记·深度学习·神经网络
龙鸣丿6 小时前
Linux基础学习笔记
linux·笔记·学习
Nu11PointerException8 小时前
JAVA笔记 | ResponseBodyEmitter等异步流式接口快速学习
笔记·学习
闲晨8 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
亦枫Leonlew9 小时前
三维测量与建模笔记 - 3.3 张正友标定法
笔记·相机标定·三维重建·张正友标定法
幼儿园老大*9 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
考试宝9 小时前
国家宠物美容师职业技能等级评价(高级)理论考试题
经验分享·笔记·职场和发展·学习方法·业界资讯·宠物
黑叶白树11 小时前
简单的签到程序 python笔记
笔记·python