基于S5PV210的ARM知识点汇总(一)

ARM版本号之间的关系

内核版本号:是指ARM公司将芯片生产出来卖给别人时的版本号
SoC版本号:ARM公司对应内核版本号所推出的版本号(比如说,ARM7之前用的是ARMv3的内核,但是后面升级成ARMv4了,所以这里有一对多的关系)
芯片型号:是半导体公司生产出芯片的型号
内核版本号和SoC版本号是有ARM公司确定的,而SoC的型号是由半导体公司确定
注意ARMv7和ARM7的差别,ARMv7指的是内核版本号,是比较新的东西,而ARM7指的是SoC版本号,是比较老的东西
ARMv4的后期生产出了ARM9
ARMv5是一款过渡的产品,用的不是太多,被一家公司垄断
ARMv7开始对针对不同行业的行业分别生产芯片
ARMv7
cortex-m微控制处理器  单片机
    m0      低功耗
    m0+     
    m3
    m4      添加了浮点运算
    m7      低功耗,主要面对物联网
cortex-a应用级处理器  手机、平板...
cortex-r实时处理器   相应速度快
ARM公司已经推出了64位架构的芯片,譬如A53、A57,主要面向高性能服务器应用
ARM下一步的重点发展方向是cortex-m7,面向物联网终端

SoC和CPU的区别

CPU只是纯粹的运算和控制,不包含内存、外部接口等其他的外部设备
SoC是指包含了CPU和其他外部设备的芯片
ARM对外提供的就是CPU,而其他的外部设备都是空出来的,将接口提供给我们,不同的公司可以通过自己的需求自己添加,总线+CPU就是ARM公司设计的东西

交叉编译

非嵌入式开发模式

在A类机器上编写源代码,在A类机器上编译得到可执行程序,然后将得到的可执行程序在A类机器上运行

嵌入式开发模式

在A类机器上编写源代码,在A类机器上编译得到可执行程序,然后将得到的可执行程序在B类机器上运行

嵌入式开发就是一个交叉编译的过程,在A机上编写、编译,在B机上执行,是编译的机器和执行的机器之间的交叉

为什么要用交叉编译

嵌入式开发的CPU比较简单,硬件比较有限,本身无法搭建开发环境,有的甚至连操作系统都没有(单片机);交叉编译可以用高性能的机器为低性能的机器开发软件(包括裸机软件、系统级和应用级的软件)

交叉编译的特点

  • 必须使用专用的交叉编译工具链
  • 由于可执行程序不能本地运行调试,我们可以通过专用调试器、JTAG调试器、USB下载、串口下载、SD启动、网络共享等方式将可执行程序加载到目标嵌入式设备上运行及调试

总线的概念

CPU通过地址总线寻址,通过数据总线和外部设备交互信息
地址总线的位数决定了CPU的寻址范围,数据总线的位数决定了CPU单次能够交换的信息数量
总线的速度决定过了CPU和外部设备数据交换的速度

为什么在嵌入式编程里边我们推荐使用int类型?

因为32位机器指的是他的CPU数据总线有32根,即为一次数据传输由这32根数据总线同时传输,如果传输64位的数据即传输两次即可传输完毕;但是如果传输8位、16位、32位的数据,他们均需要将数据传输一次才可传输完毕,在效率上是一样的,当传输8位、16位数据时,没有用到的数据总线则为空跑状态

容易弄错的知识点

  • CPU的地址总线和数据总线是可以不同的(51单片机的数据总线为8位,而地址总线为16位),但是一般都相同

  • CPU的位数指的是数据总线的位数

  • 32位地址总线的CPU寻址范围是4G,所以最多能够支持4G的内存

可编程器件的编程原理

可编程器件的特点

  • CPU在固定频率的时钟控制下节奏的运行(单片机在晶振的作用下才可以逐条指令的运行,每一次脉冲执行一个操作)
  • CPU可以通过总线读取外部存储设备中的二进制指令集,然后解码执行
  • 这些可以被CPU解码的二进制指令集是CPU设计时就已经确定的,是由CPU的设计者定义的,本质就是一串01代码,这就是CPU的汇编指令集(每一个CPU都有他自己的汇编指令集,所以说汇编没有可移植性,当适用于这个CPU的代码,可能在另一个CPU上就不能运行)

