Linux入门第二部分(ARM裸机篇) 第二章 Cortex-A7 MPCore架构与ARM汇编基础

参考教程:https://www.bilibili.com/video/BV1yE411h7uQ/?spm_id_from=333.1387.favlist.content.click

一、Cortex-A7 MPCore架构简介

1、Cortex-A7 MPCore简介

(1)Cortex-A7 MPCore支持在一个处理器上选配1~4个内核。

(2)Cortex-A7 MPCore多核配置如下图所示。

2、Cortex-A处理器运行模型

(1)Cortex-A7处理器有9种处理模式,如下表所示。这几个运行模式可以通过软件进行任意切换,也可以通过中断或者异常来进行切换。

|-----------------|-----------------------------|
| 模式 | 描述 |
| User(USR) | 用户模式,非特权模式,大部分程序运行的时候就处于此模式 |
| FIQ | 快速中断模式 |
| IRQ | 一般中断模式 |
| Supervisor(SVC) | 超级管理员模式 |
| Monitor(MON) | 监视模式 |
| Abort(ABT) | 数据访问终止模式 |
| Hyp(HYP) | 超级监视模式 |
| Undef(UND) | 未定义指令终止模式 |
| System(SYS) | 系统模式 |

(2)除了User(USR)用户模式以外,其它8种运行模式都是特权模式。大多数的程序都运行在用户模式,用户模式下是不能访问系统所有资源的,有些资源是受限的,要想访问这些受限的资源就必须进行模式切换。但是用户模式是不能直接进行切换的,用户模式下需要借助异常来完成模式切换,当要切换模式的时候,应用程序可以产生异常,在异常的处理过程中完成处理器模式切换。

(3)当中断或者异常发生以后,处理器就会进入到相应的异常模式种,每一种模式都有一组寄存器供异常处理程序使用,这样的目的是为了保证在进入异常模式以后,用户模式下的寄存器不会被破坏。

3、Cortex-A7寄存器组简介

(1)需要说明的是,本小节介绍Cortex-A 的内核寄存器组,并非芯片的外设寄存器。

(2)ARM架构提供了16个32位的通用寄存器(R0~R15)供软件使用,前15个(R0~R14)可以用作通用的数据存储,R15是程序计数器PC,用来保存将要执行的指令。ARM 还提供了一个当前程序状态寄存器CPSR和一个备份程序状态寄存器SPSR,SPSR寄存器就是CPSR寄存器的备份。

(3)Cortex-A7处理器有9种运行模式,每一种运行模式都有一组与之对应的寄存器组。每一种模式可见的寄存器包括15个通用寄存器(R0~R14)、一个或两个程序状态寄存器和一个程序计数器PC。在这些寄存器中,有些是所有模式所共用的同一个物理寄存器,有一些是各模式自己所独立拥有的,各个模式所拥有的寄存器如下图所示,浅色字体的是与User模式所共有的寄存器,蓝绿色背景的是各个模式所独有的寄存器。

(4)程序状态寄存器定义:

①所有的处理器模式都共用一个CPSR物理寄存器,因此CPSR可以在任何模式下被访问。

②CPSR是当前程序状态寄存器,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志等一些状态位以及一些控制位。

③所有的处理器模式都共用一个CPSR必然会导致冲突,为此,除了User和Sys这两个模式以外,其它7个模式每个都配备了一个专用的物理状态寄存器,叫做SPSR(备份程序状态寄存器),当特定的异常中断发生时,SPSR寄存器用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后可以用SPSR中保存的值来恢复CPSR。

④因为User和Sys这两个模式不是异常模式,所以并没有配备SPSR,因此不能在User和Sys模式下访问SPSR,会导致不可预知的结果。

⑤由于SPSR是CPSR的备份,因此SPSR和CPSR的寄存器结构相同,如下图所示。

|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| 位段 | 定义 |
| N(bit31) | 当两个补码表示的有符号整数运算的时候,N=1表示运算对的结果为负数,N=0表示结果为正数 |
| Z(bit30) | Z=1表示运算结果为零,Z=0表示运算结果不为零,对于CMP指令,Z=1表示进行比较的两个数大小相等 |
| C(bit29) | 在加法指令中,当结果产生了进位,则C=1,表示无符号数运算发生上溢,其它情况下C=0;在减法指令中,当运算中发生借位,则C=0,表示无符号数运算发生下溢,其它情况下 C=1;对于包含移位操作的非加/减法运算指令,C中包含最后一次溢出的位的数值,对 于其它非加/减运算指令,C位的值通常不受影响 |
| V(bit28) | 对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时,V=1表示符号位溢出,通常其它位不影响V位 |
| Q(bit27) | 仅ARM v5TE_J架构支持,表示饱和状态,Q=1表示累积饱和,Q=0表示累积不饱和 |
| IT1:0(bit26:25) | 和IT7:2(bit15:10)一起组成IT7:0,作为IF-THEN指令执行状态 |
| J(bit24) | 仅ARM_v5TE-J架构支持,J=1 表示处于Jazelle状态,此位通常和T(bit5)位一起表示当前所使用的指令集 |
| GE3:0(bit19:16) | SIMD 指令有效,大于或等于 |
| IT7:2(bit15:10) | 和IT1:0(bit26:25)一起组成IT7:0,作为IF-THEN指令执行状态 |
| E(bit9) | 大小端控制位,E=1表示大端模式,E=0表示小端模式 |
| A(bit8) | 禁止异步中断位,A=1表示禁止异步中断 |
| I(bit7) | I=1禁止IRQ,I=0使能IRQ |
| F(bit6) | F=1禁止FRQ,F=0使能FRQ |
| T(bit5) | 控制指令执行状态,表明本指令是ARM指令还是Thumb指令,通常和J(bit24)一起表明指令类型 |
| M4:0(bit4:0) | 处理器模式控制位 |

