Cortext-M3系统:异常系统(5)

news2024/12/24 8:31:42

1、使用中断

        在CM3中,NVIC为我们搞定了使用中断时的很多例行任务,如优先级检查、入栈/出栈、取向量等。不过在NVIC能行使职能之前,还需要我们做好如下的初始化工作:建立堆栈、建立向量表、分配各中断的优先级、使能中断。

1.1 建立堆栈

        当开发的程序比较简单时,可以从头到尾都只使用MSP。这时,只需要保证开出一个容量够大的堆栈,再把MSP初始化到其顶即可——这也是单片机开发最常见的做法。

        堆栈用穿是非常致命的错误,必须非常严肃地计算安全容量。由于CM3中的堆栈是以“向下生长的满栈”来操作SP的。在简单的场合中,经常可以把SP初始化为SRAM的末尾,这么一来就使所有的空闲内存都能为堆栈所用。

                                                 简单程序中典型的存储器分配

        然而,对于比较大型的或者是有高性能指标的嵌入式系统,往往需要两个堆栈配合使用。必须保证各堆栈都有足够的容量,尤其是主堆栈,最容易栽在它上面。要注意的是,进程堆栈除了要满足本进程的最大需求量,还需要额外留出8个字,用于容纳第一级中断时被保护的寄存器。

        事实上,准确计算主堆栈需求往往是不可能的任务,在调试阶段时,最好先选用内存更大点的器件,然后开出足够大的内存给主堆栈。然后在调试程序时,允许随时把主堆栈曾经的最大用量输出(通过调试串口或仿真器等),这样时间长了就能估算对主堆栈的需求。

1.2 建立向量表

        如果在程序执行的从头到尾,都只给每个中断提供固定的中断服务例程(这也是目前单片机开发的绝大多数情况),则可以把向量表放到ROM中。在这种情况下不需要运行时重建向量表。然而,如果想让自己的设备能随机应变地对付各种复杂情况,就常常需要动态地改变中断服务例程,更新向量表就是必需的了。此时,向量表必须被转移到可读写存储器中(如内存)。

        在把向量表重定位之前,往往要把现有的向量表往新的位置复制一份。需要拷贝的向量主要是系统异常的服务例程,如各种fault的、NMI的以及SVC的等等。如果没有建立好这些向量就启用了新的向量表,就可能会在响应异常时把不可预料的地址取出,程序极有可能跑飞。当我们把所有必要的向量都填好后,就可以启用了新的向量表了。

1.3 建立中断优先级

        在复位后,对于所有优先级可编程的异常,其优先级都被初始化为0。而对于NMI和硬fault,由于它们要在危难之际挺身而出,所以把它们的优先级定死为-2和-1(高于任何其它异常)。

        不要忘记为系统异常(包括faults)建立优先级。如果程序中有非常紧急的外部中断,它们甚至需要比系统异常还紧急,可是却因故不能连接到NMI上,就要把系统异常的优先级调低,才能保证紧急的中断能够抢占系统异常,从而不被延误。

1.4 使能中断

        在向量表与优先级都建立好后,就到了最后一步:开中断的时候了。然而,在打开中断之前,可能还有两个步骤不能省略:

        1、如果把向量表重定位到了RAM中,且这块RAM所在的存储器区域是写缓冲的,向量更新就可能被延迟。为了以防万一,必须在建立完所有向量后追加一条“数据同步隔离(DSB)”指令,以等待缓冲写入后再继续,从而确保所有数据都已落实。

        2、开中断前可能已经有中断悬起,或者请求信号有效了,这往往是不可预料的。比如,在上电期间,信号线上有发生过毛刺,就可能会被意外地判定成一次中断请求脉冲。另外,在某些外设,如UART,在串口连接瞬间的一些噪音也可以被误判为接收到的数据,从而使中断被悬起。

        在NVIC中进行中断的使能与除能时,都是使用各自的寄存器阵列(SETENA/CLRENA)来完成的:通过往适当的位写1来发出命令,而写0则不会有任何效果。这就让每个中断都可以自顾地使能和除能,而不必担心会破坏其它中断的设置。这改变了以前必须“读-改-写”的三步曲,从而在根本上消灭了在此地产生紊乱危象的可能;否则,必须使用互斥访问等机制来完成修改。

2、异常/中断服务例程

        在CM3中,中断服务例程可以纯用C来写。

        只要检测到过曾经出现的中断请求,NVIC就会记住它,因此硬件只需给一个脉冲,无需再一直保持请求电平,持续的电平反而成为一种讨厌的事了。而且当其服务例程得到执行时,NVIC自动把悬起状态清除。对于这种情况,就不必在ISR中软件清除请求信号了。