编程及运行的过程

程序员用C语言等高级语言进行编程,通过编译器将高级语言编译成汇编程序,汇编程序经过汇编器汇编成二进制的可执行文件,二进制文件被CPU读取进去,CPU内部电路对二进制文件进行解码,解码通过则CPU执行指令,完成指令。

汇编语言的本质

汇编实质是机器指令的助记符,是一种低级的符号语言。机器指令集是一款CPU的编程特征,是这款CPU的设计者制定的;CPU的内部电路设计就是为了实现这些指令集的功能;机器指令集就好像CPU的API接口一样

不同的CPU的机器指令集设计不同,因此汇编程序不能再不同的CPU间互相移植;使用汇编编程可以充分发挥CPU的设计特点(比如说这款CPU的左移运算时非常强的,我们就可以用左移来实现乘2的操作),所以汇编的效率最高,因此在操作系统内核中效率及其重要的地方都需要用汇编处理

RISC和CISC的区别

CISC(复杂指令集)

CISC体系的理念就是用最少的指令来完成任务,他将所有可能用到的运算都考虑进去,企图用最少的指令来完成相同的任务;因此CISC的CPU本身设计就非常复杂,工艺非常复杂,功耗非常高,但相对RISC来说,编译器就非常好设计,只要将相应的指令对应翻译就好了。

RISC(精简指令集)

RISC的设计理念就是让软件来完成具体的任务,CPU本身只提供基本的功能指令集。这种设计相对于CISC来说,CPU的设计、工艺都相对简单,功耗也相对较低,但是编译器的设计就变得很难了。

统一编址和独立编址

内存是程序运行的场所,内存和CPU之间通过总线进行连接,CPU通过一定的地址来访问具体内存单元。IO指的是输入输出接口,即CPU和其他外部设备之间通信的道路,一般的,IO就是指CPU的各种内部或外部外设。CPU访问外设就是访问外设中的寄存器,通过寄存器进行数据的传输。

内存的访问方式

内存通过CPU的地址总线来寻址,通过CPU的数据总线来读写。CPU的地址总线的位数是确定的,即一款CPU的寻址范围是确定的,而内存需要占用CPU的寻址空间(32位的CPU所能读取到的内存的大小根本不到4G,大概只有1.5G左右,其他的地址总线用作访问外设寄存器的地址或做其他的用途了)。内存和CPU的这种总线的连接方式是一种直接连接,优点是访问效率高,缺点是资源有限,扩展性差。

IO的访问方式

CPU访问各种外设有两种方式:
  • 类似于访问内存的方式,即把外设的寄存器当做一个内存地址来读写,从而以访问内存相同的方式来操作外设,这叫做IO与内存的统一编址方式,这种编址方式的优点就是IO当做内存访问,编程比较简单,而缺点是IO也需要占用一定的CPU空间,而CPU的地址空间是有限的资源;
  • 使用专用的CPU指令来访问某种特定的外设(比如说串口,串口没有分配专门的地址,这就需要专门的读串口指令和写串口指令来进行操作),这叫做IO与内存的独立编址方式,这种编址方式的优点是不占用CPU的地址空间,缺点是CPU的设计变复杂了;

SoC在访问外设的时候,通过增加CPU指令来对外设直接进行操作,采用的独立编址的方式,这就形成了CISC体制;ARM采用的是IO与内存统一编址的方式,是以个典型的RISC体制的CPU;

哈佛结构和冯诺依曼结构

