No.1 计算机硬件基础
计算机数据进制
计算机组成
一台计算机由输入设备、输出设备、存储器、运算器、控制器五部分组成。
运算器:CPU中负责进行算数运算和逻辑运算的部件,其核心是算术逻辑单元ALU
控制器:控制器是CPU的指挥中心,其控制着整个CPU执行程序的逻辑过程
我们常说的计算机CPU是由运算器和控制器组成的。
CPU总线

三级储存结构
为了兼顾存储器的速度、价格、容量的特点,从而产生了三级存储结构。
地址空间

CPU工作原理
取址:CPU将PC寄存器中的地址发送给内存,内存将其地址中对应的指令返回到CPU中的指令寄存器(IR)
译码:译码器对IR中的指令进行识别,将指令(机器码)解析成具体的运算
执行:控制器控制运算器中对应的运算单元进行运算,运算结果写入寄存器
每执行一条指令后PC的值会自动增加指向下一条指令,具体见实践问题的第二问进行解释。
实践问题
1.简述为什么地址总线为32bit的处理器的地址空间为4G
一个CPU能够读取数据的地址空间是基于地址总线的宽度来决定的32位的处理器,其地址空间大小应该为2的32次方,也就是4G。
首先,这里的32指的是CPU的32位地址线,可以访问2的32次方个不同的地址。
其次,CPU有8位位线,即访问一个地址可以获得8位的数据,即1字节(1B)的数据。
所以,2的32次方个地址可以访问2的32次方字节的数据,换句话说,32位的机器可以访问2的32次方字节(Byte)的内存空间。
2.简述CPU执行指令的过程
CPU工作原理:一个ARM架构CPU内有控制器与运算器
控制器:指令计数器PC 、 指令寄存器IR 、 指令译码器
运算器:寄存器
当CPU要进行一个运算时,先由指令计数器PC通过地址总线发送指定运算符指令的地址,根据地址从内存中取到指定运算符指令放入指令寄存器IR,(这个过程叫取址),而后发送运算符指令到指令译码器,(这个过程叫译码),然后将翻译后的指令发送到运算器中进行运算。最后运算的结果放到寄存器中。(最后这个过程叫执行)。
每次进行执行一条指令之后,PC的值会自动增加指向下一条指令。
No.2 ARM体系结构基础
ARM处理器介绍
RISC处理器以及CISC处理器
RISC处理器在功耗、体积、价格等方面有很大优势,所以在嵌入式移动终端领域应用极为广泛
CISC处理器:称为复杂指令集,不仅包含了常用指令,还包含了很多不常用的特殊指令,硬件结构复杂,指令条数较多,一般指令长度和周期都不固定,而且CISC处理器在性能上有很大优势,多用于PC及服务器等领域
SOC介绍
即片上系统,将一个系统中所需要的全部部件集成在一个芯片中
ARM指令集介绍
指令与指令集
大多数ARM处理器都支持两种指令集:
ARM指令集:所有指令(机器码)都占用32bit存储空间,代码灵活度高、简化了解码复杂度,执行ARM指令集时PC值每次自增4
编译原理
一条语句的编译过程:
分为三个过程,C语言代码,汇编语言,机器码
ARM存储模型介绍
数据类型

