【理解ARM架构】异常处理

news2024/11/27 5:34:15

🐱作者:一只大喵咪1201
🐱专栏:《理解ARM架构》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • ⚡ARM系统中异常与中断处理流程
    • 🍢向量表
    • 🍢保存现场
    • 🍢恢复现场
  • ⚡异常处理
    • 🍢未定义指令异常
    • 🍢SVC异常
    • 🍢SysTick异常
  • ⚡总结

⚡ARM系统中异常与中断处理流程

图
如上图所示arm系统中异常与中断的硬件框图,左侧的按键,定时器其他等等被叫做中断源,它们发出的中断汇聚到中断控制器,也就是NVIC,再由中断控制器将中断发信号给CPU,告诉它发生了那些紧急情况,CPU会中断当前正在执行的代码去处理中断。

除了中断,异常也可以打断CPU的运行,如上图所示右侧框中:

  • 指令不对
  • 数据访问有问题
  • reset信号

等等情况,这些都可以打断CPU运行,这些都属于异常

  • 中断属于一种异常。

ARM系统中处理异常与中断的重点在于保存现场以及恢复现场,中断的使用过程如下:

  • 初始化

    • 设置中断源,让它可以产生中断
    • 设置中断控制器(可以屏蔽某个中断,优先级)
    • 设置CPU总开关,使能中断
  • 执行其他程序:正常程序

  • 产生中断,举例:按下按键—>中断控制器—>CPU

  • cpu每执行完一条指令都会检查有无中断/异常产生

  • 发现有中断/异常产生,开始处理:

    • 保存现场
    • 分辨异常/中断,调用对于异常/中断的处理函数
    • 恢复现场

🍢向量表

不同的芯片,不同的架构,在这方面的处理稍有差别。先来认识一下向量表。向量,在数学定义里是有方向的量,在程序里可以认为向量就是一个数组,里面有多个项,在ARM架构里,对于异常/中断,它们的处理入口函数会整齐地排放在向量表中。

tu
如上图所示,我们在使用CubeMX或者固件库创建好的工程中,在start.s中存在一个向量表__Vectors,其中上面的蓝色框中是处理异常的入口地址,下面的蓝色框中是处理中断的入口地址。

板子上电以后,从__Vectors处的第一个DCD处执行,这里是设置栈顶的,__initial_sp就是栈顶的地址。然后再执行第二个DCD处的Reset_Handler,我们的main函数等就放在这里。

cortex M3/M4:

M3/M4的向量表中,放置的是具体异常/中断的处理函数的地址。比如发生Reset异常时,CPU就会从向量表里找到第1项,得到Reset_Handler函数的地址,跳转去执行。

比如发生EXTI Line 0中断时,CPU就会从向量表里找到第22项,得到EXTI0_IRQHandler函数的地址,跳转去执行。

cortex A7:

tu

如上图所示A7的向量表中,放置的是某类异常的跳转指令。比如发生Reset异常时,CPU就会从向量表里找到第0项,得到b reset指令,执行后就跳转到reset函数。

比如发生任何的中断时,CPU就会从向量表里找到第6项,得到ldr pc, _irq指令,执行后就跳转到_irq函数。

🍢保存现场

在跳转到向量表执行入口函数之前,先要保存现场,也就是将CPU中寄存器中的值先保存下来。

为什么要保存现场?

tu
如上图所示代码示意图,任何程序,最终都会转换为机器码,上述C代码可以转换为右边的汇编指令。

对于这4条指令,它们可能随时被异常打断,怎么保证异常处理完后,被打断的程序还能正确运行?

  • 这4条指令涉及R0、R1寄存器,程序被打断、恢复运行时,R0、R1要保持不变。
  • 执行完第3条指令时,比较结果保存在程序状态寄存器里,程序被打断、恢复运行时,程序状态寄存器要保持不变。
  • 这4条指令,读取a、b内存,程序被打断、恢复运行时,a、b内存要保持不变

内存保持不变,这很容易实现,程序不越界就可以。所以,关键在于R0、R1、程序状态寄存器要保持不变(当然不止这些寄存器),因为这些寄存器在中断中也有可能用到,此时就会改变原本的值。

图
如上图所示,在ARM处理器中有这些寄存器,而且在ARM中有个ATPCS规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)约定R0-R15寄存器的用途。