冯诺依曼结构:程序和数据都放在内存中,且不彼此分离的结构为冯诺依曼结构(Intel的CPU都是采用冯诺依曼结构的)。冯诺依曼结构由于程序和数据都放在一起,我们不能保证程序在运行过程中不被修改,因此它的安全性和稳定性就成了一个疑问,但冯诺依曼结构的好处就是数据处理比较方便。
哈佛结构:程序和数据放在不同的内存块中,彼此完全分离的结构称为哈佛结构(大部分的单片机均采用哈佛结构)。哈佛结构中的程序一般放在ROM、Flash中的,而数据一般是存放在RAM中的,他们单独存储和处理,提高了程序的一个安全性,但是这样数据处理起来也会相对较复杂(需要统一规划链接地址)。
在嵌入式系统(S5PV210)中,它的程序和数据都是存放在内存中的,与冯诺依曼结构不同的是,他将内存分为了两个部分,一部分当做ROM,一部分当做RAM,操作系统就会对ROM这部分内存做限制,只允许他进行读取操作,对RAM就可读可写。

寄存器

寄存器的概念

寄存器是CPU外设的硬件组成部分,CPU可以像访问内存一样访问寄存器,寄存器是留作外设被编程控制的“活动开关”,正如汇编指令集是CPU的编程接口API一样,寄存器是外设硬件的软件编程结构API。使用软件编程控制某一硬件,其实就是编程读写该硬件的寄存器。

通用寄存器

通用寄存器(ARM中有37个),是CPU的组成部分,这些寄存器的功能没有事先定义好,都是可读可写的,我们可以人为的定义它的功能。

SFR(特殊功能寄存器)

SFR不在CPU中,而存在于CPU的外设中,我们通过访问外设的SFR来操作这个外设,这就是硬件编程控制的方法。因为对应位的含义都是事先定义好的,专门为这个功能所制定的,具有特殊功能的,所以这类寄存器就叫做特殊功能寄存器。

CPU和外部存储器的接口

CPU连接内存和外存的连接方式不同,内存直接通过地址访问,所以是通过地址总线和数据总线的总线式访问方式连接的(好处是直接访问、随机访问;坏处是占用CPU地址空间);外存是通过CPU的外存接口来连接的(好处是不占用CPU的地址空间,坏处是访问速度没有总线快,访问时序比较复杂)

NorFlash

NorFlash采用的是总线式访问,所以CPU可以直接对他进行地址访问,一般接到SROM bank,用来启动。(但是NorFlash的价格比较贵,我们一般用NorFlash启动,用NandFlash当作硬盘);

NandFlash

NandFlash采用的是时序访问的方式,它分为SLC和MLC,SLC的容量很小,价格很贵,但是他的稳定性很好;MLC他的容量可以很大,价格可以很低,但是他很容易坏块,如果不做ECC校验很容易出现问题;

SD卡/TF卡/MMC卡

在SD卡中,本身就有一些电路用来检测卡中的坏块,可以自己屏蔽这些坏块,这样就减轻了CPU本身的负担(这也是同NandFlash的区别);

eMMC/iNand/moviNand

eMMC是一种嵌入式的MMC,他是以一种芯片的形式存在在设备中的,他就相当于在设备中接了一个SD卡;iNand是SanDisk公司推出的eMMC,moviNand是三星公司推出的eMMC;

eSSD

eSSD是SSD的一种嵌入式版本,它本身也是MLC,自身具备一些电路对他的坏块进行校验;

机械硬盘

机械硬盘采用的是机械式访问,磁存储原理;

X210有2个版本,Nand版和iNand版,分别使用Nandflash和iNand为外部存储器。我们使用的是iNand版本,板载4GB iNand。S5PV210共支持4个SD/MMC通道,其中通道0和2依次用作启动。X210开发板中SD/MMC0通道用于连接板载MMC,因此外部启动时只能使用SD/MMC2通道(注意通道3不能启动)。

S5PV210的启动过程

在我们的启动过程中,涉及到内存和外存两种设备:内存和外存;

内存:

SRAM(静态内存):容量小、价格高,不需要软件初始化,直接上电就可以用;

DRAM(动态内存):容量大、价格低,上电后不能直接使用,需要软件初始化;

