ARM01-ARM体系架构

一、ARM基本概念

① RISC和CISC:

  • RISC(Reduced Instruction Set Computer):精简指令集计算机,其核心思想是精选高频使用的简单指令。绝大多数指令能在单时钟周期内完成,且指令格式统一规整。这种设计显著提升了指令的并行处理能力和执行效率,同时硬件实现相对简化。(芯片简单、 成本低、 体积小、 功耗小,如ARM架构(手机、平板、M系列Mac)、RISC-V(新兴开源))
  • CISC(Complex Instruction Set Computer):复杂指令集计算机,其特点是指令系统庞大且功能复杂。它通过设计多功能指令来减少程序中的指令数量,但不同指令的执行周期差异较大------部分复杂指令需占用多个时钟周期。这种架构的硬件实现较为复杂。(性能强、 芯片复杂、 成本高、 体积高、 功耗大,如x86架构(Intel Core系列、AMD锐龙系列)

② ARM处理器版本分类:

  • CortexA: Application 高端应用领域、消费电子, 向托管丰富的操作系统平台的设备和用户应用提供全方位的解决方案, 包括超低成本的手机、智能手机、 移动计算平台、 数字电视、 机顶盒、 企业网络、 打印机和服务器解决方案。(CortexA7 、A8、A9)
  • **CortexR: Real-time 实时性领域,**它专用于大容量深层嵌入式片上系统应用, 如硬盘驱动器控制器、 无线基带处理器、 消费性产品和汽车系统的电子控制单元。
  • **CortexM: Micro 微控制领域,**它用以满足需要有效且易于使用的控制和信号处理功能混合的数字信号控制市场。 专门面向电动机控制、 汽车、 电源管理、 嵌入式音频和工业自动化市场的新兴类别的灵活解决方案。

③ 冯·诺依曼架构:
指令和数据共享同一个存储空间和同一套总线,硬件结构简单、成本低且灵活性高,但CPU无法同时获取指令和操作数,导致效率较低。典型应用如通用计算机。

④ 哈佛架构:
指令和数据拥有独立的存储空间和独立的总线(即两套总线),结构较复杂且成本较高,灵活性相对不足,但能实现指令与数据的同步读取。典型应用如DSP、MCU。

二、ARM内核结构

① I.MX6ULL芯片内核版本:单核 ARM Cortex-A7,指令集版本:armv7。

I.MX6ULL是恩智浦(NXP)推出的一款高性价比、低功耗的应用处理器。

② ALU(Arithmetic Logic Unit): 算术逻辑单元,执行所有数学运算(加减乘除)和逻辑运算(与或非)的核心部件。

③ 通用寄存器( 内核 16个 ,R0~R12) :存放数据。

④ SP(R13):Stack Pointer (栈指针寄存器),指向当前栈顶地址用于管理栈区空间。

⑤ LR(R14):Linked Regiter (链接寄存器), 保存函数返回地址。

⑥ PC(R15):Programmer Counter(程序计数器),本质上是一个指针, 用于指向当前执行指令的下一条指令地址。

⑦ CPSR: Current Programmer Status Register (当前程序状态寄存器), 存放当前程序运行到某一步时相关的状态( 溢出、 进位、 借位)。
⑧ SPSR: Save Programmer Status Register (保存程序状态寄存器), 当异常\中断发生时, 备份CPSR寄存器中的的状态(值),以便异常处理结束后能恢复现场。
⑨ MMU: Memory Management Unit (内存管理单元),将程序中的虚拟内存空间映射到实际物理内存空间, 提高物理内存利用率。这是实现现代操作系统(如Linux)内存隔离和保护的基础。

⑩ CACHE:高速缓存, 提高CPU访问数据的速度

  • DCACHE:Data Cache,,数据缓存。
  • ICACHE:Instruction Cache, 指令缓存。

⑪ nor flash:每一个字节都可以被寻址, 带有地址总线和数据总线, 数据访问时可以向RAM一样( 单片机)。
⑫ nand flash:块设备数据, 通常以512byte去访问( 电脑、 SOC)。

⑬ AHB:Advanced High Bus, 先进高速总线(连接一些通信速率快的外设USB、 网口、 RAM)。

⑭ APB: Advanced Peripheral Bus, 先进外设总线( 连接一些通信速率慢的外设Timer、 GPIO、 UART)。

⑮ 三级流水线结构:pc取指令、译码器译码、内核执行(取指 -> 译码 -> 执行 同一时刻并发执行)

三、ARM处理器的7种工作模式

  • User:非特权模式,大部分任务执行在这种模式
  • FIQ:当一个高优先级(fast)中断产生时将会进入这种模式
  • IRQ:当一个低优先级(normal)中断产生时将会进入这种模式
  • Supervisor:当复位或软中断指令执行时将会进入这种模式
  • Abort:当存取异常时将会进入这种模式
  • Undef:当执行未定义指令时会进入这种模式
  • System:使用和User模式相同寄存器集的特权模式

① 汇编指令的s后缀,几乎所有的汇编指令都可以在指令后面加上s后缀,s后缀的含义是在指令执行过程中会更新cpsr寄存器的N,Z,C,V位

1)N:在结果是有符号的二进制补码情况下,如果结果为负数,则N=1;如果结果为非负数,则N=0

