Bootstrap

嵌入式第一部分-第二集:ARM体系结构与汇编指令

1,可编程器件的编程原理

电子器件的发展方向:

模拟器件->数字器件

ASIC->可编程器件

ASIC,专用芯片、程序已经确定,不可更改,出厂时功能已经确定。

可编程器件的特点:

CPU在固定频率的时钟控制下节奏运行;

CPU可以通过总线读取外部存储设备种的二进制指令集,然后解码执行;

这些可以被CPU解码执行的二进制指令集是CPU设计的时候确定的,是CPU的设计者(ARM)定义的,本质上是一串由1和0组成的数字。这就是CPU的汇编指令集。

程序员用汇编指令编程->经编译器汇编程二进制可执行程序文件->二进制文件被CPU读取进去->CPU内部电路对二进制文件解码->解码通过则CPU执行指令、完成指令动作。

如果程序员用C语言等高级语言编程,则编译器先将C语言程序编译为汇编程序,再进行上面的后续部分。

2,指令集对CPU的意义 

汇编语言的本质:

机器指令(机器码)的助记符,是一种机器符号语言;

机器指令集是一款CPU的编程特征,是这款CPU的设计者制定的。CPU的内部电路设计就是为了实现这些指令集的功能。

理解:汇编指令和机器指令是一 一对应的,类似于一个人的名字和他的身份证号,记下一个人的名字比记下身份证号更容易。

不同CPU的机器指令集设计不同,因此汇编程序不能在不同CPU间相互移植。

使用汇编编程可以充分发挥CPU的设计特点,所以汇编编程效率最高,因此在操作系统内核中效率极其重要处都需要用汇编处理。 

3,RISC和CISC的区别 

 CISC:complex instruction set computer

CISC体系的设计理念是用最少的指令完成任务,因此CISC的CPU本身设计复杂、工艺复杂,但好处是编译器好设计。CISC出现较早,至今Intel还一直采用CISC设计。

理解:用最少的指令完成任务,举例:CISC中有乘加指令,对应于C中的 d = a*b+c,有一个这样的指令CPU中就需要对应的电路来完成指令,因此CISC的CPU本身设计复杂、工艺复杂。

CPU中有多少功能的电路,就有多少对应的指令。因为CPU中电路较多,功耗就较高。

RISC:Reduced Instruction-Set Computer 精简指令集CPU;

RISC的设计理念是让软件来完成具体的任务,CPU本身仅提供基本功能指令集。因此RISC CPU指令集中只有很少的指令,这种设计相对于CISC CPU的设计和工艺简单了,但是编译器的设计变难了。

理解:ARM公司之所以使用RISC,是因为当时ARM公司很小,资源有限,无法设计使用CISC的CPU。

CISC指令集复杂,汇编编程相对简单,CPU设计和工艺复杂;RISC指令集简单,汇编编程相对复杂,CPU设计和工艺相对简单。

RISC与CISC指令数对比:一般典型CISC CPU指令在300条左右,ARM CPU常用指令30条左右。

发展趋势:没有纯粹的RISC或CISC,发展方向是RISC和CISC结合,形成一种介于两者之间的CPU类型。

4,统一编址、独立编址 

CPU访问各种外设有两种方式:一种是类似于访问内存的方式,即把外设的寄存器当作一个地址来读写,从而以访问内存的相同的方式来操作外设,叫IO与内存统一编址方式;另一种是使用专用的CPU指令来访问某种特定外设,叫IO与内存独立编址。

IO与内存统一编址方式,优势是IO当作内存来访问,编程简单;缺点是IO也需要占用一定的CPU地址空间,而CPU的地址空间是有限资源。

IO与内存独立编址方式,优势是不占用CPU地址空间,缺点是CPU设计复杂。

5,冯诺依曼结构和哈佛结构

程序运行时两大核心元素: 程序和数据;

程序是我们写好的源代码经过编译、汇编之后得到的机器码,这些机器码可以拿给CPU去解码执行,CPU不会也不应该去修改程序,所以程序是只读的;

数据是程序运行过程中定义和产生的变量的值,是可以读写的,程序运行实际就是为了改变数据的值。

冯诺依曼结构:程序和数据都放在内存中,且不彼此分离的结构,例如Intel的CPU;

哈佛结构:程序和数据分开独立放在不同的内存块中,彼此完全分离的结构,例如大部分的单片机。

优劣势对比:冯诺依曼结构中程序和数据不区分的放在一起,因此安全和稳定性是个问题,好处是处理起来简单;哈佛结构中程序(一般存放在ROM/Flash中)和数据(一版存放在Ram中)独立分开存放,因此好处是安全和稳定性高,缺点是软件处理复杂一些(需要统一规划链接地址等)。

