太初有道,何为道???这就是我心中追寻的道!!!
从今天开始学习linux内核在ARM架构的实现,参考那本厚厚的书,希望在离开这里的时候,自己真的能够成长到自己崇拜的样子。
大道之行,始于足下,万里长征,从这里开始。
就从ARM的汇编指令开始吧。。。
ARM汇编指令集:
指令和伪指令:
指令:是机器的助记符,经过汇编编译后变成二进制码,由CPU执行的。
伪指令:用来指导指令的执行,不但在汇编语言中有伪指令,在C语言中也存在伪指令(#define #ifdef )等就是伪指令,伪指令是用来告诉编译器如何编译指令,是不会产生机器码的指令。
有两种不同风格的指令
(1)ARM官方的汇编风格指令:指令一般用大写,例如在Window中的IDE开发环境中。
(2)GNU风格的汇编风格指令:指令一般用小写,例如linux环境中的编程。
ARM汇编的特点
(1)LDR/STR架构
1:ARM采用RISC(精简指令集32bit)架构,CPU本身并不能直接读取内存,需要先将内存中的数据加载到CPU的通用寄存器中才能被CPU访问和处理。
2:ldr(load register)指令是将内存中的数据加载到通用寄存器的指令
3:str(store register)指令是将通用寄存器中的数据存储到内存中的指令
用ldr/str指令来实现CPU和内存之间的数据交换。
8钟寻址方式
(1)寄存器寻址:mov r1,r2 (将r2寄存器的内容给r1寄存器)
(2)立即数寻址:mov r0, #0x1 (将0x1给r0寄存器)
(3)寄存器移位寻址:mov r1, r2 lsl #2(将r2寄存器的值逻辑左移两位后给人r1寄存器)
(4)寄存器间接寻址:ldr r1, [r2] ([r2]表示内存,内存地址存在r2寄存器中,把r2寄存器中的值的内存地址的值给r1)
(5)基址变址寻址:ldr r1, [r2,#4] (将r2寄存器中存的值对应的内存地址再加4,然后取内容给r1寄存器)
(6)多寄存器寻址:ldmia r0, {r1, r2,r3,r4} (一次访问多个寄存器)
ldmia是数据加载指令,指令的后缀ia表示每次执行完加载操作后R0寄存器的值自增1个字。指令执行后,R1=[R0],R2=[R0+#4],R3=[R0+#8],R4=[R0+#12]
(7)堆栈寻址:stmfd sp1, {r1-r7,lr}将R1-R7,LR入栈,多用于保存子程序现场。
堆栈寻址是ARM处理器特有的一种寻址方式,堆栈寻址使用特定的指令来完成。
(8)相对寻址:bl flag/beq flag (跳转到flag标号的位置处)
指令后缀
同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:
B(byte)功能不变,操作长度变为8位
H(half word)功能不变,长度变为16位
S(signed)功能不变,操作数变为有符号
如 ldr ldrb ldrh ldrsb ldrsh
S(S标志)功能不变,影响CPSR标志位
如 mov和movs movs r0, #0
条件执行后缀
条件后缀是否成立取决于当前代码的前面的代码。
条件后缀只影响当前代码的执行。
CPSR寄存器包含下面的ALU状态标志:
N | Set when the result of the operation was Negative |
Z | Set when the result of the operation was Zero |
C | Set when the operation result in a Carry(发生进位,或借位) |
V | Set when the operation caused oVerflow(操作造成溢出) |
Q | ARM architecture v5E only sticky flag |
CPSR访问指令
mrs & msr
mrs用来读psr,msr用来写psr
CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr。
多级指令流水线
为增加处理器指令流的速度,ARM使用多级流水线.,(S5PV210使用13级流水线,ARM11为8级)
允许多个操作同时处理,而非顺序执行。
数据处理指令
数据处理指令大致包含以下指令:
mov,add,adds,adc,sub,subs,sbc,rsb,mul,and,orr,eor,bic,cmp,tst,teq,lsl,lsr,asr,rorv
数据传输指令 mov mvn
算术指令 add sub rsb adc sbc rsc
逻辑指令 and orr eor bic
比较指令 cmp cmn tst teq
乘法指令 mvl mla umull umlal smull smlal
前导零计数 clz
数据处理指令语法
<操作{}{S}> , , <操作码> <目标寄存器Rd> <第一操作寄存器Rn> <第二操作数Operand2> 第一个目标寄存器必须是寄存器,第二操作数可以是寄存器,也可以是立即数
数据传送指令 MOV
mov r1, #0x1 ;r1 = 0x1 0x1 是立即数 mov r2, r1 ;r2 = r1 mvn r3, r2 ;r3 = ~r2 mov r1, 0xffffff00 ;0xffffff00 不是立即数,只是编译器在编译阶段对其进行了替换 mvn r1, 0x000000ff ;替换的指令 其实一条数据传送指令 mov reg, #n mov reg占用 bit[31:12],bit[11:0]留给立即数使用,因此立即数自包含2^12个 ,一个立即数由 bits[8:0]循环右移 2 * bits[11:9]得到。(一个八位的数循环右移偶数次得到)立即数的本质是包含于指令中的数,占用指令本身的空间
加法指令 ADD
加法指令执行时,若没有进位 CPSR 'C' 位置 0 mov r0, #1 mov r1, #1 add r2, r1, r0 ;r2 = r1 + r0 add r2, r1, #2 ;r2 = r1 + 2
数据操作对CPSR的影响
默认情况下,数据处理指令不影响条件码标志位,但可以选择通过添加“S”来影响标志位。 mov r1, #0mov r2, #-1 adds r3, r1, r2
带进位的加法指令 ADC
两个64位数相加,第一个64位的低32位放在 r0,高位放到 r1,第二个64位数的低32位放在 r2 高32位放在 r3 ,编写代码实现两个64位数的和,结果的低32位放在 r4 高32位放在 r5 mov r0, #0xfffffffe ;第一个数的低32位 mov r1, #1 ;第一个数的高32位 mov r2, #0x5 ;第二个数的低32位 mov r3, #1 ;第二个数的高32位 adds r4, r0, r2 adc r5, r1, r3 ; adc运算的实质是 r5 = r1 + r3 + 'C' 'C'位 CPSR 进位标志
减法指令 SUB
减法指令执行时,没有借位时 CPSR 'C' 位置 1 mov r0, #5 mov r1, #3 sub r2, r0, r1 ;r2 = r0 - r1
带借位的减法指令 SBC
mov r0, #1 ;第一个数的低32位 mov r1, #3 ;第一个数的高32位 mov r2, #3 ;第二个数的低32位 mov r3, #1 ;第二个输的高32位 subs r4, r0, r2 sbc r5, r1, r3
逆向减法指令 RSB
mov r0, #3 rsb r1, r0, #5 ;r1 = 5 - r0
乘法指令 MUL
为了提高效率,任何乘法指令不可以使用立即数 mov r0, #3 mov r1, #5 mov r2, r0, r1 ;r2 = r0 * r1
乘——累加指令 MLA
mla r3 ,r0, r1, r2 ;r3 = (r0 * r1) + r2
逻辑与指令 AND
mov r0, #0xf0 mov r1, #0x0f and r2, r0, r1 ;r2 = r0 & r1
逻辑或指令 ORR
mov r0, #0xf0 mov r1, #0x0f orr r2, r0, r1 ;r2 = r0 | r1
逻辑异或运算指令 EOR
mov r0, #0xf0 mov r1, #0x0f eor r2, r0, r1 ;r2 = r0 ^ r1
位清零指令 BIC
mov r0, #0xff bic r0, r0, #0xf ;第二个操作数的每一位为 1 就把第一个操作数对应的位清零
比较指令 CMP
实质是一条减法指令没有目标register,用来比较两个数是否相等,结果放到 CPSR 的 'Z' 位判断 mov r0, #2 mov r1, #1 cmp r0, r1
位测试指令 TST
实质是与运算 常用于用来测试某一位或某几位是 0 还是 1,结果通过 CPSR 的 'Z' 位判断 tst r0, #0x3
相等测试指令 TEQ
实质是异或运算,测试两个数是否相等,两个数相等时异或结果位 0,通过 CPSR 的 'Z' 位判断 teq r0, r1
移位指令 LSL、LSR、ASR、ROR
需要与mov配合,不能够单独使用 mov r0, #0xff mov r1, r0, lsl #4 ;将 r0 逻辑左移 4 位放入 r1 中 LSL 逻辑左移:高位移出,低位补零 LSR 逻辑右移:低位移出,高位补零 ASR 算是右移:低位移出,高位补符号位 ROR 循环右移:低位移出,高位补低位移出位
跳转(分支)指令
b & bl & bx
b 直接跳转(就没打算返回)
bl branch and link,跳转前把返回地址放入lr中,以便返回,以便用于函数调用
bx跳转同时切换到ARM模式,一般用于异常处理的跳转。
访存指令
ldr/str & ldm/stm & swp
单个字/半字/字节访问 ldr/str
多字批量访问 ldm/stm
swp r1, r2, [r0]
swp r1, r1, [r0]
软中断指令
swi(software interrupt)
软中断指令用来实现OS中系统调用
ARM汇编中的立即数
合法立即数与非法立即数
ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数。因此立即数有合法和非法之分。
合法立即数:经过任意位数的移位后非零部分可以用8位表示的即为合法立即数。
协处理器与协处理器指令集
ARM协处理器指令包括如下三类:
1:用于ARM处理器初始化ARM协处理器的数据操作
2:用于ARM处理器的寄存器和ARM协处理器的寄存器间的数据传送操作
3:用于在ARM协处理器的寄存器和内存单元之间传送数据
这三类指令共有五条指令:
CDP协处理器数据操作指令
LDC协处理器数据读入指令
STC协处理器数据写入指令
MCR ARM寄存器到协处理器寄存器的数据传送指令
MRC 协处理器寄存器到ARM寄存器的数据传送指令
使用方法:
CDP p5, 2,c12,c10,c3,4 ;协处理器p5的操作初始化,其中操作码1为2,操作码2为4,目标寄存器为c12,源操作数寄存器为c10,和c3
LDC 指令从一系列连续的内存单元将数据读取到协处理器的寄存器中,如果协处理器不能成功的执行该操作,将产生未定义的指令异常操作
LDC p6, CR4,[R2,#4];R2为ARM寄存器,指令读取内存单元R2+4的字数据,传送到协处理器p6的CR4寄存器中
STC(协处理器数据写入指令)
STC指令将协处理器的寄存器中的数据写入一些列内存单元中
STC p8,CR8,[R2,#4]! ;R2为ARM寄存器,指令将协处理器P8的CR8寄存器中的字数据写入到内存单元(R2+4)中,然后执行R2=R2+4操作
MCR(ARM寄存器到协处理器寄存器的数据传送指令)
MCR 指令将ARM处理器的寄存器中的数据传送到协处理器的寄存器中,如果协处理器不能成功执行该操作,将产生未定义的指令异常中断
MCR p14,3,R7,c7,c11,6 ;指令从ARM寄存器中将数据传送到协处理器p14的寄存器中,其中R7为ARM寄存器,存放源操作数,C7和C11位协处理器寄存器,为目标寄存器,操作码1为3,操作码2为6
MRC协处理器寄存器到ARM寄存器的数据传送指令
MRC指令将协处理器寄存器中的数值传送到ARM的寄存器中,如果协处理器不能成功的执行这些操作,那么将产生未定义的指令异常中断
MRC p15,2,R5,c0,c2,4 ;指令将协处理器p15寄存器中的数据传送到ARM寄存器中,其中,R5为ARM寄存器,是目标寄存器,C0和C2为协处理器寄存器,存放源操作数,操作码1为2,操作码2为4
协处理器解析:
SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。
ARM设计上支持多达16个协处理器,但是一般SoC只实现其中的CP15.(cp:coprocessor)
协处理器和MMU、cache、TLB等处理有关,功能上和操作系统的虚拟地址映射、cache管理等有关。
ldm/stm与栈的处理
为什么需要多寄存器访问指令
ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ldm
ldm(load register mutiple)
stm(store register mutiple)
后缀的种类:
ia(increase after)先传输,再地址+4
ib(increase before)先地址+4,再传输
da(decrease after)先传输,再地址-4
db(decrease before)先地址-4,再传输
fd(full decrease)满递减堆栈
ed(empty decrease)空递减堆栈
fa(·······) 满递增堆栈
ea(·······)空递增堆栈
四种栈解析:
空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出。
满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针。
增栈:栈指针移动时向地址增加的方向移动的栈。
减栈:栈指针移动时向地址减小的方向移动的栈。
!的作用:
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,一般用于从异常模式返回。
总结:批量读取或写入内存时要用ldm/stm指令。
各种后缀以理解为主,不需记忆,最常见的是stmia和stmfd。
谨记:操作栈时使用相同的后缀就不会出错,不管是满栈还是空栈、增栈还是减栈。
常用gun伪指令:
global _start :给_start外部链接属性
.section .text :指定当前段为代码段
.ascii .byte .short .long .word
.quad .float .string :定义数据
.align 4 :以4字节对齐
.balignl 16 0xabcdefgh :16字节对齐填充
.equ :类似于C中宏定义
偶尔会用到的gun伪指令
.end :标识文件结束
.include : 头文件包含
.arm / .code32 :声明以下为arm指令
.thumb / .code16 :声明以下为thubm指令
重要的几个伪指令
ldr 大范围的地址加载指令
adr 小范围的地址加载指令
adrl 中等范围的地址加载指令
nop 空操作
ARM中有一个ldr指令,还有一个ldr伪指令
一般都使用ldr伪指令而不用ldr指令
adr与ldr
adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理;
adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址在哪里
ldr加载的地址和链接时给定的地址有关,由链接脚本决定。