arm cortex-m架构 SVC指令详解以及其在freertos的应用

1. 前置知识

本文基于arm cortex-m架构描述, 关于arm cortex-m的一些基础知识可以参考我另外几篇文章:

  • arm cortex-m 架构简述
  • arm异常处理分析
  • c语言函数调用规范-基于arm 分析

2 SVC指令

2.1 SVC指令位域表示

在这里插入图片描述

  • bit15 - bit12:条件码(Condition Code),用于控制指令的条件执行(EQ,NE,… )。
  • bit11- bit8:固定编码0111,用于标识这是一个SVC指令。
  • bit7- bit0:8位立即数(imm8),svc指令一般用于实现系统调用,imm8用来指定系统调用号。

2.2 SVC指令用途简述

  • ARM中的SVC(Supervisor Call)指令,一般用于实现系统调用, 在执行SVC指令时会触发一个SVC异常。
  • 在cortex-m架构中,异常/中断服务 总是处于总是处于 Privileged(特权) 模式,系统调用也是利用这一特性从用户态 进入 内核态 ,然后根据系统调用号执行特定的系统调用。 关于cortex-m架构和中断的其他信息可以参考我的另外两篇文章:
    • arm cortex-m 架构简述
    • arm异常处理分析
  • 站在硬件的角度SVC指令就是实现了一个软件中断,SVC异常和其他异常处理并无不同,硬件并没有做类似 传递系统调用号 这样的操作,系统调用号imm8也只是和指令码"打包"在一起放在代码段,在进入内核态之后 获取系统调用号 的操作是由 软件完成 的。