2)Z:如果结果为0,则Z=1;如果结果为非零,否则Z=0

3)C:是针对无符号数最高有效位向更高位进位时C=1;减法中运算结果的最高有效位从更高位借位时C=0

4)V:该位是针对有符号数的操作,会在下面两种情形变为1,两个最高有效位均为0的数相加,得到的结果最高有效位为1;两个最高有效位均为1的数相加,得到的结果最高有效位为0;除了这两种情况以外V位为0

四、ARM异常处理流程

① 中断向量: 标号, 根据向量找到中断向量表中的中断服务函数;

② 中断向量表: 数组, 数组中存放的是中断服务函数的入口地址;

③ 异常向量: 跳转到异常服务函数指令的地址;

④ 异常向量表: 数组, 数组中存放的是跳转到对应异常服务函数的指令;

⑤ ARM异常处理流程

当异常产生时, ARM core:

  • 拷贝 CPSR 到 SPSR_<mode>
  • 设置;适当的 CPSR 位:

1) 改变处理器状态进入 ARM 态

2)改变处理器模式进入相应的异常模式

3)设置中断禁止位禁止相应中断 (如果需要)

  • 保存返回地址到 LR_<mode>
  • 设置 PC 为相应的异常向量

返回时, 异常处理需要:

  • 从 SPSR_<mode>恢复CPSR
  • 从LR_<mode>恢复PC
  • Note:这些操作只能在 ARM 态执

⑥ 异常向量表地址分配:

0x00:复位(Reset)

0x04:未定义指令(Undifined Instruction)

0x08:软件中断(Software Interrupt)

0x0C:指令预取中止(Prefetch Abort)

0x10:数据访问中止(Data Abort)

0x14:(Reserved)保留

0x18:IRQ中断

0x1C:FIQ中断​

五、ARM汇编指令

5.1 学习汇编的目的

① 学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下运行。

② 启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。

③ 启动代码的主要任务有:

  • 初始化异常向量表;
  • 初始化各工作模式的栈指针寄存器;
  • 开启arm内核中断允许;
  • 将工作模式设置为user模式;
  • 完成上述工作后,引导程序进入c语言主函数执行;

5.2 keil4 编写格式

**伪操作:**不是 ARM 处理器实际的指令(如 MOV, ADD 等),而是汇编器的命令,用于指导汇编器如何工作。

复制代码
     ;汇编语言中代表注释
    area reset, code, readonly ;声明了一个名为 RESET 的代码段
    code32          ;代表了后面的指令是ARM指令
    entry           ;代表该段是程序的入口点

    ;汇编指令

    end             ;源文件结束

area:定义一个代码段或者数据段。程序、数据、堆栈等内容需要分别组织在不同的段中。

reset:段的名字。名字 reset 具有很强的暗示性,通常用于表示复位向量段,即CPU上电或复位后首先执行的代码所在的段。

