希望本是无所谓有,无所谓无的,这正如脚下的路,其实地上本没有路,走的人多了,也便成了路....原创不易,文章会持续更新,感谢您的关注
1.伪指令和指令之间的差别
指令是CPU机器码的助记符,它经过编译后会实实在在的在CPU中执行;而伪指令本质上不是指令,它是编译环境提供的,目的是指导编译过程,汇编指令经过汇编器编译之后就会消失,不会出现在机器码中。
2.两种编程风格
windows下的IDE工具(譬如:MDK或是ADS),在这种环境下,汇编指令一般是大写;另一种就是GNU环境下的,指令一般是小写;当然最主要区别还是在于伪指令有很多的不同,因为伪指令是编译环境提供的。
3.arm汇编指令的特点
(1)arm汇编指令特点1:ldr/str架构
因为arm CPU采用RISC架构,这种架构下的CPU内核是不能直接和内存通信,即CPU是不能直接访问内存的。
必须先将内存中的数据加载到那37个通用寄存器中,CPU可以直接访问这些寄存器,然后再在寄存器中处理数据,处理完成之后再将数据写回到内存中。内存中的数据加载到通用寄存器,就是ldr;将寄存器中的数据写回到内存中就是str;所以arm CPU采用的就是这种ldr-str架构。
(2)特点2:有8中寻址方式(2.1)寄存器寻址:mov r0, r1;指寄存器之间的访问,将寄存器r1中的内容加载到r0寄存器中。
(2.2)立即数寻址:mov r0, #0xff00;将一个立即数放到r0。什么是立即数?因为在计算机中所有的信息都是二进制数据,不管是图片还是文字,当然数字也是二进制的。为了能够更加详细的表示一个二进制数据本身就是一个数字,所以将这个数字的二进制或是十六进制称为立即数;譬如0x1200ff是一个二进制数,但是我们也无法知道它是真正的数字还是文字或是图片,在这个数据的前面加上一个#就表示,它是一个实实在在的数字,也就是立即数。
(2.3)寄存器移位寻址:mov r1, r0, lsl #3;先将r0中的数据左移3位,然后再移到r1。
(2.4)寄存器间接寻址:ldr r0,[r1];加上一个[ ]之后,表示r1里面放的是一个内存地址。这句代码的意思:将以r1中存放的数为内存的内容加载到r0。
(2.5)基址变址寻址:ldr r0,[r1, #4];将以r1中的数+4为内存地址处的内容放到r0寄存器中;r1中的数值就相当于基址,#4就相当于变址。
(2.6)多寄存器寻址:ldmia r1!,{r2~r7,r12} ;ldr指将内存中数据加载到寄存器中,这里指将r1中的数字作为内存的基地址,从这个基地址开始依次将内存数据加载到r2~r7和r12中;每加载一个寄存器后,r1的值就是自动的+4。
(2.7)堆栈寻址:stmfd sp!, {r2 ~ r7, lr} ;sp是一个堆栈指针,str表示将寄存器中的数据加载到内存中,这里就是r2~r7和lr这些寄存器的值加载到以sp中的数字为基地址的堆栈中。
(2.8)相对寻址:bl flag ;flag是一个标号,表示某个指令的地址,使用bl直接跳转到这个标号处去执行代码。
(3)指令后缀,加上指令后缀不会改变这条指令的基本功能
(3.1)b(byte):默认情况下arm指令操作的都是32位的数据,访问内存时也是这样。在访问内存指令的后面加上一个b表示操作数为8位,也就是一次只访问8位的数据,而不是32位的数据。
(3.2)h(helf word):表示操作的数据长度是16位,其他和上面的一样。
(3.2)s(s标志):默认情况下指令运行的结果是不会影响cpsr寄存器中的标志位的。
(4)条件执行
譬如:moveq r1,r0 这条指令是否执行取决于前面的指令(可能就是上一条指令,也可能是前好几条指令)的运行结果。如果前面的指令的运行结果影响了cpsr寄存器中的标志位,并且影响的标志位符合这个指令的后缀,那么这条指令就会执行,否则就不会执行。譬如eq的含义就是标志位Z是否等于1,若等于1就会执行这条指令,如果不等于1,就会直接跳过,不会执行这条指令。
(5)多级流水线机制:
原理不用明白,只要知道,流水线机制造成的结果就是:PC寄存器中的值不是当前正在执行的指令的地址,而是当前正在执行的指令的地址+8地址处的指令。当我们函数调用或是异常处理时,PC中保存的值不是下一条的地址,而是下一条指令的地址 + 8,所以你在返回时放到PC的地址要lr - 8。
4.ARM数据处理指令
(1) mov(将立即数移动到寄存器中)、add、sub。
(2)bic:可以将数据的特定位清零,譬如:bic r1,#0x008:将r1寄存器中数据的第4位清0。
(3)orr:逻辑或。
(4)and:逻辑与。
(5)eor:逻辑异或。
5.ARM比较指令
比较指令默认就是会影响标志位的(即使不在后面加上s也会影响对应的标志位)。
(1)cmp:比较两个数据是否相等。如果相等,就将cpsr寄存器中的对应标志位置1,如果后面的指令有条件后缀,那么这条带条件的指令就会根据cpsr对应的标志位是否满足自己的要求来选择是否执行当前这句代码。cmp的本质就是将这两个数相减,然后看结果是否为0。
(2)cmn(用得不多):也是用来比较两个数的,cmp使用的比较方式是让这两个数据进行相减;这里的比较方式是让两个数相加,然后看相加后的数据是否为0,如果为0就会影响cpsr中的对应标志位。
6.ARM cpsr访问指令
因为cpsr寄存器比较特殊,所以需要专门的mrs和msr指令来进行访问。mrs用来读cpsr寄存器,将cpsr寄存器中的值读到某个通用寄存器中;msr就是将通用普通寄存器中的数据写到cpsr中。
7.ARM跳转指令
(1)b:直接跳转到标号处,不会返回。
(2)bl:在跳转到标号处之前,会先将PC寄存器中的值放到lr寄存器中(这是CPU自动做的,不用程序员来操作),然后再跳转到标号处去执行代码,执行完毕后,再返回到原来的位置继续执行代码。
8.ARM内存访问指令
(1)ldr:将内存中的数据加载到寄存器中。
(2)str:将寄存器中数据写到内存中。
(3)ldm:批量处理指令,在前面有介绍。
(4)stm:同理,批处理指令。
9.ARM swi软中断指令
CPU只要一执行这条指令就会触发一次中断(也就是跳转到对应的异常向量表中去执行对应的处理函数),因为这种中断是通过软件来产生的而不是硬件,所以被称为软中断。
10.ARM批量处理指令
ldr/str每个周期只能访问四个字节内存,如果需要批量读写操作数效率就太低了,所以就有了ldm/stm。批量处理的过程也是一个时钟周期,即一个时钟周期就能完成批量处理。使用ldr读四个字节需要一个时钟周期,使用ldm批量读取多个内存字节也是只需要一个时钟周期,使得效率得以提升。
stmia sp,{r0~r12}:sp寄存器中的值为内存地址,将该地址中的内容写到r0中,然后sp的地址值加4后,再将该地址的数据写到r1中,依次类推即可。虽然这一条指令处理的东西有点复杂,但是也同样只要一个时钟周期就可以完成。
11.特殊符号的作用
(1)!,感叹号的作用ldmia r0,{r2~r3} ;r0寄存器中的值为内存地址,将该地址中的内容写到r2中,然后r0地址+4,接着将内存中的内容放到r3。有个关键的地方,就是每次地址在加4的时候,sp中的值没有变。这句指令不会修改sp中的值。
ldmia r0!,{r2~r3} ;这句代码和上面的一样;区别就在于,每次地址就加4之后,会同时将这个加4之后的地址赋回给sp中,即sp中的值是不停的在变的。
(2)^符号的作用ldmfd sp!,{r0-r6,pc}和ldmfd sp!,{r0-r6,pc}^ ;^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr。
12.8种批处理指令后缀
(1)Ia:先传输,再地址加4
(2)Ib:先地址加4,然后再传输
(3)da:先传输,再地址减4
(4)db:先地址减4,再传输
(5)fd:满减栈
(6)ed:空减栈
(7)fa:满增栈
(8)ea:空增栈
13.四种栈模型
(1)空栈:栈指针指向空处,每次都可以将数据直接写入栈中,然后自己再将栈指针移动一格;而取出数据的时候,要先移动一格栈指针,然后才能取数据。
(2)满栈:栈指针指向最后一格数据,每次存入数据之前都要先将栈指针移动一格,然后再存入;当取数据的时候就可以直接取。
(3)增栈:每次移动栈指针时,栈指针是加4。
(4)减栈:每次移动栈指针时,都是减4。
注: 批处理指令后缀和四种栈模型有对应关系。ARM默认使用的是满减栈(fd)。即stmia和stmfd。
专注于 嵌入式 和 qt知识分享
欢迎扫码关注
“嵌入式工程师成长之路”