6,寄存器

软件编程控制硬件的关键。

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

SoC有两类寄存器:通用寄存器和SFR;

通用寄存器(ARM中有37个)是CPU的组成部分,CPU的很多活动都需要通用寄存器的支持和参与;

SFR(Special Function Register,特殊功能寄存器) 不在CPU中,而存在于CPU的外设中,我们通过访问外设的SFR来编程操控这个外设,这就是硬件编程控制方法。

理解:寄存器是硬件,虽然看不到摸不着,但是实际存在的东西。

7,7-11章节暂未看 

学习完完善。

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

ARM采用的是32位架构;

ARM约定:

Byte: 8bits

Halfword: 16bits (2 byte)

Word: 32 bits (4 byte) 

大部分ARM core提供:ARM指令集(32 bit) /Thumb指令集(16 bit)/Thumb2指令集(16 bit/32 bit);

理解:Thumb2指令集是ARM指令集和Thumb指令集的综合,16bit和32bit指令各有优势;

ARM处理器工作模式,ARM有7个基本工作模式:

User:非特权模式,大部分任务执行在这种模式;

FIQ: 当一个高优先级(fast)中断产生时将会进入这种模式;

IRQ: 当一个低优先级(normal)中断产生时将会进入这种模式;

Supervisor: 当复位或软中断指令执行时将会进入这种模式;

Abort:当存取异常时将会进入这种模式;

Undef: 当执行未定义指令时会进入这种模式;

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

除User是普通模式外,其他6种都是特权模式,特权模式种除System模式外,其余5种为异常模式,各种模式的切换,可以程序员通过代码主动切换(通过写CPSR寄存器),也可以是CPU在某些情况下自动切换,各种模式下权限和可以访问的寄存器不同。

操作系统有安全级别要求,CPU设计多种模式是为了方便操作系统的多种角色安全等级需要。

13,ARM的37个寄存器 

ARM总共有37个寄存器,但是每种工作模式下最多只能看到18个寄存器,其他寄存器虽然名字相同但是在当前模式不可见。

对r14来说,在ARM种共有6个叫r14的寄存器,但是在每种特定处理器模式下,只有一个r14是当前可见的,其他的r14必须切换到对应模式下才能看到,这种设计叫影子寄存器(banked register)。

上图种黑色表示不同模式下相同的寄存器,非黑色寄存器是对应模式特有的。

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

理解:上边“通用”的意思是指sp和lr除r13和r14外,也可用其他寄存器,而PC/CPSR/SPSR只能使用指定的寄存器。

CPSR种各bit位表明了CPU的某些状态信息,这些信息非常重要,和后面学到的汇编指令息息相关;

CPSR中的I、F位和开中断、关中断有关;

CPSR的mode位(bit4~bit0共5位) 决定了CPU的工作模式,在uboot代码中会使用汇编进行设置。

PC(Program control register)为程序指针,PC指向哪里,CPU就会执行哪条指令(所以程序跳转时就是把目标地址代码放到PC中)。 

14,ARM的异常处理方式 

异常的概念:

正常工作之外的流程都叫异常,异常会打断正在执行的工作,并且一版我们希望异常处理完成后继续回来执行原来的工作,中断是异常的一种;

异常向量表:

所有的CPU都有异常向量表,这是CPU设计时就设定好的,是硬件决定的;当异常发生时,CPU会自动动作(PC跳转到异常向量处处理异常,有时伴有一些辅助动作);异常向量表是硬件向软件提供的处理异常的支持。

当异常产生时,ARM core的处理流程:

->拷贝CPSR到SPSR_<mode>;

->设置适当的CPSR位:

改变处理器状态进入ARM态(有可能原来是Thumb态);

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

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

->保存返回地址到LR_<mode>;

->设置PC为相应的异常向量;

异常处理完毕返回时,流程如下:

->从SPSR_<mode>恢复CPSR;

->从LR_<mode>恢复PC;

注意:以上这些操作只能在ARM态执行。

异常处理中有一些是硬件自动做的,有一些是程序员需要自己做的。需要搞清楚哪些是需要自己做的,才知道如何写代码;CPU设计时提供的异常向量表,一般称为一级向量表。有些CPU为了支持多个中断,还会提供二级中断向量表,处理思路类似于这里说的一级中断向量表。

理解:一级向量表和二级向量表非标准称谓,例如进入IRQ异常向量后还要跳转到具体的中断,就需要二级向量表。