图
如上图所示,R0-R3用在调用者和被调用者之间传参数,R4~R11在被调用者(函数)内使用,R12~R15是特殊用途的寄存器,还有一个程序状态寄存器,对于M3/M4它被称为XPSR

  • 保存现场就是在保存R0~R15以及XPSR寄存器。

在发生异常/中断后,在处理异常/中断前,需要保存现场,难道需要保存所有这些寄存器吗?不是的。

  • 这些这些寄存器被拆分成2部分:调用者保存的寄存器(R0-R3,R12,LR,PSR)被调用者保存的寄存器(R4-R11)

怎么理解呢?(R0-R3,R12,LR,PSR)这些寄存器是用来传参或者保存返回地址的,调用者主动将这些寄存器给被调用者直接使用,站在被调用者的角度,它认为它得到了允许,既然是你让我用的,那我就随便用了。

站在调用者的角度,就有责任来保证自己不受影响,所以在给被调用者使用之前,需要将这些寄存器的值保存起来,调用结束以后方便将值恢复到这些寄存器中。

(R4-R11)这些寄存器被调用者在使用的时候,并没有得到调用者的允许,所以它在使用之前有责任将这些寄存器原本的值保存起来,在使用完毕后再将值恢复到寄存器中,以防影响到调用者。

  • 所以在处理中断/异常之前,要将R0~R3,R12,LR,XPSR寄存器中的值保存。

保存现场时寄存器中的值保存到哪里呢?

tu
如上图所示,在保存现场时,将调用者要保存的寄存器挨个压栈,高编号寄存器值放在高地址。

  • 在M3/M4中,现场保存是由硬件完成的,我们写程序的不用关心。

  • 异常/中断类型的分辨也是由硬件完成的。

在保存完现场以后,就直接跳转到向量表中对于的处理入口执行对应的处理函数。


🍢恢复现场

图
如上图所示现场保护时栈的情况,在处理函数执行完毕后,它返回LR所指示的位置(普通调用是这样),难道把LR设置为被中断时程序的地址就行了吗?

如果只是返回LR所指示的地方,也就是执行MOV PC, LR,此时程序直接就返回到产生中断/异常的位置开始执行代码了,硬件帮我们保存在栈里的寄存器,怎么恢复?

所以M3/M4在调用异常处理函数前,把LR设置为一个特殊的值,该特殊的值被称为EXC_RETURN

图
如上图所示,该特殊值是一个32位的地址,它具有特别的意义,以后会具体讲解它的意义。

当处理函数执行完毕以后,会执行MOV PC, LR,当PC寄存器的值等于EXC_RETURN时,会触发异常返回机制,简单地说:会从栈里恢复R0-R3,R12,LR,PC,PSR等寄存器。

然后再把栈中红色框中的返回地址赋值给PC寄存器,让程序从产生中断/异常位置继续执行。

  • 恢复现场是由软件触发,硬件恢复的。

所谓软件触发就是我们在处理函数中执行return函数,此时就会触发异常/中断返回机制,由硬件将栈中保存的值恢复到寄存器中。

⚡异常处理

在了解了异常/中断的处理流程以后,来写代码感受一下。继续使用前面的代码。

图
如上图,修改散列文件,让代码段的加载地址和链接地址相等,不再需要代码段重定位,让代码在Flash上运行。

🍢未定义指令异常

所谓未定义指令就是写一条CPU不认识的指令,此时就会出异常,硬件就会让程序跳转到向量表中对应的处理入口,去执行处理函数。

tu

如上图,在向量表中只保留HardFault_HandlerUsageFault_Handler两个异常处理入口,并且声明这两个函数。

tu
如上图,定义HardFault_HandlerUsageFault_Handler两个异常处理函数,在函数里打印一句话,然后陷入死循环。

tu
如上图所示,声明串口初始化函数,然后在执行未定义指令之前初始化串口,否则就无法看到打印的东西了,因为串口还没有初始化就发生了异常。

然后会执行DCD 0XFFFFFFFF未定义指令,此时就会产生异常,这属于一个使用异常,所以应该会去UsageFault_Handler处执行处理函数。

tu
如上图,但是此时从串口助手上看到的是HardFault_Handler,说明执行的是HardFault_Handler处理函数,而不是UsageFault_Handler函数,这是为什么呢?

tu
如上图所示,未定义指令属于"处理器操作相关的错误",如果没有使能Usage Fault",发就会触发Hard Fault,所以上面执行的就是HardFault_Handler处理函数。

为了执行HardFault_Handler处理函数,需要将Usage Fault使能,在M3/M4内核中,有一个用于异常和中断控制的SCB寄存器:

tu
如上图所示SCB寄存器部分位,详细内容在ARM Cortex-M3与Cortex-M4权威指南这本书中有详细接收,该寄存器的基地址是0xE000ED00

TU
如上图,为了访问SCB寄存器方便,将该寄存器使用结构体描述出来。
图
如上图,定义一个函数UsageFaultInit,在里面将SCB寄存器的第18位,也就是SHCSR位置一,在执行未定义指令之前调用该函数,此时就使能了UsageFault

图
如上图,在用法错误异常处理函数UsageFault_Handler中,只打印异常名,不陷入死循环。

图
如上图,此时就会疯狂打印UsageFault_Handler,说明不停的在执行UsageFault_Handler处理函数。为什么会不停执行呢?执行一遍不就可以了吗?

  • 用法错误异常仍然存在,虽然执行了UsageFault_Handler处理函数,但是没有将该异常清除。

tu
如上图,在UsageFault_Handler函数中,先打印出保护现场时,调用者保护的R0~R3,R12,LR,返回地址,XPSR,这七项,它们存在栈中。

图
如上图所示,由于要在UsageFault_Handler函数中打印栈中存放的寄存器值,所以在调用该函数的时候要进行传参,而向量表中存放的入口处理函数指针是没有形参的。

所以重新定义一个入口处理函数UsageFault_Handler_asm,如上图红色框,将该函数放入到向量表中,当发生UsageFault的时候,就会跳转去执行该函数。

在该函数中,通过R0寄存器传参栈顶指针SP,然后再调用我们之前实现的UsageFault_Handler

  • 调用UsageFault_Handler函数的时候不能使用BL指令,因为这是异常处理函数,不能直接返回到LR中的地址处,需要触发恢复现场机制。
  • 所以只能使用B来调用UsageFault_Handler,现场恢复机制不在这里触发。

tu

如上图所示,此时串口仍然疯狂输出,我们截取打印内容中栈里的值,发现在调用UsageFault_Handler处理函数之前的现场保存时,存放到栈中的返回地址是0x08000068,程序执行完处理函数后会返回到这个地址继续执行。

tu
如上图,打开反汇编文件,查看0x08000068地址处的内容,发现该地址处就是那条未定义指令。

也就是说,未定义指令引起异常后调用处理函数,处理完毕以后又回到了异常指令这里,再次执行,再次引发异常,如此反复导致疯狂输出。

tu
如上图所示,在UsageFault_Handler函数中,设置栈中的返回地址,让其指向下一条指令,也就是在调用异常处理函数结束以后,硬件进行现场恢复完成,然后让PC指向未定义指令的下一条指令。

tu
如上图所示,此时程序就能正常执行了。

🍢SVC异常

在ARM指令中,有一条指令:

SVC #VAL

其中,VAL是个立即数,代表着一个编号,当SVC异常产生时,会调用对应编号的处理函数,默认情况下我们只有一个处理函数,所以该值一般填1。

当CPU执行了SVC指令后,会触发一个异常,在操作系统中,比如各类RTOS或者Linux,都会使用SVC指令故意触发异常,从而导致内核的异常处理函数被调用,进而去使用内核的服务(系统调用)。

比如Linux中,各类文件操作的函数openreadwrite,它的实质都是SVC指令。本喵这里不讲解这些,只是看一下SVC异常发生后的现象。

tu

如上图,定义一个SVC_Handler函数来处理SVC异常。

tu
如上图,在启动文件中,将SVC_Handler处理函数放入向量表并且声明,然后在Reset_Handler中执行SVC #1指令产生异常。

tu
如上图所示,此时可以看到,SVC_Handler处理函数被调用了,所以说,产生SVC异常时,会去执行对应的处理函数。

图
如上图所示,先给R0~R3,R12,LR赋值,然后在产生SVC异常后进入处理函数时停下来,查看此时栈中的内容,可以看到,我们原本赋给寄存器中的值此时保存在栈中。

  • 在调用异常处理函数之前,硬件进行了现场保存,将调用者保存的寄存器中的值放到了栈中。

🍢SysTick异常

Cortex-M处理器内部集成了一个小型的、名为SysTick的定时器,也叫做滴答定时器。可以使用它来为操作系统提供系统时钟,也可以把它当做一般的定时器。

它是一个24位的定时器,向下计数,在时钟源的驱动下,计数值到达0时,可以触发SysTick异常。