在单片机中,内存需求量比较小,而且希望开发尽量简单,适合全部使用SRAM;在PC机中,软件复杂,不在乎DRAM的初始化开销,适合全部用DRAM;嵌入式系统,内存需求量大,且没有NorFlash等启动介质,所以我们一般都是用NorFlash当做启动介质,然后用NandFlash当做硬盘来进行处理;

外存:

NorFlash:容量小、价格高,可以和CPU直接总线式相连,CPU上电后可以直接读取,一般作为启动介质

NandFlash(跟硬盘一样):容量大、价格低,缺点是不能总线式访问,也就是说不能上电后直接读取,需要CPU先运行一些初始化软件,然后通过时序接口来读写;

不同机器的内存配置

一般PC机是用很小的BIOS(NorFlash) + 很大容量的硬盘(类似于NandFlash) + 大容量的DRAM

一般单片机采用的是很小容量的NorFlash + 很小容量的SRAM

嵌入式系统:因为NorFlash很贵,所以现在很多嵌入式系统倾向于不是用NorFlash,直接外接大容量的Nand + 外接大容量的DRAM + SoC内置SRAM;在SoC内置的SRAM中,CPU可以知道用于启动的代码在哪个位置,可以顺利的引导启动;

S5PV210使用的启动方式是:210内置了一块96KB大小的SRAM(iRAM),同时还有一块内置的64KB大小的NorFlash(iROM)。
第一步:CPU上电后先从内部iROM中读取预先设置的代码(BL0),执行;这一段IROM做了一些基本的初始化(CPU时钟、看门狗等),这一段iROM代码是三星出厂前设置的,三星也不知道我们的板子上将来接的是什么样的DRAM,因此这一段iRAM是不能负责初始化外接的DRAM的,这一段代码只能初始化SoC内部的一些东西;然后这一段会判断我们的启动模式(我们可以通过板载开关来更改启动模式),然后从相应的外部存储器去读取第一部分启动代码(BL1,大小为16K)到内部的SRAM;第二步:从iRAM中去运行读取来的BL1,然后执行。BL1负责初始化NandFlash,然后将BL2读取到iRAM(剩余的80KB),然后运行;第三部:从iRAM运行BL2,BL2初始化DRAM,然后将OS读取到DRAM中,然后启动OS,启动过程结束。

ARM的编程模式和7种工作模式

ARM的基本设定

  • ARM约定:

    Byte:       8 bits
    Halfword:   16 bits(2 Byte)
    Word:       32 bits(4 Byte)
    
  • 大部分ARM core提供:(指令集可以体现CPU的本体特色,以下的三种指令集皆可以对CPU进行操作,指令集就相当于符号域,每一个指令对应一个区域,这块区域就存储了指令的机器代码)

    ARM指令集(32 bit)
        相对效率较高,但是占用存储空间较多,有些指令可能只需要16bit就可以完成
    Thumb指令集(16 bit)
        最先出现的指令集,每一个指令占16 bit,但是这本身并不完全,有很多操作不好处理,比如说异常处理,他必须要用32bit才能完成,虽然可以用两个
        16bit来完成,但是这样指令周期增加,伴随着的是响应是32bit的两倍,这在有些领域就不是很理想
    Thumb2指令集(16 & 32 bit)
        Thumb2指令集是上面两种的结合
    
  • Jazelle core支持Java bytecode

    对Java做了一些优化,可以支持Java快速运行

ARM 有7个基本工作模式:(有一种基本模式(user模式)和6种特权模式,6种特权模式中又有一种系统模式(system模式)和5种异常模式)

User:非特权模式,大部分任务执行在这种模式,用户进程就是运行在这种模式下的

FIQ(快速中断):当一个高优先级(fast) 中断产生时将会进入这种模式
IRQ(普通中断):当一个低优先级(normal) 中断产生时将会进入这种模式
Supervisor:当复位或软中断指令执行时将会进入这种模式,整个uboot就是运行在这种模式下的,引导系统内核
Abort:当存取异常时将会进入这种模式
Undef:当执行未定义指令时会进入这种模式

System:使用和User模式相同寄存器集的特权模式,root