15,ARM汇编指令集

15.1,指令和伪指令

指令和伪指令:

(汇编)指令是CPU机器指令的助记符,经过编译后会得到一串1和0组成的机器码,可以由CPU读取执行;

(汇编)伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码;

两种不同风格的ARM指令:

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

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

15.2,ARM汇编特点                  

 特点1,LDR/STR架构:

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

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

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

ldr/str组合用来实现ARM CPU和内存数据交换;

特点2,八种寻址方式:

寄存器寻址:mov r1,r2    (将r2寄存器中的内容放到寄存器r2中)

立即寻址:mov r0, #0xFF00  (将立即数0xFF00放到寄存器r0中,数字前加#)

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

寄存器间接寻址: ldr r1,[r2]   (将地址为r2的内存单元数据读取到r1中)

基址变址寻址:ldr r1 [r2,#4]   (将地址为r2+4的内存单元数据读取到r1中)

多寄存器寻址:ldmia r1!, {r2-r7,r12}  (将地址为r2-r7,r12的内存单元数据读取到r1中)

堆栈寻址:stmfd sp!, {r2-r7,lr}  (嵌入式STMFD SP!,{R0-R7,LR}分析) 

相对寻址:beq flag      跳转指令

特点3:指令后缀

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

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

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

S(signed)功能不变,操作数变为有符号;

如:ldr/ldrb/ldrh/ldrsb/ldrsh 

S(S标志)功能不变,影响CPSR标志位,如mov/movs

注意:上边的两个后缀S是不一样的,对于下边的S,movs r0,#0(将0放入r0中),mov后不加s,则CPSR中的Z位不置1,movs指令会使Z位置1。

特点4:条件执行后缀

 

特点5,多级指令流水线

15.3,常用ARM指令 

数据传输指令:mov mvn

算术指令:add sub rsb adc sbc rsc

逻辑指令:and orr eor bic

比较指令:cmp cmn tst teq

乘法指令:mvl mla umull umlal smull smlal

前导零计数:clz

CPSR访问指令:mrs和msr

mrs用来读psr,msr用来写;

CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr;

注意:对于上文psr的解释看下图

注意:@代表注释掉本句代码。

跳转(分支)指令:b bl bx

b:直接跳转(没打算返回);

bl:branch and link,跳转前把返回地址放入lr中,以便返回,常用于函数调用;

bx:跳转同时切换到ARM模式,一版用于异常处理的跳转。

访存指令:ldr/str & ldm/stm & swp

单个字/半字/字节访问 ldr/str;

多字批量访问 ldm/stm;

swp r1, r2, [r0]   ( 将R0 指向的存储单元内容读取数据到R1 中; 并将R2 的内容写入到该内存单元中) ;

swp r1,r1,[r0]    (取出r0地址中的数据,放在r1中,并把r1中的数据放在r0中);

ARM汇编中的立即数:

合法立即数与非法立即数。

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

软中断指令:swi(software interrupt)

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

15.4,协处理器指令

协处理器cp15操作指令:mrc & mcr

mrc用于读取CP15中的寄存器;

mcr用于写入CP15中的寄存器;

什么是协处理器?

SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务;ARM设计支持多达16个协处理器,但是一版SoC只实现其中的CP15(cp:coprocessor)。

15.5,为什么要多寄存器访问

ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ldm;

ldm(load register mutiple)

stm(store register multiple)

stmia sp,{r0 - r12}

将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址... ...,直到r12内容放入(0x30000130),指令完成。

一个访存周期同时完成13个寄存器的读写。 

8种后缀:

ia(increase after)先传输,再地址+4;

ib(increase before)先地址+4,再传输;

da(decrease after)先传输,再地址-4;

db(decrease before)先地址-4,再传输;

fd(full decrease)满递减堆栈;

ed(empty decrease)空递减堆栈;

fa(full ......)满递增堆栈;

ea(empty ......)空递增堆栈;

理解:ia ib da db与fd ed fa ea是对应的,在编程时使用一种后缀方式就会避免出错,ARM中一版使用满减栈。

四种栈:

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

满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;

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

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

!的作用:

ldmia   r0, {r2 - r3}

ldmia   r0!, {r2 - r3}

感叹号的作用是r0的值在ldm过程中发生的增加或减少最后写回到r0中,也就是ldm时会改变r0的值。

  

^的作用:

ldmfd    sp!, {r0 - r6, pc} 

ldmfd    sp!, {r0 - r6, pc} ^

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

15.6,伪指令 

 

;