(5)Cortex-A7寄存器组有很深的学问,此处仅做了简单介绍,如果想要深入了解,可以参考官方文档。

二、ARM汇编基础

1、了解Cortex-A汇编的目的

(1)Cortex-A芯片上电后,一开始还不能跑C语言程序,早期的底层硬件初始化(比如关闭看门狗、设置时钟、设置总线),必须由汇编指令完成。

(2)大部分Cortex-A芯片(比如全志、瑞芯微)DDR需要程序员写汇编初始化,不过I.MX6ULL自带Boot ROM,上电自动完成DDR初始化,所以在裸机开发时不需要再自行写汇编初始化DDR。

(3)C语言运行必须要有栈(stack),栈由SP(栈指针)指定,使用汇编指令可以设置SP,把栈放到DDR内存里,只有SP配置完成,栈环境就绪,后面才能跳转到C语言函数里执行C代码。

(4)Cortex-A芯片上电流程:上电 → BootROM运行 → 加载裸机程序(汇编启动文件) → 汇编初始化、设置SP → 跳转到C语言main函数。

2、GNU汇编语法

(1)GNU汇编语法适用于所有的架构,并不是ARM独享的,GNU汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分:

label: instruction @ comment

①label即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意label后面的":",任何以":"结尾的标识符都会被识别为一个标号。

②instruction即指令,也就是汇编指令或伪指令。

③"@"符号后面的内容是注释,作用仅限于本行,comment就是注释内容。

(2)ARM中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。

(3)GNU汇编语法中的伪操作:

①用户可以使用".section"伪操作来定义一个段,具体如下:

.section .testsection @定义一个 testsetcion 段

②汇编系统预定义了一些段名,".text"表示代码段,".data"表示初始化的数据段,".bss"表示未初始化的数据段,".rodata"表示只读数据段。

③汇编程序的默认入口标号是"_start",不过也可以在链接脚本中使用ENTRY来指明其它的入口点,下面的代码就是使用"_start"作为入口标号,其中".global"是伪操作,表示"_start"是一个全局标号,类似C语言里面的全局变量一样。

cpp 复制代码
.global _start 

_start:
    ldr r0, =0x12 @r0=0x12

④常见的伪操作如下表所示,GNU汇编还有其它的伪操作,具体可以参考官方文档。

|---------|----------------------------------------------------------|
| 伪操作 | 描述 |
| .byte | 定义单字节数据,比如".byte 0x11" |
| .short | 定义双字节数据,比如".short 0x4151" |
| .long | 定义四字节数据,比如".long 0x11451400" |
| .equ | 赋值语句,格式为".equ <变量名>, <表达式>",比如".equ num, 0x11451400" |
| .align | 数据字节对齐声明,格式为".align <对齐数>" |
| .end | 表示源文件结束 |
| .global | 定义一个全局符号,格式为".global <symbol>" |

(4)GNU汇编语法中的函数:

①GNU汇编同样也支持函数,格式如下:

<函数名>:

<函数体>

<返回语句> # 可有可无,"bx"是返回指令

②用汇编写的Cortex-A7中断服务函数:

cpp 复制代码
/* 未定义中断 */
Undefined_Handler:
	ldr r0, =Undefined_Handler
	bx r0

/* SVC中断 */
SVC_Handler:
	ldr r0, =SVC_Handler
	bx r0

/* 预取终止中断 */
PrefAbort_Handler:
	ldr r0, =PrefAbort_Handler
	bx r0

3、Cortex-A常用汇编指令

(1)处理器内部数据传输指令:

①MOV指令用于将数据从一个寄存器拷贝到另外一个寄存器,或者将一个立即数传递到寄存器里面,使用示例如下:

cpp 复制代码
MOV R0,R1 		    @将寄存器R1中的数据传递给R0,即R0=R1
MOV R0, #0X12 		@将立即数0X12传递给R0寄存器,即R0=0X12