2.3 SVC使用示例

  • SVC中断触发示例
    SVC #0x01  ; 执行SVC指令,SVC号为0x01
    SVC #0x02  ; 执行SVC指令,SVC号为0x02
    
  • SVC中断服务函数示例
    • 我在代码中加了注释,如果还是不理解,可以看看我 第一节 所推荐的文章。
    SVC_Handler:
    		; TST指令解释:
    		;	将 "LR" 与 "立即数4" 进行 "按位与" 操作
    		;	结果更新条件标志寄存器(CPSR)
    		; 	注:在发生异常时 LR 存储的是 EXC_RETURN , 这条代码主要看在发生异常时使用的是哪个栈,MSP或者PSP
    	TST LR, #4
    		; 依据TST LR, #4 的执行结果选择将 MSP或PSP加载到R0中
    		; ITE: "If-Then-Else" 的缩写,用于创建一个条件执行块(IT块)。
    		; EQ: 条件码,表示 "Equal"(相等),即当条件码寄存器(CPSR)的零标志(Z)被设置时,条件为真。
    		; 用C语言描述一下下面三行汇编:
    		; if(Z) {
    		; 	MRS R0, MSP
    		; } else {
    		; 	MRS R0, PSP
    		; }
    		; 举一反三: ITTE: "If-Then-Then-Else" 
    	ITE EQ
    	MRS R0, MSP		; LR&4 或者说 EXC_RETURN&4  为 0 ,说明在在执行SVC异常时使用的栈是 MSP
    	MRS R0, PSP     ; LR&4 或者说 EXC_RETURN&4  为 0 ,说明在在执行SVC异常时使用的栈是 PSP
    		;  获取返回地址 (原理是与发生异常时硬件压栈的顺序相关)
    		;  这里获得返回地址的原因是为了定位产生异常前执行的最后一条指令,也就是SVC指令
    	LDR R1, [R0, #24] 
    		; 获取SVC指令的低8位,也就是系统调用号,返回地址的上一条就是SVC指令,
    	LDRB R1, [R1, #-2] 
    		; 依据不同的系统调用号执行不同的系统调用
    	CMP R1, #0x01
    	BEQ SVC_01_Handler
    	CMP R1, #0x02
        BEQ SVC_02_Handler
    
    SVC_01_Handler:
    		; 处理SVC号为0x01的服务请求
    
    SVC_02_Handler:
    		; 处理SVC号为0x02的服务请求
    

3. SVC指令在freertos中的应用

freertos只使用了一次 SVC指令 也可以理解为 freertos中只实现了一个系统调用,且只使用一次,就是在完成任务创建后发起的第一次任务调度时使用。

3.1 在freertos中SVC中断的触发位置

  • freertos使用 SVC指令是在 prvPortStartFirstTask 函数中使用,目的是开始第一次调度,发生在创建任务完成后的第一次调度,我会为这段代码加上注释,如果还存在看不懂的情况,建议先看看我的其他文章或者自行查阅资料学习。
  • 在freertos中的调用关系是:
    		vTaskStartScheduler		--->
    ---> 	xPortStartScheduler		--->
    --->	prvPortStartFirstTask
    
    static void prvPortStartFirstTask( void )
    {
    	__asm volatile (
    	/* 
    	 *	- 0xE000ED08是 SCB 模块 VTOR 寄存器的地址
    	 *	- 这句汇编目的是把 0xE000ED08(VTOR的地址) 保存到 R0 寄存器
    	 *	- 执行完这句指令(伪指令),后 R0 寄存器保存的是 0xE000ED08(VTOR的地址)
    	 */
        	" ldr r0, =0xE000ED08   \n"
    	/* 
    	 *	- 将 0xE000ED08 地址的值(中断向量表的地址) 放到 R0 寄存器中
    	 *	- 执行完这句指令,后 R0 寄存器保存的是 中断向量表的地址
    	 */
        	" ldr r0, [r0]          \n"
    	/* 
    	 *	- 将 中断向量表 第一项的值 放到 R0 寄存器中
    	 *	- 中断向量表第一个字保存的 是栈顶地址(cortex-m是满减栈,栈顶即栈的开始)
    	 *	- 初始转态下使用的是MSP
    	 *	- 执行完这句指令,后 R0 寄存器保存的是栈顶地址
    	 */
        	" ldr r0, [r0]          \n"
     	/* 
    	 *	- 将 R0的值写入 MSP 中。
    	 *	- 此时 R0 内存储的是 栈顶地址,这一步其实就是把 MSP 中已经存好的内容完全销毁。
    	 *	- 因为在这段代码中,在执行完SVC指令后,会由调度器完全接管代码,这里的SVC异常永远也不会返回
    	 */
        	" msr msp, r0           \n"
        /* 
    	 *	- 将 control 寄存器清零
    	 *	- control 寄存器有三位,分别是:
    	 *		. FPCA	- 标志位,用于指示在前文中是否使用过 FPU(浮点运算单元),如果使用过,dang发生异常硬件压栈的时候,会基于此位决定是否保存浮点运算单元上下文,又此时使用的SVC异常是不会返回的,所以也不必保存,保存了也是浪费空间。
    	 *			0:前文没有使用浮点运算单元,产生异常时硬件不用保存浮点上下文
    	 *			1:前文使用了浮点运算单元,产生异常时硬件要保存浮点上下文
    	 *		. SPSEL - 控制位,控制在 Thread mode 下使用 MSP 还是 PSP , Handler mode 一定使用 PSP
    	 *			0: MSP
    	 *			1: PSP
    	 *		. nPRIV - 控制位,控制在 Thread mode 下是 Privileged(特权级) 还是 Unprivileged(非特权级) , Handler mode 下一定是 Privileged(特权级)
    	 *			0: Privileged(特权级)
    	 *			1: Unprivileged(非特权级)
    	 */
        	" mov r0, #0            \n"
        	" msr control, r0       \n"
        /* 开启全局中断 */
        	" cpsie i               \n"	
        /* 开启fpu */
        	" cpsie f               \n"	
        /* 数据同步隔离,确保上面的配置生效 */
        	" dsb                   \n"	
        /* 指令同步隔离,清空流水线 */
        	" isb                   \n" 
        /* 触发0号系统调用,永远也不会返回, 从此处开始代码由freertos全面接管 */	
        	" svc 0                 \n"
        /* svc 不会返回,永远也不会运行到这里 */
        	" nop                   \n"
        /* 这是一条伪指令,由汇编器处理,目的是告诉汇编器,把字面量池(常量)放到这个位置, 避免字面量池离使用它的指令太远,超出了寻址范围而导致错误。ARM的ldr指令寻址范围有限*/
        	" .ltorg                \n"
        );
    }
    

3.2 freertos中断服务函数解析

  • 我会为这段代码加上注释,如果还存在看不懂的情况,建议先看看我的其他文章或者自行查阅资料学习。
    void vPortSVCHandler( void )
    {
       /*
    	* 思考:在汇编中能用C语言的变量吗,为什么,C语言中的变量和符号在汇编中代表了什么
    	* 答案:应考虑如下几点
    	* 	- C语言和汇编在整个编译过程中是在不同的阶段进行的
    	* 	- C语言是在 编译阶段 进行处理的,C语言会被编译为汇编
    	* 	- 汇编 是在汇编阶段进行的,会生成可重定位的目标文件,最终会在 链接阶段 由链接器为所有符号分配 绝对地址。
    	* 	- 我们在写汇编时,会用一些符号代表地址,那些符号会由 链接器 最终分配绝对地址。
    	* 	- c语言中定义的 变量名 函数名 在经过 编译器 编译为 汇编 之后,那些变量名, 函数名都代表一个地址,最后由 链接器 分配绝对地址。
    	* 	所以,在汇编中使用的C语言符号,可以理解为那个变量或者函数的 地址。
     	*/
       /*
    	* 先看一下最后一句汇编 "pxCurrentTCBConst2: .word pxCurrentTCB             \n"
    	* 	- pxCurrentTCB:在c语言中定义,它是一个指针,一个描述 任务 的结构体指针
    	* 	- 在汇编中使用 pxCurrentTCB 这个符号, 就相当于使用这个符号的地址,在这里可以理解为一个二重指针
    	* 	- 上述汇编可以这么理解:
    	* 		. 使用 .word 分配一个字的空间,用来放 “下一个要执行的任务结构体” 指针的指针(二重指针).
    	* 		. 将这个二重指针 使用符号 pxCurrentTCBConst2 表示
     	*/
    		__asm volatile (
    		/* 
    		 * - 将要执行任务的结构体 的 指针的指针(二重指针) 保存到 R3寄存器 
    		*/
         	"   ldr r3, pxCurrentTCBConst2      \n"
         	/* 
         	 * - 将要执行任务的结构体 的 指针(一重指针) 保存到 R1寄存器 
         	 * - 也可以理解为将 要执行任务的结构体 的第一个元素的地址保存到 R1 寄存器
         	 * - 结构体第一个元素 保存将要执行任务的栈指针
         	 * - 所以这一句是将 栈指针(SP) 的地址(是栈指针的存放地址,而不是栈指针) 放到R1中。
         	 */
        	"   ldr r1, [r3]                    \n"
        	/* 
        	 * - 将栈指针 SP 保存到 R0寄存器 
        	 */
        	"   ldr r0, [r1]                    \n"
        	/* 
        	 * - 将栈指针中的地址按顺序弹栈(要执行的任务的栈)到{r4-r11, r14}
        	 * - 这里是要弹栈的内容是 创建任务时 伪造的上下文 (此处先不展开,后面降到任务的创建过程会展开来讲)
        	 */
        	"   ldmia r0!, {r4-r11, r14}        \n"
        	/* 将 PSP 指向弹完栈后的地址 */
        	"   msr psp, r0                     \n"
        	/* 指令同步隔离,清空流水线 */
        	"   isb                             \n"
        	/* 将basepri寄存器设为0,打开所有中断 */
        	"   mov r0, #0                      \n"
        	"   msr basepri, r0                 \n"
        	/* 
        	 * - 异常返回, 这时r14(LR)中存放的是一个 EXC_RETURN 值,这个值是在创建任务时伪造的。
        	 * - EXC_RETURN 的值决定了任务使用的栈
        	 * - 伪造栈的内容会在后面将创建任务时讲
        	 */
        	"   bx r14                          \n"
        	"                                   \n"
        	"   .align 4                        \n"
        	"pxCurrentTCBConst2: .word pxCurrentTCB             \n"
        	);
    }
    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入分析 Android BroadcastReceiver (一)

文章目录 深入分析 Android BroadcastReceiver (一)1. Android BroadcastReceiver 设计说明1.1 BroadcastReceiver 的主要用途 2. BroadcastReceiver 的工作机制2.1 注册 BroadcastReceiver2.1.1 静态注册2.1.2 动态注册 3. BroadcastReceiver 的生命周期4. 实现和使用 Broadca…

Android下HWC以及drm_hwcomposer普法(上)

Android下HWC以及drm_hwcomposer普法(上) 引言 按摩得全套,错了,做事情得全套,普法分析也是如此。drm_hwcomposer如果对Android图形栈有一定研究的童鞋们应该知道它是Android提供的一个的图形后端合成处理HAL模块的实现。但是在分析这个之前…

yolov8使用:数据格式转换(目标检测、图像分类)多目标跟踪

安装 yolov8地址:https://github.com/ultralytics/ultralytics git clone https://github.com/ultralytics/ultralytics.git安装环境: pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple目标检测 标注格式转换 若使用 labelimg…

sql注入-布尔盲注

布尔盲注(Boolean Blind SQL Injection)是一种SQL注入攻击技术,用于在无法直接获得查询结果的情况下推断数据库信息;它通过发送不同的SQL查询来观察应用程序的响应,进而判断查询的真假,并逐步推断出有用的信…

微服务学习Day9

文章目录 分布式事务seata引入理论基础CAP定理BASE理论 初识Seata动手实践XA模式AT模式TCC模式SAGA模式 高可用 分布式事务seata 引入 理论基础 CAP定理 BASE理论 初识Seata 动手实践 XA模式 AT模式 TCC模式 Service Slf4j public class AccountTCCServiceImpl implements A…

C语言 | Leetcode C语言题解之第126题单词接龙II

题目: 题解: char** list; int** back; int* backSize;// DFS uses backtrack information to construct results void dfs(char*** res, int* rSize, int** rCSizes, int* ans, int last, int retlevel) {int i ans[last];if (i 0) {res[*rSize] (c…

DALL·E 2详解:人工智能如何将您的想象力变为现实!

引言 DALLE 2是一个基于人工智能的图像生成模型,它通过理解自然语言描述来生成匹配这些描述的图像。这一模型的核心在于其创新的两阶段工作流程,首先是将文本描述转换为图像表示,然后是基于这个表示生成具体的图像。 下面详细介绍DALL-E2的功…

Vivado Design Suite一级物件

Vivado Design Suite一级物件 按设计过程导航内容 Xilinx文档围绕一组标准设计流程进行组织,以帮助您 查找当前开发任务的相关内容。本文件涵盖 以下设计过程: •硬件、IP和平台开发:为硬件创建PL IP块 平台,创建PL内核&#xff0…

HTML的标签(标题、段落、文本、图片、列表)

HTML的标签1 标题标签:段落标签:文本标签:图片标签:列表标签:有序列表:无序列表:定义列表:列表案例: 标题标签: 标签:h1~h6 注意:如果使用无效标…

C语言怎样写数据⽂件,使之可以在不同字⼤⼩、 字节顺序或浮点格式的机器上读⼊?

一、问题 怎样写数据⽂件,使之可以在不同字⼤⼩、字节顺序或浮点格式的机器上读⼊,也就是说怎样写⼀个可移植性好的数据⽂件? 二、解答 最好的移植⽅法是使⽤⽂本⽂件,它的每⼀字节放⼀个 ASCII 代码,代表⼀个字符。 …

从JS角度直观理解递归的本质

让我们写一个函数 pow(x, n),它可以计算 x 的 n 次方。换句话说就是,x 乘以自身 n 次。 有两种实现方式。 迭代思路:使用 for 循环: function pow(x, n) {let result 1;// 在循环中,用 x 乘以 result n 次for (let i…

短时间内如何顺利通过 Java 面试?

今天我们来探讨一个重要的话题:短时间内如何顺利通过 Java 面试? 在此之前,我正在精心编写一套完全面向小白的 Java 自学教程,我相信这套教程会非常适合正在努力提升的你。教程里面涵盖了丰富全面的编程教学内容、详细生动的视频…

2.8Flowmap的实现

一、Flowmap 是什么 半条命2中水的流动 求生之路2中的水的流动 这种方式原理简单,容易实现,运算量少,如今也还在使用 1.flowmap的实质 Flow map(流向图) ,一张记录了2D向量信息的纹理,Flow map上的颜色(通常为RG通道…

Python知识点14---被规定的资源

提前说一点:如果你是专注于Python开发,那么本系列知识点只是带你入个门再详细的开发点就要去看其他资料了,而如果你和作者一样只是操作其他技术的Python API那就足够了。 在Python中被规定的东西不止有常识中的那些关键字、构造器等编程语言…

Vue3-Ref Reactive toRef toRefs对比学习、标签ref与组件ref

响应式数据: Ref 作用:定义响应式变量。 语法:let xxx ref(初始值)(里面可以是任何规定内类型、数组等)。 返回值:一个RefImpl的实例对象,简称ref对象或ref,ref对象的value属性是响应式的。 注意点&am…

公网如何访问内网?

公网和内网已经成为我们生活中不可或缺的存在。由于内网的安全性考虑,公网无法直接访问内网资源。如何实现公网访问内网呢?本文将介绍一种名为【天联】的私有通道技术,通过安全加密,保障数据传输的安全性。 【天联】私有通道技术 …

利用Python处理DAX多条件替换

小A:白茶,救命啊~~~ 白茶:什么情况? 小A:是这样的,最近不是临近项目上线嘛,有一大波度量值需要进行类似的调整,一个两个倒没啥,600多个,兄弟,救命…

STM32_FSMC_HAL(介绍)

FSMC(Flexible Static Memory Controller)是STM32微控制器中的一种内存控制器,它允许微控制器与外部存储器接口,如SRAM、NOR Flash、NAND Flash和PSRAM等。FSMC特别适用于需要高速数据交换和大量数据存储的应用场景。 典型应用&a…

06.持久化存储

6.持久化存储 pv: persistent volume 全局的资源 pv,node pvc: persistent volume claim 局部的资源(namespace)pod,rc,svc 6.1:安装nfs服务端(192.168.111.11) yum install nfs-utils.x86_64 -y mkdir /data vim /…

Linux——多线程(二)

在上一篇博客中我们已经介绍到了线程控制以及对应的函数调用接口&#xff0c;接下来要讲的是真正的多线程&#xff0c;线程安全、线程互斥、同步以及锁。 一、多线程 简单写个多线程的创建、等待的代码 #include<iostream> #include<pthread.h> #include<un…