我们可以在各种模式下切换,可以是程序员通过代码主动切换(通过写CPSR寄存器);也可以是CPU在某些情况下自动切换(比如说机器复位了,就会进入特定的模式)。各种模式下权限和可以访问的寄存器是不同的。

CPU为什么设计这些模式?

这是由OS所决定的,CPU是硬件,OS是软件,软件的设计要依赖硬件的特性,硬件的设计要考虑软件需要,便于实现软件特性。操作系统有安全级别要求,因此CPU设计多种模式是为了方便操作系统的多种角色安全等级需要。

ARM的37个通用寄存器详解

ARM的37个通用寄存器

这里看到的就是ARM内核里面的所有寄存器了,一共有37个。ARM体系一共有7种模式,其中用户模式和系统模式拥有物理空间上完全相同的寄存器,而其它5种异常模式都有一些自己独立的寄存器。我们看图中左边显示的是当前模式的可见的寄存器,右边显示的是其它模式备用的寄存器。其中深蓝色的部分为当前模式与用户模式共用的寄存器,而彩色部分为各个寄存器独立物理空间的寄存器。这样安排的好处是当各种异常发生的时候,每种异常模式都可以保存一些重要的数据,使异常处理程序完成之后返回异常前的程序时不会破坏原有的寄存器或状态。

sp寄存器是用作堆栈指针的

lr寄存器是用来存储的返回地址的

pc寄存器是用来记录程序当前运行到什么位置的

cpsr(程序状态寄存器),用来记录CPU的运行状态信息,这些信息非常重要,和后面学到的汇编指令息息相关(譬如BLE指令中的E就和CPSR中的Z标志位有关)

spsr用来保存cpsr的,即为当我们到其他的模式下时,我们要有一块地方来保存上一个模式的cpsr,当我们返回回去时,可以从spsr来复原上一个cpsr

ARM共有37个寄存器,都是32位长度,37个寄存器中30个为“通用”型,1个固定用作PC,一个固定用作CPSR,5个固定用作5种异常模式下的SPSR。

CPSR程序状态寄存器详解

CPSR程序状态寄存器一共有32bit,其中0~4bit是用来设置处理器的运行模式的,我们一般在uboot代码中会使用汇编进行设置;T位(5bit)是用来设置CPU是处于什么样的状态,使用的是怎样的指令集;6~7bit是用来禁止中断的;24bit or 27bit我们一般用不到;28bit~31bit作为CPU的条件位,N = Negative result from ALU(当计算为负数时,这一位为1),Z = Zero result from ALU(当计算为零时,这一位为1),C = ALU operation Carried out(当计算有进位时,这一位为1),V = ALU operation oVerflowed(当计算溢出时,这一位为1)

这些位跟ARM的指令是有关系的,ARM的指令是条件执行的,所有的指令都可以添加条件后缀,当条件发生的时候才会执行相应的指令

PC(Program control register)为程序指针,PC指向哪里,CPU就会执行哪条指令(所以程序跳转时就是把目标地址代码放到PC中)
整个CPU中只有一个PC(CPSR也只有一个,但SPSR有5个)。

ARM的异常处理方式

什么是异常:

正常工作之外的流程都叫做异常,中断也是一种异常,异常会打断正在执行的工作,并且一般我们会对其现场保护,希望异常处理之后继续回来执行原来的工作

异常向量表

所有的CPU都有异常向量表,这是CPU设计时就设定好的,是硬件决定的。当异常发生时,CPU会自动动作(PC跳转到异常向量处处理异常,有时伴有一些辅助动作;比如说,当鼠标单击了,CPU会自动响应鼠标单击这个事件,但是CPU不知道鼠标单击到底意味着什么,即不知道鼠标单击之后要做什么事情)。异常向量表是硬件向软件提供的处理异常的支持。

硬件已经定义好了当什么异常发生时,CPU自动运行异常向量表中所定义的跳转指令,转向这个异常所要异常处理的代码;异常向量表中每个异常所占四个字节,用于存放异常处理函数;

异常处理流程:

当异常产生时, ARM core:
拷贝 CPSR 到 SPSR_<mode>
设置适当的 CPSR 位: 
    改变处理器状态进入 ARM 态
    改变处理器模式进入相应的异常模式
    设置中断禁止位禁止相应中断 (如果需要)
保存返回地址到 LR_<mode>
设置 PC 为相应的异常向量

返回时, 异常处理需要:
从 SPSR_<mode>恢复CPSR
从LR_<mode>恢复PC 

PS:这些操作只能在 ARM 态执行。

ARM汇编指令集

汇编指令是CPU机器指令的助记符,经过编译后会得到一串01组成的机器码,可以由CPU读取执行。
汇编伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,相当于一个工具,经过编译后伪指令最终不会生成机器码。

两种不同风格的ARM指令

ARM官方的ARM汇编风格:指令一般用大写、Windows中IDE开发环境(如ADS、MDK等)常用。如: LDR R0, [R1]

GNU风格的ARM汇编:指令一般用小写字母、linux中常用。如:ldr r0, [r1]

这两种不同的风格在指令部分差别不是很大,但是在伪指令的部分会有很大的差别,因为指令部分最后是给CPU看的,所以我们的标准是差不多的,但是伪指令只是引导编译过程的,是给编译器看的,所以伪指令的差别会很大。

ARM汇编的特点:

LDR/STR架构

ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。

ldr(load register)指令将内存内容加载入通用寄存器。

str(store register)指令将寄存器内容存入内存空间中。

ldr/str组合用来实现 ARM CPU和内存数据交换
8种寻址方式
寄存器寻址       mov r1, r2(将r2的值赋给r1)

立即寻址            mov r0, #0xFF00(将#0xFF00这个地址里的值赋给r0)

寄存器移位寻址 mov r0, r1, lsl #3(将r1中的值左移3位后赋给r0)

寄存器间接寻址 ldr r1, [r2]([r2]就相当于内存中的r2这个地址,将内存中r2的值赋给r1)

