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

2024-06-02 06:44

本文主要是介绍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或者PSPTST 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 EQMRS R0, MSP		; LR&4 或者说 EXC_RETURN&4  为 0 ,说明在在执行SVC异常时使用的栈是 MSPMRS R0, PSP     ; LR&4 或者说 EXC_RETURN&4  为 0 ,说明在在执行SVC异常时使用的栈是 PSP;  获取返回地址 (原理是与发生异常时硬件压栈的顺序相关);  这里获得返回地址的原因是为了定位产生异常前执行的最后一条指令,也就是SVC指令LDR R1, [R0, #24] ; 获取SVC指令的低8位,也就是系统调用号,返回地址的上一条就是SVC指令,LDRB R1, [R1, #-2] ; 依据不同的系统调用号执行不同的系统调用CMP R1, #0x01BEQ SVC_01_HandlerCMP R1, #0x02BEQ SVC_02_HandlerSVC_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");
    }
    

这篇关于arm cortex-m架构 SVC指令详解以及其在freertos的应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023231

相关文章

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input