图
如上图所示SysTick定时器框图,每到了一次时钟信号,VAL计数器就会减一,当减到0以后会产生一次SysTick异常。

然后再自动从LOAD重装载寄存器中读取计数值到VAL中,如此反复产生多次异常。

控制SysTick定时器的寄存器基地址为0xE000E010


tu
如上图所示STCK_CTRL控制寄存器,通过BIT2来选择时钟源,该位是1时选择处理器时钟,也就是晶振直接作为时钟,STM32F103ZET6的晶振频率是8MHZ。

通过BIT1来使能SysTick异常,将该位设置为1,通过BIT0来使能SysTick定时器,将该位设置成1。

图
如上图所示计数器STK_VAL寄存器,其bit0~bit23存放的是计数值,要给它设置一个初始值。

图
如上图所示STK_LOAD重装载寄存器,VAL减为0以后会从这里重新拿值,所以该寄存器的值要设置成和VAL中的值一样。

图
如上图所示SCB_ICSR寄存器,SysTick异常发生以后,需要在处理函数中清除异常,将该寄存器的BIT25设置为1。


图
如上图,为了使用方便,同样将SysTick定时器用到的寄存器用结构体描述出来。

tu
如上图所示,定义一个SysTickInit函数来初始化滴答定时器,将VALLOAD寄存器的值都设置为8000,定时时间为1s,因为晶振时钟频率是8000。

再设置CTRL控制寄存器中的bit0~bi2,全部设置为1,表示选择晶振作为时钟源,使能SysTick异常,使能SysTick定时器。

图
如上图,定义异常处理函数SysTick_Handler,在里面清除SysTick异常,并且打印异常名字。

tu
如上图所示,声明异常处理函数SysTick_Handler,并将其放到向量表中。再声明定时器初始化函数SysTickInit,并在调用mymain之前调用,完成滴答定时器初始化。

tu
如上图,此时每隔一秒钟会产生一次SysTick中断,会调用一次SysTick_Handler异常处理函数。

⚡总结

要清楚异常发生的流程,包括现场保存,分辨异常源且执行相应的处理函数,通过软件触发现场恢复机制。其中现场保存和现场恢复是由硬件完成的,包括异常源的分辨也是。

  • 异常并不会经过中断控制器NVIC。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1267158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mendix组件推荐:灵活的在线表格

- 视频 mendix在线表格.mp4 20.95MB - 客户需求 如果你是一个中小型企业的负责人,你可能面临着: 多人协作录入数据展示数据库中的数据对数据安全有要求、希望本地离线部署并且IT人员配置有限等挑战 为了更好地管理你的业务数据,你需要一个…

【IEEE出版】2024年第四届消费电子与计算机工程国际学术会议(ICCECE 2024)

2024年第四届消费电子与计算机工程国际学术会议(ICCECE 2024) 2024 4th International Conference on Consumer Electronics and Computer Engineering 进入21世纪以来,计算机技术的高速发展带来了消费电子产品的快速更迭。在技术迅速发展历…

WMS仓储管理系统的实施流程是什么

WMS仓储管理系统是现代企业不可或缺的重要工具,它可以有效地优化仓库管理,提高工作效率,减少误差。但是,实施WMS仓储管理系统并不是一件轻松的事情,需要经过一系列的步骤来确保其成功实施。本文将详细介绍WMS仓储管理系…

建立健全涉密测绘外业安全保密管理制度,落实监管人员和保密责任,外业所用涉密计算机纳入涉密单机进行管理