基址变址寻址      ldr r1, [r2, #4](将r2这个地址加上4然后赋给r1)

多寄存器寻址      ldmia r1!, {r2-r7, r12}(依次将内存中r2~r7和r12地址中的内容,依次填入由r1开始的寄存器,因为是32位,所以每次递加的地址应该是4Byte)

堆栈寻址            stmfd sp!, {r2-r7, lr}(依次将内存中r2~r7和lr地址中的内容,依次填入栈中)

相对寻址                beq flag(标号)
                    flag:
标号用于保存函数的入口点
指令后缀

同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:

B(byte)功能不变,操作长度变为8位

H(half word)功能不变,长度变为16位

S(signed)功能不变,操作数变为有符号
如 ldr ldrb ldrh ldrsb ldrsh

S(S标志)功能不变,影响CPSR标志位(有些操作一般不会影响CPSR标志位,如果你想要改变标志位,可以在命令后面加上S,即可改变标志位)

如 mov和movs movs r0, #0

条件执行后缀

条件执行后缀

条件后缀执行特点:

  1. 条件后缀是否成立,不是取决于本句代码,而是取决于这句代码之前的代码运行后的结果;

  2. 条件后缀决定了本句代码是否被执行,而不会影响上一句和下一句代码是否被执行;

多级指令流水线

为了增加处理器指令流的速度,ARM使用多级流水线,下图为3级流水线的工作原理图。(S5PV210使用13级流水线,ARM11使用8级流水线)

条件执行后缀

多级指令流水线的特点:
  • 允许多个操作同时处理,而非顺序执行
  • 当流水线中的某一级发生问题(比如说一共有13级,在第10级的时候发生了跳转),那么这一级之前所有的操作都需要从第一级开始重新操作
  • PC指向正被取指的指令,而非正在执行的指令(要是想执行正在被执行的指令,那么就要往前递减)

数据处理指令

数据传输指令  mov mvn
    mov r0,r1       //mov之间的对象一定是两个寄存器,不可以是两个内存地址,或者一个内存地址一个寄存器
    mov r0,#0x0     //这种立即数赋值的方式方式也是可以的
    mvn和mov的用法相同,区别是mov是原封不动的传递,而mvn是将值按位取反后传递

算术指令        add sub rsb adc sbc rsc
    add r0,r1,r2
    add r0,r1,#0x1
    add r0,r1,r2,lsl#0x1    //将r2左移1位后,与r1相加后赋值给r0

    adc(带进位加法):就是说两个数相加,如果没有进位,那么与add的结果相同,如果有进位,那么结果还要加上进位的1
    adc r0,0x0f,0x0f    //r0 = 0x0f + 0x0f + 1(因为有进位,所以要加上1)

    sub和add的用法类似

    rsb(反向减法)
    rsb r0,r1,r2    //r0 = r2 - r1
    rsb r0,r0,0xff  //r0 = 0xff - r0,即将一个数减去本身

    sbc(带借位减法):和adc类似,如果需要借位,那么原结果还要再减去1

    rsc(带借位的反向减法)

逻辑指令        and orr eor bic
    bic(位清除指令):将某几位清零
    bic r0,r1,0x1f  //将r1中的bit0-bit4清零后赋值给r0

比较指令        cmp cmn tst teq
    cmp(比较操作):相减操作
    cmp r0,r1   //将r0和r1进行比较

    cmn(比较取负的值):相加操作
    cmn R0,#1  //将R0与-1进行比较

    teq(测试等价):相异或操作

    tst(测试位)相与操作
    tst r0,#0x01    //测试Bit0是否为0

    ps:这些操作均不保存操作的结果,只是影响状态寄存器CPSR的状态位

乘法指令        mvl mla umull umlal smull smlal

前导零计数   clz
clz的作用是:统计一个数前面到底有几个0

cpsr访问指令

cpsr和spsr之间的联系和区别:cpsr是程序状态寄存器,整个SoC中只有一个,而spsr有5个,分别在五种异常模式下,作用是当从普通模式进入异常模式时,用来保存普通模式的cpsr,以此在返回普通模式时恢复原来的cpsr

mrs和msr分别用来读和写cpsr或者spsr
mrs r0,cpsr
msr cpsr,r0

跳转(分支)指令

b:直接跳转(出去了就没打算回来,类似于C语言的goto指令,用于绝对跳转)

bl:跳转前把返回地址放入lr中,以便返回,以便函数调用bl 函数名

bx:跳转同时切换ARM模式,一般用于异常处理的跳转(这个我们一般用不到了,因为我们基本上是在纯ARM环境里面开发的,不存在什么跳转到ARM模式)

访存指令

ldr/str(单字/半字/字节访问):如果直接ldr就是以一个word为单位,加上h就是hardword为单位,加上b就是以byte为单位

ldm/stm(多字批量访问)

swp(将寄存器和内存交换内容)
swp r1,r2,[r0]      //将内存中r0地址的值赋给r1,再将r2的值赋给内存中的r0这个地址

立即数的合法性

ARM指令都是32位的,除了指令标记和操作标记外,本身只能附带很少位数的立即数,所以经过任意位数移位之后,非零部分可以用8位表示的即为合法立即数

swi软中断指令

软中断指令用来实现操作系统中的系统调用

协处理器和协处理器指令

mrc用于读取CP15中的寄存器
mcr用于写入CP15中的寄存器

协处理器是SoC内部的另一处理核心,也具备一定的运算和控制功能,它在处理一些东西时不需要主CPU参与,主要协助主CPU实现一些功能,被主CPU调用执行任务,我们一般通过读/写CP15中的寄存器来对协处理器发出指令;ARM在设计上支持16个协处理器,但是一般SoC只实现其中的CP15(CP就是协处理器的意思);

协处理器和MMU、cache、TLB等处理有关,功能上和操作系统的虚拟地址映射、cache管理有关;

mcr{<cond>} p15, <opcode_1>, <Rd>, <Crn>, <Crm>, {<opcode_2>}
opcode_1:对于cp15永远为0
Rd:ARM的普通寄存器,不能是r15
Crn:cp15的寄存器,合法值是c0~c15
Crm:cp15的寄存器,一般均设为c0
opcode_2:一般省略或为0

mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1
mcr p15, 0, r0, c1, c0, 0

ldm/stm(多寄存器访问)与栈的处理

为什么需要多寄存器访问指令?

ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ldm,若干个寄存器同时进行操作,可以大幅度提升读写效率;

stmia   sp, {r0 - r12}  //ia是后缀

将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址······直到r12内容放入(0x3001030),指令完成。一个访存周期同时完成13个寄存器的读写

ia(increase after)先传输,再地址+4
ib(increase before)先地址+4,再传输
da(decrease after)先传输,再地址-4
db(decrease before)先地址-4,再传输
fd(full decrease)满递减堆栈
ed(empty decrease)空递减堆栈
fa(·······) 满递增堆栈
ea(·······)空递增堆栈

空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出

满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针

增栈:栈指针移动时向地址增加的方向移动的栈

减栈:栈指针移动时向地址减小的方向移动的栈

我们使用最多的是stmia和stmfd,ARM默认使用的是满减栈,我们在操作栈的时候,进栈、出栈使用相同的后缀就不会出错;

!的作用
ldmia   r0, {r2 - r3}
ldmia   r0!, {r2 - r3}

感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说如果你最后想改变r0里面的值,就加上!,如果你想保持原先的值,就不要加!;

^的作用
ldmfd   sp!, {r0 - r6, pc}
ldmfd   sp!, {r0 - r6, pc}^

^的作用就是,在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。

ARM汇编伪指令

伪指令和汇编指令的区别是:伪指令在编译后不会生成机器码,伪指令的意义在于指导编译过程;伪指令和具体的编译器是相关的,所以我们使用的是什么工具链,就用这个工具链环境下的汇编伪指令就好了;

C语言中的基本语法在汇编里面基本都是可以通用的;

GNU汇编中的符号:
@ 用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//类似
# 做注释,一般放在行首,表示这一行都是注释而不是代码。
:以冒号结尾的是标号,用于标记命令的地址,跟变量的含义差不多
. 点号在GNU汇编中表示当前指令的地址,一般用来写死循环:b .(b是跳转指令),跳转到当前指令就是死循环
# 立即数前面要加#或$,表示这是个立即数

最重要的几个伪指令:
ldr     大范围的地址加载指令(ARM中有一个ldr指令,还有一个ldr伪指令,一般都使用ldr伪指令而不用ldr指令,因为我们在使用ldr指令的时候,要考虑立即数的合法问题,伪指令就不用,他会自己进行处理,就算不是合法的,在编译之后就会转换成几条合法的指令)
adr 小范围的地址加载指令
adrl    中等范围的地址加载指令
nop 空操作

adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理;

adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址在哪里;

ldr加载的地址和链接时给定的地址有关,由链接脚本决定;

adr和ldr的差别:ldr加载的地址在链接时确定,而adr加载的地址在运行时确定;所以我们可以通过adr和ldr加载的地址比较来判断当前程序是否在链接时指定的地址运行。

常用的伪指令:
.global _start      @ 给_start声明为外部链接属性,这样就可以在别的文件去访问它,类似于全局变量
.section .text      @ 指定当前段为代码段
.ascii .byte .short .long .word @ 相当于定义变量的数据类型
.quad(双字) .float .string @ 定义数据
.align 4            @ 以16(2^4)字节对齐
.balignl 16 0xabcdefgh  @ 16字节对齐填充(b表示位填充;l表示long,以4字节为单位填充;16表示16字节对齐;0xabcdefgh是用来填充的原料)
.equ                @ 类似于C中宏定义,我们一般用不到,我们定义宏一般就用C语言中的# defind就可以了

偶尔会用到的伪指令:
.end                @标识文件结束
.include            @ 头文件包含
.arm / .code32      @声明以下为arm指令
.thumb / .code16    @声明以下为thubm指令
  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值