②MRS指令用于将特殊寄存器(如CPSR和SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用MRS指令,使用示例如下:

cpp 复制代码
MRS R0, CPSR 	@将特殊寄存器CPSR里面的数据传递给R0,即R0=CPSR

③MSR指令和MRS刚好相反,MSR指令用于将普通寄存器的数据传递给特殊寄存器,也就是写特殊寄存器,写特殊寄存器只能使用MSR指令,使用示例如下:

cpp 复制代码
MSR CPSR, R0 	@将 R0 中的数据复制到CPSR中,即CPSR=R0

(2)存储器访问指令:

①LDR主要用于从存储加载数据到寄存器Rx中,LDR也可以将一个立即数加载到寄存器Rx中,不过加载立即数的时候要使用"=",而不是"#",使用示例如下:

cpp 复制代码
LDR R0, =0X0209C004	 @将寄存器地址0X0209C004加载到R0中,即R0=0X0209C004
LDR R1, [R0] 		 @读取地址0X0209C004中的数据到R1寄存器中

②STR主要用于将数据写入到存储器中,使用示例如下:

cpp 复制代码
LDR R0, =0X0209C004	    @将寄存器地址0X0209C004加载到R0中,即R0=0X0209C004
LDR R1, =0X20000002	    @R1保存要写入到寄存器的值,即R1=0X20000002
STR R1, [R0] 			@将R1中的值写入到R0中所保存的地址中

③LDR和STR都是按照字进行读取和写入的,也就是操作的32位数据,如果要按照字节、半字进行操作,可以在指令后面加上"B"或"H",比如按字节操作的指令就是"LDRB"和"STRB",按半字操作的指令就是"LDRH"和"STRH"。

(3)压栈和出栈指令:

①PUSH指令用于将寄存器列表压入栈中,比如要将R0~R3和R12这5个寄存器压栈,当前的SP指针指向0X80000000,处理器的堆栈是向下增长的,使用的汇编代码如下:

cpp 复制代码
PUSH {R0~R3, R12} 	@将R0~R3和R12压栈

②基于上一步,此时的SP指向0X7FFFFFEC,现在再将LR进行压栈,汇编代码如下:

cpp 复制代码
PUSH {LR} 	@将LR压栈

③POP指令用于栈中的内容弹出到寄存器中,基于上一步,现在再将栈中内容弹出,汇编代码如下:

cpp 复制代码
POP {LR} 			@先恢复LR
POP {R0~R3,R12} 	@再恢复R0~R3,R12

④PUSH和POP的另外一种写法是"STMFD SP!"和"LDMFD SP!"。

(4)跳转指令:

①B指令会将PC寄存器的值设置为跳转目标地址, 一旦执行B指令,ARM处理器就会立即跳转到指定的目标地址(如果跳转范围超过±2KB,可以指定"B.W <label>"使用32位版本的指令跳转)。如果要调用的函数不会再返回到原来的执行处,那就可以用B指令,如下示例:

cpp 复制代码
_start:

	ldr sp,=0X80200000 	@设置栈指针
	b main 				@跳转到main函数

②BL指令相比B指令,在跳转之前会在寄存器LR(R14)中保存当前PC寄存器值,所以可以通过将LR寄存器中的值重新加载到PC中,以继续从跳转之前的代码处运行,这是子程序调用一个基本但常用的手段。

cpp 复制代码
push {r0, r1} 			    @保存r0,r1
cps #0x13 			        @进入SVC模式,允许其它中断再次进去

bl system_irqhandler 	    @加载C语言中断处理函数到r2寄存器中

cps #0x12 			        @进入IRQ模式
pop {r0, r1} 
str r0, [r1, #0X10] 		@中断执行完成,写EOIR

③BX指令类似于B指令,不过它是间接跳转,格式为"BX <Rm>",跳转到存放于Rm中的地址处,并且切换指令集。

④BLX指令结合BX和BL指令的特点,格式为"BLX <Rm>",跳转到存放于Rm中的地址处,并且并将返回地址保存在LR中,切换指令集。

(5)算数运算指令:

(6)逻辑运算指令:

相关推荐
倔强的石头1061 小时前
【Linux指南】进程通信系列(七):System V 信号量 —— 同步互斥的内核原语
linux·运维·服务器
GeW2 小时前
从"补短板"到"强优势"Linux与数据库认证成为产业跃迁人才支点
linux·数据库
Zwawa2 小时前
串口命令如何从字符串长成协议:帧头、长度、校验和与状态机
c语言·单片机·嵌入式
小李不想当小白2 小时前
BKP备份寄存器与RTC实时时钟(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
PellyKoo4 小时前
【Ubuntu】GTX 1080 Ti 显卡占用为0但功率高达60W的排查与解决
linux·运维·ubuntu
Fruiticecake4 小时前
epoll 回显服务器是怎么工作的:从 accept 队列到就绪链表,以及它和 select / poll 的区别
linux
La Pulga4 小时前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
承渊政道5 小时前
Linux网络学习【从NAT与代理服务理解网络转发、探索内网穿透原理、再到认识DNS与ICMP、最后动手部署并测试内网穿透】
linux·网络·学习·内网穿透·dns·nat
重生之小比特5 小时前
【Linux系统】17 库制作与原理(下)
linux·运维·服务器