code:指定该段的属性为代码,表明该段包含可执行的指令。

readonly:指定该段的属性为只读。对于代码段而言,这一属性通常是默认且必需的。

code32:表示后续指令使用 32位的 ARM 指令集。
thumb: 表示后续指令使用 16位的 Thumb 指令集。
entry:代表该段是程序的入口点。

end:源文件结束。

② ARM指令集可以分为六大类

  • 分别为数据处理指令(完成CPU内部的计算);

数据处理指令根据指令实现处理功能可分为以下六类:数据传送指令;算术运算指令;逻辑运算指令;比较指令;测试指令;乘法指令。

  • Load/Store指令(完成CPU与内存IO外设之间的数据传输);
  • 跳转指令(完成程序的跳转);
  • 程序状态寄存器处理指令(完成CPSR的管理);
  • 协处理器指令(完成CPU扩展功能的实现);
  • 异常产生指令(用户程序异常触发)。

**③ 指令格式说明:**immediate:立即数;register:寄存器;shifted register:平移

  • {S}:更新 CPSR 标志位。在执行数据传送的同时,会更新当前程序状态寄存器(CPSR)中的条件标志位(主要是 N(负数)、Z(零)、C(进位)和 V(溢出))。ALU运算的结果会影响cpsr寄存器中的NZCV位,指令后面需要
    加s

(1)不加 S:仅传送数据,不修改标志位(如 MOV R0, #5)。

(2)加 S:传送数据并根据结果更新标志位(如 MOVS R0, #5,结果非零,所以 Z 标志位会被清零),常用于后续的条件跳转。

  • <c> :这是一个条件执行后缀,用于指定指令在什么条件下才执行。它会被替换为具体的条件助记符,如 EQ(相等)、NE(不相等)、GT(大于)、LT(小于)、HS(高于或相同)等。如果不写,则默认无条件执行。

  • <Rd> :目标寄存器 (Destination Register),代表存放运算结果的目标寄存器。它会被替换为具体的 ARM 通用寄存器,如 R0R1......R12 等。

  • <Rn>、<Rm> ------ 第一、二源寄存器 (Source Register),代表第一、二个源操作数寄存器,即被传送的那个"值"来自哪个寄存器。它会被替换为具体的 ARM 通用寄存器,如 R2R3 等。

  • <Rs> ------ 第三源寄存器 / 移位量寄存器 (Shift Register),这是一个用于指定移位位数的寄存器。指令执行时,处理器会读取 <Rs> 寄存器中的数值,作为对 <Rm> 的值进行左移(LSL)的位数。

④ 立即数:直接写在指令里的一个固定常数

⑤ 判断12位立即数条件:

  • 如果某个数的数值范围是0~0xFF之间,那么这个数一定是立即数;
  • 把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能超过8位;
  • 这个数的二进制序列凑够8位之后的的右边必须为偶数个连续的 0。

例如:

1)0x234 = 0000 0000 0000 0000 0000 0010 0011 0100

最高位1至最低位1之间的二进制数序列:1000 1101没有超过8位,末尾1的右边有2个0,所以0x234是立即数。

2)0x7f8 = 0000 0000 0000 0000 0000 0111 1111 1000

最高位1至最低位1之间的二进制数序列:1111 1111从第一个1开始到最后一个1之间没有超过8位 ,末尾1的右边有3个0,不满足第二条,所以0x7f8不是立即数。

⑥ 移位

逻辑右移( LSR) : 最高位补0, 不需要考虑数值的符号位( 无符号数)
算术右移( ASR) : 最高位补符号位, 考虑数值的符号位( 有符号数)
循环右移( ROR) : 循环将每次移出的最低位补到最高位

5.3 汇编指令

(1)MOV :加载12位立即数到寄存器或转移一个寄存器的值到另外一个寄存器

基本格式:

mov{S}<c> <Rd>, #<const>

mov{S}<c> <Rd>, <Rm>

mov{S}<c> <Rd>, <Rm>, lsl #<const>

mov{S}<c> <Rd>, <Rm>, lsl #<Rs>