Byte 8bits 1字节
Halfword 16bits 2字节
Word 32bits 4字节
数据存储
Word型数据在内存的起始地址必须是4的整数倍
Halfword型数据在内存的起始地址必须是2的整数倍
因此数据本身是多少位在内存存储时就应该多少位对齐
字节序
在数据存储的过程种,有两种方式,大端对齐与小端对齐
大端对齐:低地址存放高位,高地址存放低位,如 a = 0x12345678; 第一个地址放12
小端对齐:低地址存放低位,高地址存放高位,如 a = 0x12345678; 第一个地址放78
注:ARM处理器一般使用小端对齐
对于指令集的储存
处理器处于Thumb指令集状态时:所有指令在内存的起始地址必须是2的整数倍,PC值由其[31:1]决定,[0]位未定义
注:即指令本身是多少位在内存存储时就应该多少位对齐
ARM工作模式介绍
工作模式
User 非特权模式,一般在执行上层的应用程序时ARM处于该模式
FIQ 当一个高优先级中断产生后ARM将进入这种模式
IRQ 当一个低优先级中断产生后ARM将进入这种模式
SVC 当复位或执行软中断指令后ARM将进入这种模式
Abort 当产生存取异常时ARM将进入这种模式
Undef 当执行未定义的指令时ARM将进入这种模式
System 使用和User模式相同寄存器集的特权模式
Monitor 为了安全而扩展出的用于执行安全监控代码的模式
按照状态区分:FIQ、IRQ、SVC、Abort、Undef属于异常模式,即当处理器遇到异常后会进入对应的模式
实践问题
1.简述CISC处理器与RISC处理器的本质区别是什么
CISC处理器:复杂指令集,包含了常用指令以及很多不常用的特殊指令,其硬件结构更为复杂,指令条数较多,一般指令长度和周期都不固定,CISC处理器在性能上有很大优势,多用于计算机以及服务器等领域
RISC处理器:精简指令集,只保留常用的的简单指令,硬件结构简单,复杂操作一般通过简单指令的组合实现,一般指令长度固定,且多为单周期指令,RISC处理器在功耗、体积、价格等方面有很大优势,所以在嵌入式移动终端领域应用极为广泛
2.简述什么叫做指令集
首先:指令是能够指示处理器执行某种运算的命令称为指令(如加、减、乘 ...),指令在内存中以机器码(二进制)的方式存在,而且每一条指令都对应一条汇编语句,我们编写的程序是指令的有序集合
其次:处理器能识别的指令的集合称为指令集,不同架构的处理器指令集不同,而且指令集也是处理器对开发者提供的接口
3.简述ARM处理器有哪几种工作模式以及什么时候进入哪种模式
User 非特权模式,一般在执行上层的应用程序时ARM处于该模式 //非特权模式
FIQ 当一个高优先级中断产生后ARM将进入这种模式 //异常模式
IRQ 当一个低优先级中断产生后ARM将进入这种模式 //异常模式
SVC 当复位或执行软中断指令后ARM将进入这种模式 //异常模式
Abort 当产生存取异常时ARM将进入这种模式 //异常模式
Undef 当执行未定义的指令时ARM将进入这种模式 //异常模式
System 使用和User模式相同寄存器集的特权模式 //特权模式
Monitor 为了安全而扩展出的用于执行安全监控代码的模式 //特权模式
No.3 ARM寄存器
寄存器
分类:包括通用寄存器、专用寄存器、控制寄存器
ARM寄存器
以上为ARM处理器内的寄存器,在某个特定模式下只能使用当前模式下的寄存器,一个模式下特有的寄存器其他模式下不可使用(也就是上方带角标的寄存器)
专用寄存器
执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址程序需要返回时将LR的值复制到PC即可实现
产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回
CPSR寄存器
CPSR(Current Program Status Register),用于存储当前程序状态的寄存器。
CPSR寄存器分为四个域,[31:24]为条件域用F表示、[23:16]为状态域用S表示、[15:8]为预留域用X表示、[8:0]为控制域用C表示
[10000]User [10001]FIQ [10010]IRQ [10011]SVC
[10111]Abort [11011]Undef [11111]System [10110]Monitor
[0]ARM状态 [1]Thumb状态
[0]开启FIQ [1]禁止FIQ
[0]开启IRQ [1]禁止IRQ
Bit[28]
> 当运算器中进行加法运算且产生符号位进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生符号位借位时该位自动置0,否则为1
> 当运算器中进行加法运算且产生进位时该位自动置1,否则为0
> 当运算器中进行减法运算且产生借位时该位自动置0,否则为1
当运算器中产生了0的结果该位自动置1,否则为0
当运算器中产生了负数的结果该位自动置1,否则为0
实践问题
1.简述ARM处理器中R13、R14、R15、CPSR寄存器的作用
R13:可以理解为栈指针,用于存储当前模式栈存储的栈顶的地址
R14:链接寄存器,用于跳转执行跳转指令时,或者产生异常时保存指令的地址
R15:程序计数器,和PC功能相同,存储当前取址指令的地址
CPSR:用于保存当前程序的状态
No.4 ARM异常处理
异常
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生,这时处理器就要将当前的程序暂停下来转去处理这个异常的事件,异常事件处理完成之后再返回到被异常打断的点继续执行程序
异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制
ARM处理器异常源(导致异常产生的事件)
FIQ 快速中断请求引脚有效 IRQ 外部中断请求引脚有效
Reset 复位电平有效 Software Interrupt 执行swi指令
Data Abort 数据终止 Prefetch Abort 指令预取终止
Undefined Instruction 遇到不能处理的指令
注:这里虽然异常源的名称和ARM的工作模式名称相同,但并不是同一个内容
ARM处理器异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切换成对应的异常模式
ARM处理器异常响应