3 、软件触发中断

        触发中断有多种方法:外部中断输入、设置NVIC的悬起寄存器中设置相关的位、使用NVIC的软件触发中断寄存器(STIR)

        系统中总是会有一些中断没有用到,此时就可以当作软件中断来使用。软件中断的功用与SVC类似,两者都能用于让任务进入特权级下,以获取系统服务。不过,若要使用软件中断,必须在初始化时把NVIC配置与控制寄存器的USERSETMPEND位置位,否则是不允许用户级下访问STIR。

        但是软件中断没有SVC专业:比如,它们是不精确的,也就是说,抢占行为不一定会立即发生,即使当时它没有被掩蔽,也没有被其它ISR阻塞,也不能保证马上响应。这也是写缓冲造成的,会影响到与操作NVIC STIR相临的后一条指令:如果它需要根据中断服务的结果来决定如何工作(如条件跳转),则该指令可能会误动作——这也可以算是紊乱危象的一种表现形式。为解决这个问题,必须使用一条DSB指令。

        如果欲触发的软件中断被除能了,或者执行软件中断的程序自己也是个异常服务程序,软件中断就有可能无法响应。因此,必须在使用前检查这个中断已经在响应中了。为达到此目的,可以让软件中断服务程序在入口处设置一个标志。这种方式属于卡bug级别的,慎用,需要软件中断,还是用SVC比较好。

4、使用SVC

        SVC是用于呼叫OS所提供API的正道。用户程序只需知道传递给OS的参数,而不必知道各API函数的地址。SVC指令带一个8位的立即数,可以视为是它的参数,被封装在指令本身中,如:

 SVC     3;    呼叫3号系统服务

        则3被封装在这个SVC指令中。因此在SVC服务例程中,需要读取本次触发SVC异常的SVC指令,并提取出8位立即数所在的位段,来判断系统调用号。

        一旦获取了调用号,就可以用它来调用系统服务函数了。有理由相信,OS应该使用TBB/TBH查表跳转指令来加速定位正确的服务函数。然而,如果你是设计OS的人,必须检查这个参数的合法性,以免因数字超出跳转表的范围而跳飞。因为不能在SVC服务例程中嵌套使用SVC,所以如果有需要,就要直接调用SVC函数,例如,使用BL指令。

5、在C中使用SVC

        因为晚到中断的关系,SVC中不能再使用寄存器来传递参数,而是必须使用堆栈。因此,需要使用一段汇编代码来给SVC函数传参数。如果SVC服务例程的主部由C来写,则必须在前面伴随一个汇编写的封皮,用于把堆栈中的参数提取到寄存器中。下面给出一段代码来演示这个工作。

//汇编封皮,用于提出堆栈帧的起始位置,并放到R0中,然后跳转至实际的SVC服务例程中。
__asm void svc_handler_wrapper(void)
{
	IMPORT     svc_handler
	TST        LR, #4
    ITE        EQ
    MRSEQ      R0,  MSP
    MRSNE      R0,  PSP
    B          svc_handler
}
//不必写下BX LR来返回,而是由svc_handler来做决定

        接下来的SVC服务例程的主体就可以由C来写了,它使用R0作为输入参数(这也是堆栈帧的起始位置),用于进一步提取服务代号,并且传递参数(通过堆栈中的R0-R3)。

//使用C写成的SVC服务例程,接受一个指针参数(pwdSF):堆栈栈的起始地址。
//pwdSF[0] = R0 ,pwdSF[1] = R1
//pwdSF[2] = R2 ,pwdSF[3] = R3
//pwdSF[4] = R12,pwdSF[5] = LR
// pwdSF[6] =返回地址(入栈的PC)
// pwdSF[7] = xPSR
unsigned longsvc_handler(unsigned int* pwdSF)
{
	unsigned int svc_number;
	unsigned int svc_r0;
	unsigned int svc_r1;
	unsigned int svc_r2;
	unsigned int svc_r3;
	int retVal;//用于存储返回值
	svc_number = ((char *) pwdSF[6])[-2];       //写的太抽象了

	svc_r0 = ((unsigned long) pwdSF[0]);
	svc_r1 = ((unsigned long) pwdSF[1]);
    svc_r2 = ((unsigned long) pwdSF[2]);
    svc_r3 = ((unsigned long) pwdSF[3]);
    
    printf (“SVC number = %xn”, svc_number);
    printf (“SVC parameter 0 = %x\n”, svc_r0);
    printf (“SVC parameter1 = %x\n”, svc_r1);
    printf (“SVC parameter 2 = %x\n”, svc_r2);
    printf (“SVC parameter 3 = %x\n”, svc_r3);//做一些工作,并且把返回值存储到retVal中
	pwdSF[0]=retVal;
    return 0;
}

        注意,这个函数返回的其实不是0。return 0;只是用于骗过编译器,不用给出警告,实际的返回值是retVal。

        SVC服务例程不能像普通的C函数那样——通过把原型声明为”unsigned int func()”,再在末尾来一句”return xx;”来返回。因为这种常规的作法在所有的ARM中其实是把返回值放到R0里。但是别忘了,这个函数可是异常服务例程,它的返回可是享受“异常返回”的待遇的——伴随着一个硬件控制的自动出栈行为,这会从堆栈中重建R0的值,从而覆盖“return”指定的值。因此,它必须把返回值写到堆栈中R0的位置,才能借自动出栈之机返回自己的值(pwdSF[0]=retVal)。

        虽然内部暗流汹涌,但是从应用程序的表面上看还是风平浪静——对于系统服务函数来说,这种独特的返回方式与普通的return xx效果是相同的,依然可以用普通的形式接收返回值。其实,在写系统软件时,这根本算不上耍狠,只不过是寻常的基本功罢了,要不然怎么说C是“低级高级语言”呢。而病毒/木马所采用的“堆栈/缓冲区溢出攻击”,那才算真正的狠招呢,但是它们原理是一脉相承的。可见,对底层理解得深刻,能让我们写出更好,更强大的程序来。

        在RVDS和KeilRVMDK中,为了方便我们放参数,提供了”__ svc”编译器指示字。举例来说,如果需要在3号服务请求中传递4个参数,则可以类似下例写:

unsigned long  __svc(0x03)   CallSvc3(unsigned long svc_r0, unsigned longsvc_r1,
									  unsigned long svc_r2, unsigned long svc_r3);

当C程序调用这种函数时,则编译器会自动生成SVC指令,如下所示:

int  Func(void)
{
	unsigned long p0, p1, p2, p3;   //传递给SVC服务例程的4个函数
	unsigned long svcRet;    //系统服务的返回值
	
	 //呼叫3号系统服务,并且传递4个参数,依次为:p1,p2,p3,p4,再接收返回值到svcRet中(别忘了,这个返回	 //值的来历不寻常)
	 svcRet = CallSvc3(p0, p1, p2, p3);		
	. . .
	return 0;
}

        如欲获知__svc的官方说明,可以查阅《RVCT 3.0 Compiler and Library Guide(Ref6)》。

        如果使用的是GNU的工具链,里面没有svc关键字。但是GCC支持内联汇编,可以实现此功能。例如,如果需要呼叫3号系统服务,同时传递一个参数,还接收一个返回值(两者都通过R0),则可以使用如下的内联汇编来呼叫SVC:

int MyDataIn=0x123;asmvolatile ("mov R0, %0\n"
                                 "svc 3\n" : "": "r" (MyDataIn) );

        上段内联汇编码中,两个“:”后面分别对应输入数据——由r(MyDataIn)指定,以及输出数据——即上段代码中是””,语法模式如下所示:

__asm ( assembler_code : output_list : input_list )

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

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

相关文章

node笔记_读取目录的文件

文章目录 ⭐前言⭐fs.readdirSync💖 读取目录 不加withFileTypes💖 读取目录 加withFileTypes💖 读取目录时 判断元素文件还是目录 ⭐结束 ⭐前言 大家好,我是yma16,本文分享关于node读取目录文件 往期文章 node_wind…

【java】Jconsole 开启远程连接遇到的一些坑

文章目录 背景一、JMX二、配置远程连接2.1、Java 程序启动2.2、tomcat 启动2.3、无法远程问题排查2.4、解决方案 三、关闭 tomcat 报错3.1、问题分析3.2、问题解决 总结 背景 最近在学习 JVM,其中涉及到性能、内存等指标分析需要使用工具分享,Java 提供…

dvwa靶场通关(六)