复制代码
;mov指令
	mov r0, #1				;move 1 to r0
	mov r1, #2	    		;move 2 to r1
	mov r2, r1				;move r1 to r2
	mov r3, r0, lsl #2		;move (r0 << 2) to r3
	mov r4, r3, lsl r1	    ;move (r3 << r1) to r4

(2)ADD:加法指令

基本格式:

add{S}<c> <Rd>, <Rn>, #<const>

add{S}<c> <Rd>, <Rn>, <Rm>

add{S}<c> <Rd>, <Rn>, <Rm> , <type> <Rs>

复制代码
;add指令
	add r4, r3, #6	 		;r4 = r3 + 6
	add r5, r4, r1			;r5 = r4 + r1
	add r5, r1, r0, lsl #1	;r5 = r1 + (r0 << 1)
	add r6, r1, r0, lsl r0	;r6 = r1 + (r0 << r0)

(3)SUB 减法指令

基本格式:

sub{S}<c> <Rd>, <Rn>, #<const>

sub{S}<c> <Rd>, <Rn>, <Rm>

sub{S}<c> <Rd>, <Rn>, <Rm> , <type> <Rs>

复制代码
;sub指令
	mov r0, #2
	mov r1, #3
	mov r3, #1
	sub r2, r1, #1
	sub r2, r1, r0
	sub	r2, r1, r3, lsl r0	      ;r2 = r1 - (r3 << r0)
	mov r0, #0
	sub r0, r0, #1

(4)MVN:将立即数先按位取反再装入到通用寄存器

基本格式: mvn{S}<c> <Rd>, #<const>
mvn r0, #0

(5)LDR:向寄存器中加载一个非立即数;将RAM中的数据加载到通用寄存器

LDR = 从RAM内存地址读数据到寄存器(取);STR = 从寄存器写数据到RAM内存地址(存)。

基本格式:

LDR{<c>}{<q>} <Rt>, <label>

