【理解ARM架构】异常处理

🐱作者:一只大喵咪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.mzph.cn/news/181597.shtml

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

相关文章

对一个预算有限的创业者来说,应该选择哪些形式的办公场地

对于一个预算有限的创业者来说,选择合适的办公场地是一个重要的决策。不同的办公场地形式有各自的优缺点,需要根据创业者的具体情况和需求来权衡。 一般来说,有以下几种常见的办公场地形式: - 家庭办公:这是最节省成本…

觉得可视化地图太难做?那你是没用过它!

后台一直有粉丝私信老李,问到现在各大企业对数据可视化越来越看重,但是感觉那些高大上的图表做起来一定很复杂甚至可能还需要一些编程基础,希望老李可以推荐一些简单好上手的数据可视化工具。   作为一名数据分析爱好者,我也尝试…

STM32之定时器--超声波测距

1、模块介绍 型号:HC-SR04 超声波测距模块是用来测量距离的一种产品,通过发送和收超声波,利用时间差和声音传播速度, 计算出模块到前方障碍物的距离。 2、超声波模块的使用方法 怎么让它发送波 Trig ,给Trig端口至…

面试篇之微服务(一)

目录 概览 1.什么是微服务? 2.微服务带来了哪些挑战? 3.现在有哪些流行的微服务解决方案? 这三种方案有什么区别吗? 4.说下微服务有哪些组件? 注册中心 5.注册中心是用来干什么的? 6.SpringCloud可…

损失函数总结(十六):NRMSELoss、RRMSELoss

损失函数总结(十六):MSLELoss、RMSLELoss 1 引言2 损失函数2.1 NRMSELoss2.2 RRMSELoss 3 总结 1 引言 在前面的文章中已经介绍了介绍了一系列损失函数 (L1Loss、MSELoss、BCELoss、CrossEntropyLoss、NLLLoss、CTCLoss、PoissonNLLLoss、Ga…

亚马逊云科技 re:Invent 2023:引领科技前沿,探索未来云计算之窗

文章目录 一、前言二、什么是亚马逊云科技 re:Invent?三、亚马逊云科技 re:Invent 2023 将于何时何地举行四、亚马逊云科技 re:Invent 2023 有什么内容?4.1 亚马逊云科技 re:Invent 2023 主题演讲4.2 亚马逊云科技行业专家探实战 五、更多亚马逊云科技活…

二十章 多线程

线程简介 在 Java 中,并发机制非常重要。在以往的程序设计中,我们都是一个任务完成后再进行下一个任务,这样下一个任务的开始必须等待前一个任务的结束。Java 语言提供了并发机制,程序员可以在程序中执行多个线程,每一…

基于Webserver的工业数据采集控制小项目

主要用到的知识点&#xff0c;http协议&#xff0c;modbus协议&#xff0c;以及进程间通信&#xff0c;消息队列&#xff0c;共享内存等 框架 数据采集 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #…

vue+uniapp校园寻物失物招领平台 微信小程序1f6z5

系统中的核心用户是管理员&#xff0c;管理员登录后&#xff0c;通过管理员菜单来管理后台系统。主要功能有&#xff1a;首页、个人中心、用户管理、物品分类管理、物品信息管理、物品归还管理、留言板管理、系统管理等功能。管理员用例如图3-7所示。 对于本网上失物招领小程序…

Linux—进程状态

目录 一.前言 1.1.通过系统调用获取进程标示符 1.2.通过系统调用创建进程 二.进程状态 三.Z(zombie)-僵尸进程 四.僵尸进程危害 一.前言 学习进程的状态&#xff0c;我们首先了解一下进程的基本数据 1.1.通过系统调用获取进程标示符 由getpid&#xff08;&#xff09…

【Python】plt库详解和示例

plt 是 Python 中 Matplotlib 库的一个常用别名&#xff0c;它表示 pyplot&#xff0c;这是一个用于创建图形和图形的可视化表示的工具。下面是一些 plt 函数的详解和示例&#xff0c;以帮助大家理解和使用。 目录 plt.subplots&#xff08;&#xff09;plt.savefig&#xff0…

前端算法专栏-数组-75.颜色分类

介绍 Hi 大家好。我是程序员库里&#xff0c;今天新开一个前端算法专栏。 接下来会分类给大家分享常考算法题目。 很多朋友也是看着这套系列算法拿到很多offer&#xff01;所以也是想分享给更多朋友&#xff0c;帮助到有需要的朋友。 分类 数组-三路快排 题目 75. 颜色分…

Html网页threejs显示obj,ply三维图像实例

程序示例精选 Html网页threejs显示obj,ply三维图像实例 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《Html网页threejs显示obj,ply三维图像实例》编写代码&#xff0c;代码整洁&#xff0…

uniapp上架app store详细攻略

​ 目录 uniapp上架app store详细攻略 前言 一、登录苹果开发者网站 二、创建好APP 前言 uniapp开发多端应用&#xff0c;打包ios应用后&#xff0c;会生成一个ipa后缀的文件。这个文件无法直接安装在iphone上&#xff0c;需要将这个ipa文件上架app store后&#xff0c;才…

c语言-数据在内存中的存储

文章目录 1. 整数在内存中的存储2. 大小端字节序和字节序判断3. 浮点数在内存中的存储 1. 整数在内存中的存储 1.整数的2进制表示方法有三种&#xff0c;即 原码、反码和补码 2. 三种表示方法均有符号位和数值位两部分&#xff0c;符号位都是用0表示“正”&#xff0c;用1表示“…

客服管理者如何有效管理客服团队,有哪些高效方式?

在如今的市场竞争中&#xff0c;客户服务是企业成功的关键因素之一。因此&#xff0c;客服团队的有效管理至关重要。客服管理者需要了解如何有效地管理客服团队&#xff0c;以确保客户的满意度和忠诚度&#xff0c;从而提高企业的竞争力。 以下是客服管理者如何有效管理客服团队…

CSS特效020:涌动的弹簧效果

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

206. 反转链表

206. 反转链表 题目&#xff1a; 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例&#xff1a; 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1…

【Openstack Train安装】六、Keystone安装

OpenStack是一个云计算平台的项目&#xff0c;其中Keystone是一个身份认证服务组件&#xff0c;它提供了认证、授权和目录的服务。其他OpenStack服务组件都需要使用Keystone来验证用户的身份和权限&#xff0c;并且彼此之间需要相互协作。当一个OpenStack服务组件接收到用户的请…

绿色积分如何拉伸经济发展?场景适用何处?

一、引言 绿色积分发展政策是一种新兴的商业模式&#xff0c;它旨在通过积分奖励的方式鼓励消费者和企业采取环保行为&#xff0c;促进绿色消费和可持续发展。这种商业模式不仅有利于环境保护&#xff0c;也有利于提高消费者和企业的参与度和粘性&#xff0c;为商业模式的创新和…