1.拷贝CPSR中的内容到对应异常模式下的SPSR_<mode>
2.修改CPSR的值
2.1.修改中断禁止位禁止相应的中断
2.2.修改模式位进入相应的异常模式
2.3.修改状态位进入ARM状态
3.保存返回地址到对应异常模式下的LR_<mode>
4.设置PC为相应的异常向量(异常向量表对应的地址)
ARM处理器异常向量表

>异常向量表的本质是内存中的一段代码
> 表中为每个异常源分配了四个字节的存储空间
> 遇到异常后处理器自动将PC修改为对应的地址
> 因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口
注:ARM的异常向量表的基地址默认在0x00地址但可以通过配置协处理器来修改其地址
ARM处理器异常处理返回
1.将SPSR_<mode>的值复制给CPSR使处理器恢复之前的状态
2.将LR_<mode>的值复制给PC使程序跳转回被打断的地址继续执行
ARM处理器异常示例(IRQ异常)
注:整个过程CPSR保存的永远是当前程序运行状态SPSR只是异常时对原来的CPSR进行备份
ARM处理器异常优先级
指令流水线
指令流水线机制的引入确实能够大大的提升指令执行的速度,但在实际执行程序的过程中很多情况下流水线时是无法形成的,比如芯片刚上电的前两个周期、执行跳转指令后的两个周期等,所以指令流水线的引入以及优化只能使平均指令周期不断的接近1而不可能真正的达到1,且流水线级数越多芯片设计的复杂程度就越高,芯片的功耗就越高
ARM7采用3级流水线 ,ARM9采用5级流水线 ,Cortex-A9采用8级流水线
注:虽然流水线级数越来越多,但都是在三级流水线的基础上进行了细分
不管几级流水线,PC指向的永远是当前正在取指的指令,而当前正在执行 的指令的地址为PC-8
实践问题
1.FIQ为什么比IRQ的响应速度更快
首先FIQ在异常向量表位于最末,则可直接把异常处理写在异常向量表之后,省去跳转
其次,FIQ模式有5个私有寄存器(R8-R12),执行中断处理程序前无需压栈保存寄存器,直接处理中断(FIQ的地址处于异常向量表的最末尾,因此可以直接将FIQ的代码卸载异常向量表内,无需进行跳转对于一个中断过程,当执行异常处理程序时,需要将普通模式下某些寄存器的数据进行压栈的操作,用于腾出寄存器,但是FIQ拥有5个私有的寄存器,因此不需要进行压栈腾出寄存器)
同时,FIQ的优先级高于IRQ,两个中断同时发生时先响应FIQ而且FIQ可以打断RIQ,但RIQ不能打断FIQ
2.以IRQ为例,简述ARM处理器在正常执行程序过程中如果遇到IRQ异常会自动完成哪些动作以及处理完异常后如何返回到正常程序
自动完成
1.拷贝CPSR中的内容到IRQ异常模式下的SPSR_<mode>
2.修改CPSR的值,修改中断禁止位禁止IRQ的中断,修改模式位进入IRQ的异常模式 修改状态位进入ARM状态(所有的异常都是在ARM状态下进行的)
3.保存返回地址到IRQ异常模式下的LR_<mode>
4.设置PC为IRQ的异常向量(异常向量表对应的地址)
返回(手动编写)
5.将SPSR_<mode>的值复制给CPSR使处理器恢复之前的状态
6.将LR_<mode>的值复制给PC使程序跳转回被打断的地址继续执行
No.5 ARM汇编以及环境
汇编介绍
汇编中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等,都需要自己维护
C中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等,都是编译器来分配和维护
keil介绍
Keil MDK是用于基于ARM Cortex-M 微控制器的完整软件开发环境。它集成了uVision IDE以及C/C++编译器、调试器以及其他中间组件,支持众多芯片供应商,易于学习和使用
注:安装的集成开发环境版本是keil4版本,同时安装keil集成开发环境还需要安装ARM的gcc编译环境,即安装gcc交叉编译工具链
实践问题
1.简述C语言和汇编语言的本质区别是什么
每条汇编都会唯一对应一条机器码,且CPU能直接识别和执行,即汇编中所有的指令都是CPU能够识别和执行的,汇编中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等都需要自己维护但是每条C语句都要被编译器编译成若干条汇编指令才能被CPU识别和执行,即C语句中的指令CPU不一定能直接识别,需要编译器进行“翻译”,C中寄存器的使用、栈的分配与使用、程序的调用、参数的传递等都是编译器来分配和维护
No.6 ARM数据处理指令
数据处理指令
所谓指令就是能够编译生成一条32位的机器码,且能被CPU识别和执行,而数据处理指令一般是进行数学运算以及逻辑运算的指令
数据运算指令基本格式
《操作码》《目标寄存器》《第一操作寄存器》《第二操作数》
操作码:指示执行哪种运算
目标寄存器: 存储运算结果
第一操作寄存器:第一个参与运算的数据(只能是寄存器)
第二操作数: 第二个参与运算的数据(可以是寄存器或立即数)
注:所谓立即数,立即数的本质就是包含在指令当中的数(可以编译通过),属于指令的一部分
立即数的优点:立即数在对指令进行取址的时候就可以将其读取到CPU内,和变量相比不需要单独去内存内读取,速度快
立即数的缺点:不能是任意的32位的数字,有局限性
例如:MOV R0, #0x12345678,会报错也就不是立即数,MOV R0, #0x12不会报错,就是立即数
GCC编译器会在你写的指令无法被CPU识别的情况下进行替换成CPU可以识别的指令,当然这里的替换的前提条件是能够找出一条执行效果相同且可以被CPU识别的指令
加法指令ADD:ADD R1, R2, R3 也就是R1 = R2 + R3
减法指令SUB:SUB R1, R2, R3 也就是R1 = R2 - R3
逆向减法指令RSB:RSB R1, R2, #3 也就是R1 = 3 - R2
乘法指令MUL:MUL R1, R2, R3 也就是R1 = R2 * R3
按位与指令AND:AND R1, R2, R3 也就是R1 = R2 & R3
按位或指令ORR:ORR R1, R2, R3 也就是R1 = R2 | R3
按位异或指令EOR:EOR R1, R2, R3 也就是R1 = R2 ^ R3
左移指令LSL,右移指令LSR,由于ARM处理器内没有除法运算,一般会以右移指令代替
位清零指令BIC:MOV R2, #0xFF BIC R1, R2, #0x0F 结果为R1=0xF0(第二操作数中的哪一位为1,就将第一操作寄存器的中哪一位清零,然后将结果写入目标寄存器)
对于CPSR的内容:数据运算指令对条件位(N、Z、C、V)的影响,但是默认情况下数据运算不会对条件位产生影响,在指令后加后缀”S“才可以影响,如:ADDS,产生进位才会使CPSR变化
数据处理指令实例
@ 带进位的加法指令
@ 两个64位的数据做加法运算
@ 第一个数的低32位放在R1
@ 第一个数的高32位放在R2
@ 第二个数的低32位放在R3
@ 第二个数的高32位放在R4
@ 运算结果的低32位放在R5
@ 运算结果的高32位放在R6
@ 第一个数
@ 0x00000001 FFFFFFFF
@ 第二个数
@ 0x00000002 00000005
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x00000001
@ MOV R3, #0x00000005
@ MOV R4, #0x00000002
@ ADDS R5, R1, R3
@ ADC R6, R2, R4
@ 本质:R6 = R2 + R4 + 'C'
@ 带借位的减法指令
@ 第一个数
@ 0x00000002 00000001
@ 第二个数
@ 0x00000001 00000005
@ MOV R1, #0x00000001
@ MOV R2, #0x00000002
@ MOV R3, #0x00000005
@ MOV R4, #0x00000001
@ SUBS R5, R1, R3
@ SBC R6, R2, R4
@ 本质:R6 = R2 - R4 - '!C'
实践问题
1.编程实现使用32bit的ARM处理器实现两个128位的数据的加法运算。
@第一种情况,仅最低位产生进位
@ 第一个数
@ 0x00000003 00000002 00000001 FFFFFFFF
@ 第二个数
@ 0x00000004 00000003 00000002 00000005
@ MOV R1, #0xFFFFFFFF
@ MOV R2, #0x00000001
@ MOV R3, #0x00000002
@ MOV R4, #0x00000003
@ MOV R5, #0x00000005
@ MOV R6, #0x00000002
@ MOV R7, #0x00000003
@ MOV R8, #0x00000004
@ ADDS R9, R1, R5
@ ADC R10, R2, R6
@ ADD R11, R3, R7
@ ADD R12, R4, R8
@第二种情况,非仅最低位产生进位
@ 第一个数
@ 0x00000003 00000002 FFFFFFFF FFFFFFFF
@ 第二个数
@ 0x00000004 00000003 00000002 00000005
MOV R1, #0xFFFFFFFF
MOV R2, #0xFFFFFFFF
MOV R3, #0x00000002
MOV R4, #0x00000003
MOV R5, #0x00000005
MOV R6, #0x00000002
MOV R7, #0x00000003
MOV R8, #0x00000004
ADDS R9, R1, R5
ADCS R10, R2, R6
ADC R11, R3, R7
ADD R12, R4, R8
@ ADD不关心之前是否有进位,也不关心加了后是否有进位
@ ADDS不关心之前是否有进位,但关心加了后是否有进位
@ ADC只关心之前是否有进位,不关心加了后是否有进位
@ ADCS关心之前是否有进位,关心加了后是否有进位
2.如何理解MOV R2, #3 MOV R1, R2, LSL #1这两句代码。
MOV R1, R2, LSL #1 = MOV R1, (R2, LSL #1)也就是将R2左移一位之后的值移给R1,但R2值不变
No.7 ARM跳转与存储器访问指令
跳转指令
跳转指令:实现程序段的跳转,其本质就是修改了PC寄存器内储存的地址
方式一:直接修改PC寄存器的值(不建议使用,需要自己计算目标指令的绝对地址)
方式二:不带返回的跳转指令,本质是将PC寄存器的值修改成跳转标号下指令的地址(指令B)
方式三:带返回的跳转指令,本质就是将PC寄存器的值修改成跳转标号下指令的地址,同时将跳转指令下一条指令的地址存储到LR寄存器(指令BL)
比较指令:CMP指令的本质就是减法指令(SUBS),但没有将运算结果(1/0)存入目标寄存器
注:这里引入一个新得概念,指令条件码(可以直接写在指令后面,ARM指令集中大多数指令都可以带条件码后缀)
存储器访问指令
储存器访问指令:Load/Srore指令:访问(读写)内存
寻址方式:也就是CPU去寻找操作数的方式,对于不同得方式也有不同的名称,这里介绍几种典例
立即数寻址:此寻址方式是操作数包括在指令字节中,指令操作码后面字节的内容就是操作数本身,其数值由程序员预先指定,以字节的形式存放在储存器内,如MOV R1, #1 ADD R1, R2, #1
寄存器寻址:由指令指出某一个寄存器的内容作为操作数,如ADD R1, R2, R3
寄存器移位寻址:位操作指令可以对内部RAM种的位寻址区和某些有位地址的特殊功能寄存器进行位操作,如MOV R1, R2, LSL #1
寄存器间接寻址:由指令指出某一个寄存器的内容作为操作数,如STR R1, [R2]
基址加变址寻址:这种方式主要用于访问数据表格,在基本地址的基础上加上变址寄存器的内容
基址加变址寻址的索引方式:特殊的基址加变址寻址
相对寻址:以程序计数器PC的当前值作为基地址,与指令中给出的相对偏移量进行相加,得到的和作为程序的转移地址
直接寻址:在指令中包含由操作数的直接地址,该地址指出了参与操作的数据所在的字节地址
多寄存器访问:
@ 多寄存器内存访问指令
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STM R11,{R1-R4}
@ 将R1-R4寄存器中的数据写入到以R11为起始地址的内存空间中
@ LDM R11,{R6-R9}
@ 将以R11为起始地址的内存空间中的数据读取到R6-R9寄存器中
@ 当寄存器编号不连续时,使用逗号分隔
@ STM R11,{R1,R2,R4}
@ 不管寄存器列表中的顺序如何,存取时永远是低地址对应小编号的寄存器
@ STM R11,{R3,R1,R4,R2}
@ 自动索引照样适用于多寄存器内存访问指令
@ STM R11!,{R1-R4}
@ 多寄存器内存访问指令的寻址方式
@ MOV R1, #1
@ MOV R2, #2
@ MOV R3, #3
@ MOV R4, #4
@ MOV R11,#0x40000020
@ STMIA R11!,{R1-R4}
@ 先存储数据,后增长地址
@ STMIB R11!,{R1-R4}
@ 先增长地址,后存储数据
@ STMDA R11!,{R1-R4}
@ 先存储数据,后递减地址
@ STMDB R11!,{R1-R4}
@ 先递减地址,后存储数据
实践问题
1.用汇编语言实现以下逻辑
@ int R1 = 9;
@ int R2 = 15;
@ START:
@ if(R1 == R2){
@ STOP();}
@ else if(R1 > R2) {
@ R1 = R1 - R2;
@ goto START;}
@ else{
@ R2 = R2 - R1;
@ goto START; }
@ MOV R1, #9
@ MOV R2, #15
@ START:
@ CMP R1,R2
@ BEQ STOP
@ SUBGT R1, R1, R2
@ SUBLT R2, R2, R1
@ B START
@ STOP:
@ B STOP
2.使用汇编语言实现100以内的正整数之和
MOV R2, #1 @sum
MOV R1, #1 @num
ADDSUM:
ADD R1, R1, #1 @R1自增1后的结果存入R1寄存器
ADD R2, R2, R1 @将R2+R1的结果存储到R2寄存器
CMP R1, #100 @比较自增的最大值是否等于100
BNE ADDSUM @判断结果如果不相等则跳转至ADDSUM
No.8 栈的介绍与应用
栈的介绍
所谓栈:栈的本质就是一段内存,程序运行时用于保存一些临时数据,如局部变量、函数的参数、返回值、以及程序跳转时需要保护的寄存器等
增栈:压栈时栈指针越来越大,出栈时栈指针越来越小
减栈:压栈时栈指针越来越大,出栈时栈指针越来越小
满栈:栈指针指向最后一次压入到栈中的数据,压栈时,需要先移动栈指针到相邻位置然后再压栈
空栈:栈指针指向最后一次压入到栈中的数据的相邻位置,压栈时可直接压栈,之后在将栈指针移动到相邻位置
栈分为空增(EA)、空减(ED)、满增(FA)、满减(FD)四种,ARM处理器一般使用满减栈
栈的应用
首先引入ARM处理器中操作栈的两个指令STM(数据存入)和LDM(数据从栈取出)
注:局部变量不初始化则其的值是随机的,因为局部变量是存放在栈内的,但是全局变量的值不是随机的,因为它是存放在BSS段内的,系统会对BSS段内的内容进行自动清零。
对于栈的应用,这里使用C语言中的叶子函数调用来说明,以下是代码段
@ 1.叶子函数的调用过程举例
@ 初始化栈指针
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC
@ ADD R3, R1, R2
@ B STOP
@ FUNC:
@ 压栈保护现场
@ STMFD SP!, {R1,R2}
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ 出栈恢复现场
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
@ 2.非叶子函数的调用过程举例
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC1
@ ADD R3, R1, R2
@ B STOP
@ FUNC1:
@ STMFD SP!, {R1,R2,LR}
@ MOV R1, #10
@ MOV R2, #20
@ BL FUNC2
@ SUB R3, R2, R1
@ LDMFD SP!, {R1,R2,LR}
@ MOV PC, LR
@ FUNC2:
@ STMFD SP!, {R1,R2}
@ MOV R1, #7
@ MOV R2, #8
@ MUL R3, R1, R2
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
@ 执行叶子函数时不需要对LR压栈保护,执行非叶子函数时需要对LR压栈保护
实践问题
1.使用汇编语言模拟C语言叶子函数的调用过程
@ 初始化栈指针
@ MOV SP, #0x40000020
@ MIAN:
@ MOV R1, #3
@ MOV R2, #5
@ BL FUNC
@ ADD R3, R1, R2
@ B STOP
@ FUNC:
@ 压栈保护现场
@ STMFD SP!, {R1,R2}
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ 出栈恢复现场
@ LDMFD SP!, {R1,R2}
@ MOV PC, LR
No.9 ARM专用指令
状态寄存器传送指令
该指令用于访问(读写)CPSR寄存器,也就是对ARM的工作模式的修改
@ 读CPSR的值
@ MRS R1, CPSR
@ R1 = CPSR
@ 写,修改CPSR的值
@ MSR CPSR, #0x10
@ CPSR = 0x10
@ 在USER模式下不能随意修改CPSR,因为USER模式属于非特权模式
@ MSR CPSR, #0xD3
软中断指令
该指令用于触发软中断异常,进入异常,ARM处理器会进入SVC工作模式
@ 异常向量表 这几行代码主要是防止异常向量表的地址被占用覆盖
@ B MAIN
@ B .
@ B SWI_HANDLER
@ B .
@ B .
@ B .
@ B .
@ B .
@ 应用程序
@ MAIN:
@ MOV SP, #0x40000020
@ 初始化SVC模式下的栈指针,必须在修改模式之前进行初始化,否则将会使用其他模式栈寄存器
@ MSR CPSR, #0x10
@ 切换成USER模式,开启FIQ、IRQ
@ MOV R1, #1
@ MOV R2, #2
@ SWI #1
@ 触发软中断异常,上面那个#1,这个是对异常进行标识,能够让ARM进入指定的异常处理程序,通过该软中断立即数来区分用户不同操作,执行不同内核函数。
@ ADD R3, R2, R1
@ B STOP
@ 异常处理程序
@ SWI_HANDLER:
@ STMFD SP!,{R1,R2,LR}
@ 压栈保护现场
@ MOV R1, #10
@ MOV R2, #20
@ SUB R3, R2, R1
@ LDMFD SP!,{R1,R2,PC}^
@ 出栈恢复现场
@ 将压入到栈中的LR(返回地址)出栈给PC,实现程序的返回
@ ‘^’表示出栈的同时将SPSR的值传递给CPSR,实现CPU状态的恢复
协处理器指令
该指令用于操控协处理器的指令,协处理器一般存放处理器不频繁进行的指令
@ 1.协处理器数据运算指令
@ CDP
@ 2.协处理器存储器访问指令
@ STC 将协处理器中的数据写入到存储器
@ LDC 将存储器中的数据读取到协处理器
@ 3.协处理器寄存器传送指令
@ MRC 将协处理器中寄存器中的数据传送到ARM处理器中的寄存器
@ MCR 将ARM处理器中寄存器中的数据传送到协处理器中的寄存器
伪指令
该指令本身不是指令,编译器可以将其替换成若干条等效指令
@ 空指令
@ NOP
@ 指令
@ LDR R1, [R2]
@ 将R2指向的内存空间中的数据读取到R1寄存器
@ 伪指令
@ LDR R1, =0x12345678
@ R1 = 0x12345678
@ LDR伪指令可以将任意一个32位的数据放到一个寄存器
@ LDR R1, =STOP
@ 将STOP表示的地址写入R1寄存器
@ LDR R1, STOP
@ 将STOP地址中的内容写入R1寄存器
实践问题
1.编程实现通过状态寄存器传送指令,将ARM处理器的模式修改成USER模式并将FIQ与IRQ使能
MSR CPSR, #0x10 这句代码的功能就是将ARM的工作模式切换成USER模式,同时开启FIQ、IRQ中断请求
2.简述伪指令和指令的本质区别是什么
区别在于:伪指令本身不是指令,编译器可以将其替换成若干条等效指令然后执行,它是一种特殊的指令,由编译器或汇编器提供的操作,用于简化程序编写和优化,不会被转化生成机器码指令
No.10 伪操作与混合编程
伪操作介绍
伪操作:在程序中不会生成代码以及机器码,只是在编译之前告诉编译器如何去编译,GNU编译器的伪操作一般都以‘.’开头,如下例子:
@ .global symbol
@ 将symbol声明成全局符号
@ .local symbol
@ 将symbol声明成局部符号
@ .equ DATA, 0xFF
@ MOV R1, #DATA
@ 类似于#deifine的功能,宏定义,但是不生成机器码
@ .macro FUNC
@ MOV R1, #1
@ MOV R2, #2
@ .endm
@ 类似于C中的函数封装FUNC
@ .if 0
@ MOV R1, #1
@ MOV R2, #2
@ .endif
@ 条件编译
@.rept 3
@ MOV R1, #1
@ MOV R2, #2
@.endr
@将内部代码执行多次,比如上面的rept 3 ,那就是将内部代码每行都执行3次
@ .weak symbol
@ 弱化一个符号,即告诉编译器即便没有这个符号也不要报错
@ .weak func
@ B func
@ .word VALUE
@ 在当前地址申请一个字的空间并将其初始化为VALUE
@ MOV R1, #1 这一行在0x0000000地址
@ .word 0xFFFFFFFF 这一行在0x00000004 要占用一个语句地址
@ MOV R2, #2 这一行在0x00000008
@ .byte VALUE
@ 在当前地址申请一个字节的空间并将其初始化为VALUE
@ MOV R1, #1
@ .byte 0xFF
@ .align N
@ 告诉编译器后续的代码2的N次方对齐
@ .align 4
@ MOV R2, #2
@ .arm
@ 告诉编译器后续的代码是ARM指令
@ .thumb
@ 告诉编译器后续的代码是Thumb指令
@ .text
@ 定义一个代码段
@ .data
@ 定义一个数据段
@ .space N, VALUE
@ 在当前地址申请N个字节的空间并将其初始化为VALUE
@ MOV R1, #1
@ .space 12, 0x12 这个空间里的值全是0x12
@ MOV R2, #2
@@@@@@@不同的编译器伪操作的语法不同@@@@@@@@@@@
C语言与汇编的混合编程
所谓混合编程也就是文件内既可以写汇编代码也可以写C语言代码,C和汇编的混合编程原则:在哪种语言环境下符合哪种语言的语法规则
1. 在汇编中将C中的函数当做标号处理
2. 在C中将汇编中的标号当做函数处理
3. 在C中内联的汇编当做C的语句来处理
@ 1. 方式一:汇编语言调用(跳转)C语言
@ MOV R1, #1
@ MOV R2, #2
@ BL func_c
@ MOV R3, #3
int a=1,b=1,c=1,d=1,e=1,f=1,g;
int func(int a,int b,int c,int d,int e,int f)
{
return a+b+c+d+e+f;
}
int main(void)
{
g = func(a,b,c,d,e,f);
return 0;
}
@ 2. 方式二:C语言调用(跳转)汇编语言
@ .global FUNC_ASM
@ FUNC_ASM:
@ MOV R4, #4
@ MOV R5, #5
@ 3. C内联(内嵌)汇编
void func_c(void)
{
int a;
a ++;
//C内联(内嵌)汇编
asm
(
"MOV R6, #6\n"
"MOV R7, #7\n"
);
//C语言调用(跳转)汇编语言
FUNC_ASM();
a --;
}
ATPCS协议
所谓ATPCS协议:ATPCS协议是一种用于协同系统的信息系统协议,主要是统一一些标准和规范。
主要内容如下:
1.规定栈的种类
1.1 规定ARM处理器使用满减栈
2.规定寄存器的使用
2.1 规定R15用作程序计数器,不能作其他用途
2.2 规定R14用作链接寄存器,不能作其他用途
2.3 规定R13用作栈指针,不能作其他用途
2.4 规定函数的参数不多于4个时用R0-R3传递,当函数的参数多于4个时,多出的部分用栈传递
2.5 规定函数的返回值使用R0传递
2.6 规定其它寄存器主要用于存储局部变量