LDR<c> <Rt>, \{, #+/-\} ; 如ldr r0,r1,#4,将内存地址为R1+4的字数据读入寄存器R0,

LDR<c> <Rt>, \, #+/-<imm12> 如:ldr r0, r1, #8 ;将内存地址R1的字数据读入R0,之后R1+8

LDR<c> <Rt>, \, #+/-\! 如:ldr r0,r1,#8 ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。

复制代码
	ldr r0, =0x2A3B4F
	ldr r0, =0x40000000
	ldr r1, [r0]      ; r1 = *r0

	ldr r0, =0x40000000
	ldr r1, =0x1A2B3C4D
	str	r1, [r0]     ; *r0 = r1
	str r1, [r0, #4] ; *(r0 + 4) = r1 1 是4个字节
	str r1, [r0], #4	 ; *r0++ = r1  R1 = *(R0); R0 += 4
	str r1, [r0, #4]!	 ; *++r0 = r1  R1 = *(R0 += 4)

	; 两数求和
	ldr r0, =0x40000000  ; i
	ldr r1, [r0]

	ldr r0, =0x40000004  ; j
	ldr r2, [r0]

	add r3, r1, r2
	ldr r0, =0x40000008	 ; sum
	str r3, [r0]

(6)BIC、ORR

bic指定位清零指令:BIC{S}<c> <Rd>, <Rn>, #<const>;将Rn操作数中 const为bit1的位清0,清0的结果写回到通用寄存器中Rd。

orr指定位置位指令:ORR{S}<c> <Rd>, <Rn>, #<const>;将Rn操作数中 const为bit1的位置1,置1的结果写回到通用寄存器中Rd。

复制代码
	; bic orr
    ldr r0, =0xFFFFFFFF		 ; 1111 1111 1111 1111 1111 1111 1111 1111
	;清0					 ; 1111 1111 1111 1111 1111 1011 1111 1111
	bic r0, r0, #(1 << 10)	 ; 0000 0000 0000 0000 0000 0100 0000 0000

	mov r0, #0				 ; 0000 0000 0000 0000 0000 0000 0000 0000
	;置1						
	orr r0, r0, #(1 << 7)	 ; 0000 0000 0000 0000 0000 0000 1000 0000

(7)CMP 比较指令****用于比较两个寄存器的值或者比较一个寄存器和立即数的值,其原理是对待比较的两个数求差,看结果是否为0,这个指令会无条件修改N,V,C,Z位。

复制代码
; 找三个数最大值
	mov r0, #3
	mov r1, #5
	mov r3, #7

	cmp r0, r1
	movlt r2, r1
	movgt r2, r0

	cmp r2, r3
	movlt r2, r3

(8)B 跳转指令,函数调用

b:无条件指令跳转,在函数跳转前无法将函数返回地址装入到lr寄存器。
bl:在函数跳转前可以将返回地址装入到lr寄存器。
bx:bx后面加寄存器,可以将寄存器中的地址恢复给pc。

复制代码
;跳转 在ARM体系结构中,向PC写入值即执行跳转,处理器会从目标地址开始取指执行。   
0x0	mov pc, #8  ; 将立即数8传送到程序计数器PC(即跳转到地址8)
0x4	mov r0, #1  ; 这条指令不会被执行,第一条指令已经改变了PC
0x8	mov r1, #2  ; 这条指令会被执行,第一条指令已经改变了PC
	;b 跳转指令
	b lable
	mov r0, #2
	mov r1, #3
lable
	mov r2, #5

	;找出最大值
	mov r0, #5
	mov r1, #3
	mov r3, #7
	cmp r0, r1
	bgt great
	blt less
great
	mov r2, r0
	b lable
less
	mov r2, r1
	b lable
lable
	cmp r2, r3
	bgt finished
	movlt r2, r3
finished
	b finished

	;do-while 实现1-100之和
	mov r0, #1	;i
	mov r1, #0  ;sum
lable
	add r1, r0, r1
	add r0, r0, #1
	cmp r0, #100
	ble lable    ; i <= 100
finished	
	b finished

	;while 实现1-100之和
	mov r0, #1
	mov r1, #0
loop 
	cmp r0, #100
	bgt finished
	add r1, r0, r1
	add r0, r0, #1
	ble loop
finished	
	b finished

(9)栈指令****在每次函数调用前必须要保护现场,在函数调用完毕以后要恢复现场

stmfd:将寄存器的值保存到栈区(入栈),保护现场。

ldmfd:将寄存器的值从栈区取出(出栈),恢复现场

1) 在底层(如ARM处理器)的实现中,根据栈指针(SP) 的指向和内存增长方向,栈可以组合出四种类型:

  • 空减栈:先写入数据,再让栈指针自减;
  • 空增栈:先写入数据,再让栈指针自增
  • 满减栈:先让栈指针自增,再写入数据;
  • 满增栈:先让栈指针自减,再写入数据。

增栈:入栈时,sp指针往上走
减栈:入栈时,sp指针往下走
空栈:先写数据,再挪指针 *sp++ *sp--
满栈:先挪指针,再写数据 *++sp *--sp

2)arm体系采用的方案是满减栈,但是在进行操作之前,我们必须告诉2440栈底的位置,这里我们把栈底设置为0x40001000,从地址0x40000000开始的0x1000这段内存空间对应的是2440内部的一段ram,总共4k。实际能够使用的内存空间为0x40000000\~0x40000FFF,设置栈底指针寄存器: ldr sp =0x40001000

3) 入栈保护指令stmfd(STMDB) :STMFD<c> <Rn>{!}, <registers> 其中Rn表示栈底指针寄存器,< registers >表示需要入栈保护的寄存器,!表示入栈之后sp自动自减。

4) 出栈恢复指令ldmfd(LDM/LDMIA/) :LDMFD<c> <Rn>{!}, <registers>

中Rn表示栈底指针寄存器,< registers >表示需要入栈保护的寄存器,!表示出栈之后sp自动自增。

复制代码
 ;函数实现
    ldr pc, =_main