第六关:Insecure CAPTCHA(不安全的验证码) 不安全的验证码?不是这个意思,而是指验证码验证可以被绕过。怎么绕?一般都是验证码的验证和最终修改的验证分离,导致了中间过程(验证码的…

io.netty学习(八)零拷贝原理

目录 零拷贝 传统I/O操作存在的性能问题 零拷贝技术原理 虚拟内存 mmap/write 方式 sendfile 方式 带有 scatter/gather 的 sendfile方式 splice 方式 总结 io.netty学习使用汇总 零拷贝 零拷贝(Zero-Copy)是一种 I/O 操作优化技术&#xff0c…

总结906

学习目标: 月目标:6月(线性代数强化9讲,背诵15篇短文,考研核心词过三遍) 周目标:线性代数强化3讲,英语背3篇文章并回诵,检测 每日规划 今日已做: 1.回环背诵…

chatgpt赋能python:Python捕捉按键:探索基础和应用

Python捕捉按键:探索基础和应用 Python作为高级编程语言,可以用于各种任务,例如数据分析、机器学习、图形用户界面等等。其中,捕捉用户键盘输入是一个常见的任务,它可以用于实现简单的游戏、命令行应用和用户交互&…

Redis持久化说明及其单台Linux服务器搭建Redis集群架构

一.Redis持久化方式 1.1 RDB快照 说明:RDB快照主要以二进制文件的形式进行存储数据,主要以文件名dump.rdb进行存储,主要设置redis.conf里面设置’save 60 1000’命令可以开启, 表示在60秒内操作1000次进行一次备份数据。在客户端…

《网络安全0-100》网络安全工具

网络安全工具 抓包工具 抓包工具是网络安全领域中常用的一种工具,用于捕获和分析网络数据包,帮助用户了解网络流量、发现网络攻击和漏洞等问题。以下是几个常用的抓包工具: Wireshark:Wireshark是一种开放源代码的网络协议分析工…

软考A计划-系统集成项目管理工程师-信息化知识(五)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 👉关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

Unity核心6——Animation

一、动画窗口 ​ 通过 Window --> Animation --> Animation 打开 Animation 窗口 ​ Animation窗口主要用于在 Unity 内部创建和修改动画,所有在场景中的对象都可以通过 Animation 窗口为其制作动画 ​ 原理: ​ 制作动画时:记录在…

chatgpt赋能python:Python提供的68个内置函数:一个全面的指南

Python提供的68个内置函数:一个全面的指南 Python是一种强大的编程语言,具有出色的生态系统和强大的功能。它提供了许多内置函数,这些函数可以使你的编程任务变得更加容易和高效。本篇文章将介绍Python提供的68个内置函数,帮助你…

chatgpt赋能python:Python排版快捷键:提高效率的必备技能

Python排版快捷键:提高效率的必备技能 随着Python编程语言的广泛应用,作为开发者,我们不仅需要注重代码质量,还需要注重代码的可读性。在编写Python代码时,排版也是一项重要的任务。好的排版不仅使代码更易于理解&…

k8s日志收集组件 Grafana loki --- 理论篇

当我们在k8s上运行程序时,习惯的会使用ELK来收集和查询程序运行日志。今天我们介绍一款新的专为日志收集而生的神器:Grafana loki。Grafana Loki 是一组组件,可以组合成一个功能齐全的日志堆栈。 与其他日志记录系统不同,Loki 仅…

VUE 2X 数据代理 ④

目录 文章有误请指正,如果觉得对你有用,请点三连一波,蟹蟹支持✨ V u e j s Vuejs Vuejs数据代理 什么是数据代理❓ 代理底层~ D e f i n e P r o p e r y DefinePropery DefinePropery V u e Vue Vue数据代理原理理解总结 文章有误…

Xubuntu22.04之便签工具(一百八十)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…

三、Kafka的消费全流程

Kafka的消费全流程 我们接着继续去理解最后这条消息是如何被消费者消费掉的。其中最核心的有以下内容。 1、多线程安全问题 2、群组协调 3、分区再均衡 多线程安全问题 当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线…

深度学习实战39-U-Net模型在医学影像识别分割上的应用技巧,以细胞核分割任务为例

大家好,我是微学AI,今天给大家介绍一下深度学习实战39-U-Net模型在医学影像识别分割上的应用技巧,以细胞核分割任务为例。本文将介绍在医学影像分割领域中应用U-Net模型的方法。我们将从U-Net模型原理出发,并使用PyTorch搭建模型,详细展示模型代码。接着,我们将展示一些医…

I.MX RT1170之FlexSPI(2):LUT表格的组成和FlexSPI结构体配置

从上一节FlexSPI的框图中可知,SEQ_CTL实现了对外部存储器的时序控制。不同的存储器有着不同的时序,这个时序就是由LUT(Look Up Table)指定的。LUT有它自己的寄存器,当我们设置好之后,外部存储器的读、写和擦除等操作就会根据LUT寄…

《网络安全0-100》安全事件案例

网络安全事件案例分析 2017年Equifax数据泄露事件 Equifax是美国一家信用评级机构,2017年9月,该公司披露发生了一起重大的数据泄露事件,涉及1.43亿美国人的个人信息,包括姓名、出生日期、社会安全号码等敏感信息。经过调查&#…

《OpenCV 计算机视觉编程攻略》学习笔记(一:图像编程入门)

1、参考引用 OpenCV 计算机视觉编程攻略(第3版)本书结合 C 和 OpenCV 3.2 全面讲解计算机视觉编程所有代码均在 Ubuntu 系统中用 g 编译执行 0. 安装 OpenCV 库 在Ubuntu上安装OpenCV及使用OpenCV 库分为多个模块,常见模块如下 opencv_core …