Showing posts with label cpu. Show all posts
Showing posts with label cpu. Show all posts

2009-12-10

对照Startup.s学习ARM汇编指令(quote)

(1)GBLL 伪指令用于定义一个全局的逻辑变量,并初始化为{False}。
GBLL    BOOTLOADER
BOOTLOADER   SETL    {TRUE}
(2)GET(或 INCLUDE)
GET 伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。可以使用 INCLUDE 代替 GET。
INCLUDE ..\\..\\kernel\\oal\\startup.s
(3)IMPORT 伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
    IMPORT      BootloaderMain
IMPORT      MMUSetup
(4)BL  带返回的跳转指令
(5)BEQ表示“相等则跳转”,即当CPSR中的Z标志置位时发生跳转
B   Label    ;程序无条件跳转到标号Label处执行
CMP R1,#0   ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行
BEQ Label   
(6)LDR 指令的格式为:
LDR{条件} 目的寄存器,<存储器地址>
LDR 指令用于从存储器中将一个 32 位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取 32 位的字数据到通用寄存器,然后对数据进行处理。当程序计数器 PC 作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。
指令示例:
LDR  R0,[R1]           ;将存储器地址为R1的字数据读入寄存器R0。
LDR  R0,[R1,R2]       ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR  R0,[R1,#8]      ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR  R0,[R1,R2] ! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1
LDR  R0,[R1,#8] ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR  R0,[R1],R2   ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR  R0,[R1,R2,LSL#2]!  ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR   R0,[R1],R2,LSL#2   ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
(7)STR 指令的格式为:
STR{条件} 源寄存器,<存储器地址>
STR 指令用于从源寄存器中将一个 32 位的字数据传送到存储器中。与LDR对应
(8)采用多寄存器寻址方式,一条指令可以完成多个寄存器值的传送。这种寻址方式可以用一条指令完成传送最多 16 个通用寄存器的值。以下指令:
LDMIA   R0,{R1,R2,R3,R4}   ;R1←[R0]
                                   ;R2←[R0+4]
                                  ;R3←[R0+8]
                                  ;R4←[R0+12]
该指令的后缀 IA表示在每次执行完加载/存储操作后,R0 按字长度增加,因此,指令可将连续存储单元的值传送到 R1~R4。
(9)SBC 指令的格式为:
SBC{条件}{S} 目的寄存器,操作数 1,操作数 2
SBC指令用于把操作数1减去操作数 2,再减去 CPSR 中的C 条件标志位的反码,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令使用进位标志 来表示借位,这样就可以做大于 32 位的减法。注意不要忘记设置 S后缀来更改进位标志。该指令可用于有符号数或无符号数的减法运算。
指令示例:
SUBS  R0,R1,R2     ; R0 = R1 - R2 - !C,并根据结果设置CPSR的进位标志位
(10)BX  带状态切换的跳转指令
(11)MCR 指令的格式为:
MCR{条件} 协处理器编码,协处理器操作码 1,源寄存器,目的寄存器 1,目的寄存器 2,协处理器操作码 2
MCR 指令用于将 ARM 处理器寄存器中的数据传送到协处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1和协处理器操作码2为协处理器将 要执行的操作,源寄存器为 ARM 处理器的寄存器,目的寄存器1和目的寄存器2均为协处理器的寄存器。
指令示例:
MCR   P3,3,R0,C4,C5,6    ;该指令将ARM处理器寄存器R0中的数据传送到协处理器P3的寄存器C4和C5中。 
(12)CMP 指令的格式为:
CMP{条件} 操作数 1,操作数 2
CMP 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新 CPSR 中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位表示的是操作数 1 与操作数 2 的关系(大、小、相等),例如,当操作数 1 大于操作操作数 2,则此后的有 GT 后缀的指令将可以执行。
指令示例:
CMP R1,R0  ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的标志位
CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标志位
(13)批量数据加载/存储指令LDM(或 STM)指令的格式为:
LDM(或 STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
LDM(或 STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈(SDM)或出栈(LDM)。其中,{类型}为以下几种情况:
IA  每次传送后地址加 1;
IB  每次传送前地址加 1;
DA  每次传送后地址减 1;
DB  每次传送前地址减 1;
FD  满递减堆栈;
ED  空递减堆栈;
FA  满递增堆栈;
EA  空递增堆栈;
{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
STMFD  R13!,{R0,R4-R12,LR}  ;将寄存器列表中的寄存器(R0,R4 到R12,LR)存入堆栈
LDMFD  R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)
(14)ORR 指令的格式为:
ORR{条件}{S} 目的寄存器,操作数 1,操作数 2
ORR 指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。操作数 1
应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数 1 的某些位。
指令示例:
ORR   R0,R0,#3             ; 该指令设置R0的0、1位,其余位保持不变。
(15)BIC 指令的格式为:
BIC{条件}{S} 目的寄存器,操作数 1,操作数 2
BIC指令用于清除操作数1 的某些位,并把结果放置到目的寄存器中。操作数 1 应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。操作数 2 为 32 位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不变。
指令示例:
BIC   R0,R0,#%1011     ; 该指令清除 R0 中的位 0、1、和 3,其余的位保持不变。
(16)
ADR(小范围的地址读取伪指令)
ADRL(中等范围的地址读取伪指令)
LDR(大范围的地址读取伪指令)
ldr     r0, =0xFFFFC000
用于将基于PC的地址或基于寄存器的地址读取到寄存器中。
///伪指令通过汇编编译器替换成对应的ARM/Thumb 指令。
以下指令的疑问:
ldr        r2, =(EbootImageSize/16)    // 加小括号代表什么?-----地址空间里存的数据

常用ARM汇编指令(quote)

ARM指令集可以分为六大类,分别为数据处理指令、Load/Store指令、跳转指令、程序状态寄存器处理指令、协处理器指令和异常产生指令。
ARM指令使用的基本格式如下:
〈opcode〉{〈cond〉}{S}     〈Rd〉,〈Rn〉{,〈operand2〉}
opcode 操作码;指令助记符,如LDR、STR等。
cond 可选的条件码;执行条件,如EQ、NE等。
S 可选后缀;若指定“S”,则根据指令执行结果更新CPSR中的条件码。
Rd 目标寄存器。
Rn 存放第1操作数的寄存器。
operand2 第2个操作数
 
arm的寻址方式如下:
立即寻址
寄存器寻址
寄存器间接寻址
基址加偏址寻址
堆栈寻址
块拷贝寻址
相对寻址
这里不作详细描述,可以查阅相关文档。
 
数据处理指令
Load/Store指令
程序状态寄存器与通用寄存器之间的传送指令
转移指令
异常中断指令
协处理器指令

 
 
在S3C2410、S3C2440的数据手册中对各种汇编指令有详细的描述;这里只对较常见的作写介绍。
1、相对跳转指令:b、bl
这两条指令的不同之处在于bl指令除了跳转之外,还将返回地址(bl的下一条指令的地址)保存在lr寄存器中。
这两条指令的可跳转范围是当前指令前后32M。
 

b funa
....
funa:
    b funb
....
funb:
....

 
2、数据传送指令mov,地址读取伪指令ldr
mov指令可以把一个寄存器的值赋给另外一个寄存器,或者把一个常数赋给寄存器。

mov r1, r2
/*上面是r1 = r2*/
mov r1,#1024
/*r1 = 1024*/

mov传送的常数必须能用立即数来表示。当不能用立即数表示时,可以用ldr命令来赋值。
ldr是伪命令,不是真实存在的指令,编译器会把它扩展成真正的指令;如果该常数能用“立即数”来表示,则使用mov指令,否则编译时将该常数保存在某个位置,使用内存读取指令把它读出来。

ldr r1, = 1024
/*r1 = 1024*/

3、内存访问指令 ldr、str、ldm、stm
ldr既可以指低至读取伪指令,也可以是内存访问指令。当他的第二个参数前面有'='时标伪指令,否则表内存访问指令。
ldr指令从内存中读取数据到寄存器,str指令把寄存器的指存储到内存中,他们的操作数都是32位的。
 
 
ldr r1, [r2, #4] /*将地址为r2+4的内存单元数据读取到r1中*/
ldr r1,[r2] /*将地址为r2的内存单元数据读取到r1中*/
ldr r1,[r2], #4/*将地址为r2的内存单元数据读取到r1中,然后r2=r2+4*/
str r1 ,[r2, #4]/*将r1的数据保存到地址为r2+4的内存单元中*/
str r1, [r2]/*。。。。*/
str r1, [r2],#4/*将r1的数据保存到地址为r2的内存单元,然后r2= r2+4*/

多寄存器传送指令可以用一条指令将16个可见寄存器(R0~R15)的任意子集合(或全部)存储到存储器或从存储器中读取数据到该寄存器集合中。与单寄存器存取指令相比,多寄存器数据存取可用的寻址模式更加有限。多寄存器存取指令的汇编格式如下:
LDM/STM{<cond>}<add mode>  Rn{!},  <registers>
4、加减指令 add、sub
add r1, r2, #1 /*r1=r2+1*/
sub r1, r2, #1 /*r1=r2-1*/

5、程序状态寄存器的访问指令msr,mrs
ARM指令中有两条指令,用于在状态寄存器和通用寄存器之间传送数据。修改状态寄存器一般是通过“读取-修改-写回”三个步骤的操作来实现的。 这两条指令分别是:
状态寄存器到通用寄存器的传送指令(MRS)
通用寄存器到状态寄存器的传送指令(MSR)
其汇编格式如下:
MRS{<cond>} Rd,CPSR|SPSR
其汇编格式如下:
MSR{<cond>} CPSR_f | SPSR_f,#<32-bit immediate>
MSR{<cond>} CPSR_<field> | SPSR_<field>,Rm
msr cpsr, r0 /*复制r0到cpsr中*/
mrs r0, cpsr /*复制cpsr到r0中*/

6、异常中断指令
异常中断指令可以分为一下两种:
软件中断指令(SWI)
断点指令(BKPT—仅用于v5T体系)
软件中断指令SWI用于产生SWI异常中断,用来实现在用户模式下对操作系统中特权模式的程序的调用;断点中断指令BKPT主要用于产生软件断点,供调试程序用。
7、其他伪指令
.extern main
.text
.global _start
_start:

'.extern' 定义一个外部符号(可以是变量也可以是函数),上面的代码表示表文本文件中引用的main是一个外部函数。
'.text'表示下面的语句都属于代码段
'.global'将本文件中的某个程序标号定义为全局的,如‘_start’就是个全局函数

ARM汇编学习的总结(quote)

ARM汇编学习的总结
ARM汇编指令的一些总结
ARM汇编指令很多,但是真正常用的不是很多,而且需要认真琢磨的又更少了。
比较有用的是MOV B BL LDR STR
还是通过具体汇编代码来学习吧。
      @ disable watch dog timer     
   mov   r1, #0x53000000   //立即数寻址方式
   mov   r2, #0x0
   str   r2, [r1]       
MOV没有什么好说的,只要掌握几个寻址方式就可以了,而且ARM的寻址方式比386的简单很多。立即数寻址方式,立即数要求以“#作前缀,对于十六进制的数,还要求在#后面加上0x或者&0x大家很好理解。有一次我碰到了&ff这个数,现在才明白跟0xff是一样的。
STR是比较重要的指令了,跟它对应的是LDRARM指令集是加载/存储型的,也就是说它只处理在寄存器中的数据。那么对于系统存储器的访问就经常用到STRLDR了。STR是把寄存器上的数据传输到指定地址的存储器上。它的格式我个人认为很特殊:
    STR(条件源寄存器,<存储器地址>
比如 STR R0, [R1] ,意思是R0-> [R1],它把源寄存器写在前面,跟MOVLDR都相反。
LDR应该是非常常见了。LDR就是把数据从存储器传输到寄存器上。而且有个伪指令也是LDR,因此我有个百思不得其解的问题。看这段代码:
mov r1, #GPIO_CTL_BASE
   add   r1, r1, #oGPIO_F
   ldr   r2,=0x55aa   // 0x55aa是个立即数啊,前面加个=干什么?
   str   r2, [r1, #oGPIO_CON]
   mov   r2, #0xff
   str   r2, [r1, #oGPIO_UP]
   mov   r2, #0x00
   str   r2, [r1, #oGPIO_DAT]
对于当中的ldr 那句,我就不明白了,如果你把=去掉,是不能通过编译的。我查了一些资料,个人感觉知道了原因:这个=应该表示LDR不是ARM指令,而是伪指令。作为伪指令的时候,LDR的格式如下:
    LDR 寄存器, =数字常量/Label
它的作用是把一个32位的地址或者常量调入寄存器。嗬嗬,那大家可能会问,
MOV r2,#0x55aa也可以啊。应该是这样的。不过,LDR是伪指令啊,也就是说编译时编译器会处理它的。怎么处理的呢?——规则如下:如果该数字常量在MOV指令范围内,汇编器会把这个指令作为MOV。如果不在MOV范围中,汇编器把该常量放在程序后面,用LDR来读取,PC和该常量的偏移量不能超过4KB
这么一说,虽然似懂非懂,但是能够解释这个语句了。
 
 
然后说一下跳转指令。ARM有两种跳转方式。
1 mov pc <跳转地址〉
 这种向程序计数器PC直接写跳转地址,能在4GB连续空间内任意跳转。
2)通过 B BL BLX BX 可以完成在当前指令向前或者向后32MB的地址空间的跳转(为什么是32MB呢?寄存器是32位的,此时的值是24位有符号数,所以32MB)。
B是最简单的跳转指令。要注意的是,跳转指令的实际值不是绝对地址,而是相对地址——是相对当前PC值的一个偏移量,它的值由汇编器计算得出。
BL非常常用。它在跳转之前会在寄存器LR(R14)中保存PC的当前内容。BL的经典用法如下:
       bl NEXT   ; 跳转到NEXT
       ……
    NEXT
       ……
       mov pc, lr    ; 从子程序返回。
 
最后提一下Thumb指令。ARM体系结构还支持16位的Thumb指令集。Thumb指令集是ARM指令集的子集,它保留了32位代码优势的同时还大大节省了存储空间。由于Thumb指令集的长度只有16位,所以它的指令比较多。它和ARM各有自己的应用场合。对于系统性能有较高要求,应使用32位存储系统和ARM指令集;对于系统成本和功耗有较高要求,应使用16位存储系统和ARM指令集。
对ARM异常(Exceptions)的理解
毕设笔记
1.对ARM异常(Exceptions)的理解
所有的系统引导程序前面中会有一段类似的代码,如下:
.globl _start                    ;系统复位位置
_start: b       reset            ;各个异常向量对应的跳转代码
        ldr     pc, _undefined_instruction ;未定义的指令异常
        ldr     pc, _software_interrupt     ;软件中断异常
        ldr     pc, _prefetch_abort          ;内存操作异常
        ldr     pc, _data_abort               ;数据异常
        ldr     pc, _not_used                  ;未使用
        ldr     pc, _irq                       ;慢速中断异常
        ldr     pc, _fiq                       ;快速中断异常
从中我们可以看出,ARM支持7种异常。问题时发生了异常后ARM是如何响应的呢?第一个复位异常很好理解,它放在0x0的位置,一上电就执行它,而且我们的程序总是从复位异常处理程序开始执行的,因此复位异常处理程序不需要返回。那么怎么会执行到后面几个异常处理函数呢?
看看书后,明白了ARM对异常的响应过程,于是就能够回答以前的这个疑问。
当一个异常出现以后,ARM会自动执行以下几个步骤:
1)把下一条指令的地址放到连接寄存器LR(通常是R14),这样就能够在处理异常返回时从正确的位置继续执行。
2)将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。从异常退出的时候,就可以由SPSR来恢复CPSR
(3) 根据异常类型,强制设置CPSR的运行模式位。
4)强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中。
至于这些异常类型各代表什么,我也没有深究。因为平常就关心reset了,也没有必要弄清楚。
ARM规定了异常向量的地址:
   b       reset            ; 复位 0x0
ldr pc, _undefined_instruction ;未定义的指令异常 0x4
       ldr     pc, _software_interrupt     ;软件中断异常    0x8
       ldr     pc, _prefetch_abort          ;预取指令    0xc
       ldr     pc, _data_abort               ;数据        0x10
       ldr     pc, _not_used                  ;未使用      0x14
       ldr     pc, _irq                       ;慢速中断异常   0x18
        ldr   pc, _fiq                       ;快速中断异常    0x1c
这样理解这段代码就非常简单了。碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到相应的处理程序,然后再返回到主程序继续执行。
这些引导程序的中断向量,是仅供引导程序自己使用的,一旦引导程序引导Linux内核完毕后,会使用自己的中断向量。
嗬嗬,这又有问题了。比如,ARM发生中断(irq)的时候,总是会跑到0x18上执行啊。那Linux内核又怎么能使用自己的中断向量呢?原因在于Linux内核采用页式存储管理。开通MMU的页面映射以后,CPU所发出的地址就是虚拟地址而不是物理地址。就Linux内核而言,虚拟地址0x18经过映射以后的物理地址就是0xc000 0018。所以Linux把中断向量放到0xc000 0018就可以了。
另外,说一下MMU。说句实话,还不是很明白这个MMU机理。参加Intel培训的时候,李眈说了MMU的两个主要作用:
(1)安全性:规定访问权限
 (2) 提供地址空间:把不连续的空间转换成连续的。
第2点是不是实现页式存储的意思?
2005年6月9日
补充一下:  05/06/14

.globl _start ;系统复位位置
_start: b reset ;各个异常向量对应的跳转代码
ldr pc, _undefined_instruction ;未定义的指令异常

……

_undefined_instruction :
.word undefined_instruction

也许有人会有疑问,同样是跳转指令,为什么第一句用的是 b reset;
而后面的几个都是用ldr?

为了理解这个问题,我们以未定义的指令异常为例。

当发生了这个异常后,CPU总是跳转到0x4,这个地址是虚拟地址,它映射到哪个物理地址
取决于具体的映射。
ldr pc, _undefined_instruction
相对寻址,跳转到标号_undefined_instruction,然而真正的跳转地址其实是_undefined_instruction的内容——undefined_instruction。那句.word的相当于:
_undefined_instruction dw undefined_instruction (详见毕设笔记3)。
这 个地址undefined_instruction到底有多远就难说了,也许和标号_undefined_instruction在同一个页面,也许在很 远的地方。不过除了reset,其他的异常是MMU开始工作之后才可能发生的,因此undefined_instruction 的地址也经过了MMU的映射。
在刚加电的时候,CPU从0x0开始执行,MMU还没有开始工作,此时的虚拟地址和物理地址相同;另一方面,重启在MMU开始工作后也有可能发生,如果reset也用ldr就有问题了,因为这时候虚拟地址和物理地址完全不同。

因此,之所以reset用b,就是因为reset在MMU建立前后都有可能发生,而其他的异常只有在MMU建立之后才会发生。用b reset,reset子程序与reset向量在同一页面,这样就不会有问题(b是相对跳转的)。如果二者相距太远,那么编译器会报错的。