_asm_max
	mov r0, #20
	mov r1, #10
	stmfd sp!, {r0, r1, lr} ;{r0-r12,lr}入栈:保护现场
	bl _asm_min
	ldmfd sp!, {r0, r1, lr}	;出栈:恢复现场
	cmp r0, r1
	movgt r2, r0
	movlt r2, r1
	bx lr   ; mov pc, lr

_asm_min
	mov r0, #4
	mov r1, #3
	cmp r0, r1
	movlt r2, r0
	movgt r2, r1
	bx lr   ; mov pc, lr
_main
	ldr sp, =0x40001000

(10)mrs,msr

mrs指令:读取某个特殊寄存器(cpsr)值到通用寄存器
msr指令:将通用寄存器的值写入到特殊寄存器(cpsr)

1) 修改处理器工作模式为USER模式:

  • 将cpsr寄存器中的值读到通用寄存器中

  • 将通用寄存器中的低5位修改为:10000(USER)

  • 将通用寄存器中的值写回到CPSR寄存器

    复制代码
    ;切换工作模式到USER模式
    mrs r0, cpsr
    bic r0, r0, #0x1F	 ;低5位清0
    orr r0, r0,#0x10	  ;10000 置1
    msr cpsr_c, r0
    
    ;设置USER模式下的栈
    ldr sp, =0x40001000
    sub sp, sp, #1024

(11) 在汇编中调用c语言编写的函数

1)设有c语言定义的函数int c_max(int a, int b);在汇编代码中调用该函数,只需用import声明函数名即可,之后就可以使用bl指令调用该函数,注意,既然是调函数,就一定要保护现场。

2)汇编调用C函数:
汇编->C函数传参:通过通用寄存器传参,将C语言函数返回值保存到R0寄存器。
如果参数个数小于等于4个时,可以通过通用寄存器R0 - R3传参。
当参数个数超过4个时,从第五个参数开始就必须通过栈来传参(入栈,出栈)。

复制代码
//main.c
int c_max(int a, int b)
{
 	return a > b ? a : b;
}

;asm.s
	import c_max	 ;汇编里面调用C语言函数
	mov r0, #4
	mov r1, #8
	stmfd sp!, {r0-r12,lr}
	bl c_max
	ldmfd sp!, {r0-r12,lr}

(12)在c语言中调用汇编编写的函数

1)在汇编中用export声明函数,同时需要在c语言中用extern声明函数,按照标准,调用者负责保护现场和恢复现场。

2)C语言调用汇编:
C -> 汇编传参:将C语言传递的参数保存到通用寄存器中。
汇编程序结果通过R0寄存器返回。

复制代码
//main.c
void delay(unsigned int n)
{
	while(n--);
}
extern int _asm_min(int a, int b); //先声明
int main(void)
{
	int ret = _asm_min(10, 20);
	while(1)
	{
		delay(ret);
	}	
	return 0;
}

;asm.s
	import main
	b main
	export _asm_min		;C语言中调用汇编
_asm_min
;	mov r0, #4
;	mov r1, #3
	cmp r0, r1
	movlt r2, r0
	movgt r2, r1
	mov r0, r2
	bx lr   ; mov pc, lr
finished
	b finished
相关推荐
XUHUOJUN16 小时前
AKS 不是安装在 Windows Server 上,而是运行在 Windows Server 之上的 Azure 平台能力
windows·架构·k8s·azure local·azure stack
RisunJan17 小时前
Linux命令-screen(终端复用器)
linux·运维
啊哦11117 小时前
安装VMware Workstation
linux·运维·服务器
Cosolar17 小时前
AI Agent 架构原理详解:从一次提问到任务完成的完整闭环
人工智能·后端·架构
持敬chijing17 小时前
Linux基础命令与目录命令
linux·运维·服务器
yunwei3718 小时前
实证研究:AI Agent 规则需要上下文与分层强制执行
linux·安全·ai编程
小张成长计划..18 小时前
【Linux】12:进程状态
linux·运维·服务器
cjr_xyi18 小时前
AT1202Contest_c binarydigit 题解
c语言·c++·算法
会周易的程序员18 小时前
Libnodave S7 通信库:架构设计与实现解析
linux·c++·物联网·架构·c·s7·工业协议