Load-Store体系结构
◆ Load-Store 结构——这个应该是RISC
设计中⽐较有特点的⼀部分。在RISC
中,CPU并不会对内存中的数据进⾏操作,所有的计算都要求在寄存器中完成。⽽寄存器和内存的通信则由单独的指令来完成。⽽在CSIC 中,CPU是可以直
接对内存进⾏操作的,这也是⼀个⽐较特别的地⽅。
◆ 更多的寄存器——和CISC
相⽐,基于RISC的处理器有更多的通⽤寄存器可以使⽤,且每个寄存器都可以进⾏数据存储或者寻址。
当然,作为RISC
领域最成功的处理器,ARM也遵从上⾯的特点。这⾥,我们不妨来看⼀看在user
模式下,ARM处理器的体系结构,这对于我们了解其汇编语⾔是有好处的。⽽其它模式下只是有⼀些寄存器分组略有不同,⼤家可以在ARM的⼿册上查到。这⾥
要说明的是,尽管ARM处理器也⽀持16位指令,不过在下⽂中,我们都假定ARM处理器在32
位模式下⼯作。
图1:user模式下ARM处理器体系结构
从图1中我们看到,在user 模式下,ARM CPU
有16个数据寄存器,被命名为r0~r15(这个要⽐x86的多⼀些)。r13~r15有特殊⽤途,其中:
◆ r13 - 指向当前栈顶,相当于x86的esp,这个东西在汇编指令中要⽤sp
表⽰
◆ r14 -
称作链接寄存器,指向函数的返回地址。⽤lr表⽰,这和x86将返回地址保存在栈中是不同的
◆ r15 -
类似于x86的eip,其值等于当前正在执⾏的指令的地址+8(因为在取址和执⾏之间多了⼀个译码的阶段),这个⽤pc表⽰
另外,ARM处理器还有⼀个名为cspr的寄存器,⽤来监视和控制内部操作,这点和x86
的状态寄存器是类似的。具体的内容就⽤到再说了。
ARM 指令集
ARM处理器可以⽀持3种指令集——ARM,Thumb和Jazelle。
采⽤那种指令集,由cspr中的标志位来决定。⼤体说来:
◆ ARM——这是ARM⾃⾝的32 位指令集
◆ Thumb ——这是⼀个全16 位的指令集,在16
位外部数据总线宽度下,这个指令集的效率要⽐32
位的ARM指令⾼⼀些。
◆ Jazelle ——这是⼀个8位指令集,⽤来加速Java字节码的执⾏
整个ARM指令集由数据处理指令、分⽀指令、Load-Store指令、程序中断指令和⼀些系统控制指令构成,除了Load-Store指令外,其他部分
和x86指令集是⽐较类似的。但和x86相⽐,ARM指令最显著的特点它们都是32-bit
定长的。另外,由于arm是基于RISC指令集的,所以CPU只处理在寄存器中的数据并通过独⽴的load-store指令在内存和寄存器之间进⾏数据的
传递。
在使⽤⽅⾯,ARM指令的格式也要⽐Intel的复杂些。⼀般说来,⼀条ARM指令有如下的形式:
{S} [Rd], [Rn], [Rm]
其中:
* {S} —— 加上这个后缀的指令会更新cpsr 寄存器
* [Rd] —— ⽬的寄存器
* [Rn]/[Rm] —— 源寄存器
⼀般来说,arm
指令有3个操作数,其中Rm寄存器在执⾏指令前可以进⼊桶形移位器进⾏移位操作,⽽Rn则会直接进⼊ALU
单元。如果⼀条arm 指令只有2 个操作数,那么源寄存器按照Rm
来处理。例如,⼀条加法指令:
add r0, r1, #1
就会把r1+1的结果存放到r0中。
在熟悉了基本的汇编格式后,读者就可以⾃⾏去查询基本的ARM汇编指令了,下⾯,我们出ARM中⽐较有特⾊部分——Load-Store指令结构,它是CPU
x86架构和arm架构区别和内存进⾏通信的⼀个重要媒介。
Load-Store 指令体系
由于ARM
CPU并不直接处理内存中的数据,这个指令体系就担起了在寄存器和内存之间交换数据的重要媒介。
它要⽐x86
的内存访问机制复杂⼀些。该指令体系分成3 类:
◆ 单寄存器传输(这是与x86 最为相像的)
◆ 多寄存器传输
◆ 交换指令
单寄存器传输
先看第⼀个,很简单:把单⼀的数据传⼊(LDR)
或传出(STR)寄存器,对内存的访问可以是DWORD(32-bit),
WORD(16-bit)和BYTE(8-bit)。指令的格式如下:
DWORD:
Rd, addressing1
WORD:
H Rd, addressing2 ⽆符号版
SH Rd, addressing2 有符号版
BYTE:
B Rd, addressing1 ⽆符号版
SB Rd, addressing2 有符号版
addressing1 和addressing2
的分类下⾯再说,现在理解成某种寻址⽅式就可以了。
在单寄存器传输⽅⾯,还有以下三种变址模式,他们是:
◆ preindex
这种变址⽅式和x86的寻址机制是很类似的,先对寄存器进⾏运算,然后寻址,但是在寻之后,基址寄存器的内容并不发⽣改变,例如:
ldr r0, [r1, #4]
的含义就是把r1+4 这个地址处的DOWRD 加载到r0,⽽寻址后,r1
的内容并不改变。
◆ preindex with writeback
这种变址⽅式有点类似于++i的含义,寻址前先对基地址寄存器进⾏运算,然后寻址.
其基本的语法是在寻址符[]后⾯加上⼀个”!” 来表⽰.例如:
ldr r0, [r1, #4]!
就可以分解成:
add r1, r1, #4
ldr r0, [r1, #0]
◆ postindex
⾃然这种变址⽅式和i++的⽅式就很类似了,先利⽤基址寄存器进⾏寻址,然后对基址寄存器进⾏运算,其基本语法是把offset
部分放到[]外⾯,例如:
ldr r0, [r1], #4
就可以分解成:
ldr r0, [r1, #0]
add r1, r1, #4
如 果你还记得x86 的SIB
操作的话,那么你⼀定想ARM是否也有,答案是有也没有。在ss上⾯提到的addressing1
和addressing2的区别就是⽐例寄存器的使⽤,addressing1可以使⽤[base,
scale, 桶形移位器]来实现SB
的效果,或者通过[base,offset](这⾥的offset
可以是⽴即数或者寄存器)来实现SI
的效果,⽽addressing2则只能⽤后者了。于是每⼀种变址⽅式最多可以有3
种寻址⽅式,这样⼀来,最多可以有9种⽤来寻址的指令形式。例如:
ldr r0, [r1, r2, LSR #0x04]!
ldr r0, [r1, -#0x04]
ldr r0, [r1], LSR #0x04
每样了⼀种,⼤概就是这个意思。到此,单寄存器传输就结束了,掌握这些⾜够应付差事了。下⾯来看看多寄存器传输吧。
多寄存器传输
说得很明⽩,意思就是通过⼀条指令同时把多个寄存器的内容写到内存或者从内存把数据写到寄存器中,效率⾼的代价是会增加系统的延迟,所以armcc
提供了⼀个编译器选项来控制寄存器的个数。指令的格式有些复杂:
<;寻址模式> Rn{!}, {r^}
我们先来搞明⽩寻址模式,多寄存器传输模式有4 种:
也就是说以A开头的都是在Rn的原地开始操作,⽽B开头的都是以Rn的下⼀个位置开始操作。如果你仍然感到困惑,我们不妨看个例⼦。
所有的⽰例指令执⾏前:
mem32[0x1000C] = 0x04
mem32[0x10008] = 0x03
mem32[0x10004] = 0x02
mem32[0x10000] = 0x01
r0 = 0x00010010
r1 = 0x00000000
r3 = 0x00000000
r4 = 0x00000000
1) ldmia r0!, {r1-r3} 2) ldmib r0!, {r1-r3}
执⾏后:
执⾏后:
r0 =
0x0010001C    r0
= 0x0010001C
r1 =
0x01
r1 = 0x02
r2 =
0x02
r2 = 0x03
r3 =
0x03
r3 = 0x04
⾄于DA 和DB 的模式,和IA / IB 是类似的,不多说了。
最后要说的是,使⽤ldm
和stm指令对进⾏寄存器组的保护是很常见和有效的功能。配对⽅案:
stmia / ldmdb
stmib / ldmda
stmda / ldmib
stmdb / ldmia
继续来看两个例⼦:
执⾏前:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
执⾏的指令:
stmib r0!, {r1-r3}
mov r1, #1 ; These regs have been modified
mov r2, #2
mov r3, #3
当前寄存器状态:
r0 = 0x0000100C
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x00000003
ldmia r0!, {r1-r3}
最后的结果:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
另外,我们还可以利⽤这个指令对完成内存块的⾼效copy:
loop
ldmia r9!, {r0-r7}
stmia r10!, {r0-r7}
cmp r9, r11
bne loop
说到这⾥,读者应该对RISC的Load-Store体系结构有⼀个⼤概的了解了,能够正确配对使⽤指令,是很重要的。