建立健全涉密测绘外业安全保密管理制度,落实监管人员和保密责任,外业所用涉密计算机纳入涉密单机进行管理 1.涉密测绘外业安全保密管理制度 2.外业人员及设备清单(包括:外业从业人员名单、工作岗位,外业设备名称、密…

【网络安全】-安全常见术语介绍

文章目录 介绍1. 防火墙(Firewall)定义通俗解释 2. 恶意软件(Malware)定义通俗解释 3. 加密(Encryption)定义通俗解释 4. 多因素认证(Multi-Factor Authentication,MFA)定…

如何在ASO优化策略中确定季节性的框架

由于我们全年都需要考虑许多季节性事件,因此可能会让人不知所措,我们需要遵循一个清晰的框架来在ASO策略中处理季节性事件。 1、进行应用的研究。 确定与我们应用或游戏相关的所有季节性事件。查看所有三种类型:假期、行业活动和预期的特定于…

dockerfile介绍与使用

文档:https://docs.docker.com/engine/reference/builder/ dockerfile介绍 dockerfile是什么 Dockerfile是一个创建镜像所有命令的文本文件, 包含了一条条指令和说明, 每条指令构建一层, 通过 docker build命令,根据Dockerfile的内容构建镜像,因此每一条指令的内…

商品橱窗和抖音小店有什么区别?新手应该选择哪一个?

我是电商珠珠 在抖音小店内,有两种经营方式,一种是商品橱窗,还有一种是抖音小店。 很多人会将他们混之一谈,说开抖店需要粉丝,商品橱窗不用。 事实真的是这样吗? 接下来,我就来给大家讲讲二…

UE Web Remote Control

前言 最近在研究UE自启WEB服务和网页通信以此来通过网页与UE进行数据交互,这样最好的方式就是可以摒弃掉整个繁琐的通信连接流程如TCP UDP,但是找到的一些方法都不是很适用,尤其是WEBUI这个插件它只适合内嵌到UE本身才能完成交互,…

PCB设计注意事项

四个二极管不能省略 pwm波跟电机频率不要是倍频 运放越靠近取样电阻越好 反向输入端跟输出端很敏感,有寄生电容就容易震荡 距离取样电阻近就会距离单片机远,那么线上会有寄生电容,这时候在输出端接一个10k电阻到地

压缩字符串II

null备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/string-compression/description/ 给你一个字符数组 chars ,请使用下述算法压缩&#xff…

高档建筑覆膜板,胶水足表面光滑

在建筑材料行业,选择高质量的建筑覆膜板至关重要。贵港市能强优品木业是专业从事建筑覆膜板生产销售25年的源头工厂。这家工厂一直以来致力于生产出色的覆膜板,以确保建筑物外观精美,持久耐用。 无论是商业大楼还是家庭住宅,外墙装…

PingCAP 被评为 Translytical Data Platforms 2023 全球技术领导者

近日,PingCAP 在全球化商业咨询公司 Quadrant Knowledge Solutions 公布的 SPARK Matrix for Translytical Data Platforms 中,被评为 2023 年全球 Translytical Data Platforms 技术领导者 。Translytical Data Platforms 可以帮助金融科技、电子商务、…

深入理解虚拟 DOM:提升前端性能的关键技术

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

创业公司or大厂怎么选?不是凡尔赛,一个技巧让你涨薪10W!

最近总有一些特别“凡尔赛”的发几个 offer 问我选择哪个?其中比较典型的一个问题就是: “一个是处于上升期的创业型公司 ,一个行业大厂,薪资待遇差不多,到底该如何进行选择和取舍呢?“ 这个问题不是个别…

初刷leetcode题目(10)——数据结构与算法

😶‍🌫️😶‍🌫️😶‍🌫️😶‍🌫️Take your time ! 😶‍🌫️😶‍🌫️😶‍🌫️😶‍🌫️…

家电产品扇叶零部件自动化三维检测设备高精度3D测量系统-CASAIM-IS(2ND)

一、背景介绍 某家电制造商希望对其生产的家电产品零部件进行高精度的3D测量,以确保零部件的尺寸精度和质量符合严格的标准,零部件的形状复杂且多样化,对于一些细节部位的测量精度要求极高。本文将介绍CASAIM-IS(2ND)…

Excel导入组件的封装以及使用页面点击弹出该弹框

封装的组件 <template><el-dialogwidth"500px"title"员工导入":visible"showExcelDialog"close"$emit(update:showExcelDialog, false)"><el-row type"flex" justify"center"><div class&q…

鸿蒙原生应用/元服务开发-AGC分发如何下载管理Profile

一、收到通知 尊敬的开发者&#xff1a; 您好&#xff0c;为支撑鸿蒙生态发展&#xff0c;HUAWEI AppGallery Connect已于X月XX日完成存量HarmonyOS应用/元服务的Profile文件更新&#xff0c;更新后Profile文件中已扩展App ID信息&#xff1b;后续上架流程会检测API9以上Harm…

设计好的测试用例,6大注意事项

设计好的测试用例对于发现缺陷、验证功能、提高可靠性、降低风险和提高效率都具有重要的作用&#xff0c;是保证产品质量和稳定性的重要环节。如果测试用例有问题&#xff0c;可能会导致遗漏缺陷、功能验证不充分、测试效率低下以及误报漏报等问题&#xff0c;从而影响项目的质…