精华内容
下载资源
问答
  • 本章主要对Java并发中非常重要的概念Java内存模型、指令重排和happens-before原则进行学习。 1.内存模型 如果想要设计表现良好的并发程序,理解Java内存模型是非常重要的。 Java线程之间的通信由Java内存模型...

    [超级链接:Java并发学习系列-绪论]

    本章主要对Java并发中非常重要的概念Java内存模型、指令重排和happens-before原则进行学习。

    1.内存模型

    如果想要设计表现良好的并发程序,理解Java内存模型是非常重要的。

    Java线程之间的通信由Java内存模型(Java Memory Model,简称JMM)控制。
    JMM决定一个线程对共享变量的写入何时对另一个线程可见

    JMM把JVM内部划分为线程栈(Thread stack)堆(Heap),这张图演示了JMM的逻辑视图:
    这里写图片描述
    说明:

    1. 每个线程都拥有自己的线程栈。
    2. 线程栈包含了当前线程执行的方法调用相关信息,我们也把它称作调用栈
    3. 线程栈还包含了当前方法的所有本地变量信息
    4. 一个线程只能读取自己的线程栈,每个线程中的本地变量都会有自己的版本,线程中的本地变量对其它线程是不可见的。
    5. 所有原始类型(8种)的本地变量都直接保存在线程栈当中,线程可以传递原始类型的副本给另一个线程,线程之间无法共享原始类型的本地变量。
    6. 堆区包含了Java应用创建的所有对象信息。
    7. 堆区包含了所有线程创建的对象信息。
    8. 堆区包含了原始类型的封装类(如Byte、Integer、Long等等)的对象信息。

    下图展示了调用栈本地变量都存储在栈区对象都存储在堆区
    这里写图片描述

    • 局部的基本类型:完全存储于Thread Stack中。
    • 局部的对象引用:引用会被存储于Thread Stack中,对象本身会被存储于Heap中。
    • 对象的成员方法中的局部变量:如果方法中包含本地变量,则会存储在Thread Stack中。
    • 对象的成员变量:不管它是原始类型还是包装类型,都会被存储到Heap区。
    • Static类型的变量:存储于Heap中。
    • 类本身相关信息:存储于Heap中。

    堆中的对象可以被多线程共享:

    1. 一个线程如果要使用一个共享变量,则首先需要从Heap中加载这个共享变量,并在Thread Stack中形成这个共享变量的副本。
    2. 一个线程使用完这个共享变量之后,还需要将Thread Stack中共享变量的副本更新到Heap中。

    竞争

    如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。

    举例:
    Obj.count的初始值是0,线程A和线程B同时对Obj.count做自增操作。

    串行情况之一:

    • 线程A在Thread Stack中创建Obj.count的副本,线程A的栈区中的Obj.count=0。
    • 线程A在Thread Stack中对Obj.count执行自增操作,线程A的栈区中的Obj.count=1。
    • 线程A将Thread Stack中Obj.count的副本更新到Heap中,这时,Heap中的Obj.count=1。
    • 线程B在Thread Stack中创建Obj.count的副本,线程B的栈区中的Obj.count=1。
    • 线程B在Thread Stack中对Obj.count执行自增操作,线程B的栈区中的Obj.count=2。
    • 线程B将Thread Stack中Obj.count的副本更新到Heap中,这时,Heap中的Obj.count=2。
    • 经过两次自增操作,最终Obj.count=2。

    并行情况之一:

    • 线程A在Thread Stack中创建Obj.count的副本,线程A的栈区中的Obj.count=0。
    • 线程B在Thread Stack中创建Obj.count的副本,线程B的栈区中的Obj.count=0。
    • 线程A在Thread Stack中对Obj.count执行自增操作,线程A的栈区中的Obj.count=1。
    • 线程B在Thread Stack中对Obj.count执行自增操作,线程B的栈区中的Obj.count=1。
    • 线程A将Thread Stack中Obj.count的副本更新到Heap中,这时,Heap中的Obj.count=1。
    • 线程B将Thread Stack中Obj.count的副本更新到Heap中,这时,Heap中的Obj.count=1。
    • 经过两次自增操作,最终Obj.count=1。

    经过学习JMM,可以加深对并发会安全问题的理解。

    2.指令重排

    CPU执行单元的速度要远超主存访问速度。

    在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。

    • 编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
    • 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
    • 内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
    • 通过内存屏障禁止重排序:JMM通过插入特定类型的内存屏障,来禁止特定类型的编译器重排序和处理器重排序。

    2.1.单线程程序语义

    编译器、runtime和处理器都必须遵守as-if-serial语义。

    不管怎么重排序,单线程的执行结果不会改变。

    2.2.数据依赖性

    如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。

    编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。

    2.3.内存屏障

    内存屏障或内存栅栏(Memory Barrier),是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。

    内存屏障有两个能力:

    1. 就像一套栅栏分割前后的代码,阻止栅栏前后的没有数据依赖性的代码进行指令重排序,保证程序在一定程度上有序性
    2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效,保证数据的可见性

    内存屏障有三种类型和一种伪类型:

    1. lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新数据
    2. sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见
    3. mfence,即全能屏障,具备ifence和sfence的能力。
    4. Lock前缀:Lock不是一种内存屏障,但是它能完成类似全能型内存屏障的功能。

    在Java中:

    实现了内存屏障的技术有volatile

    volatile就是用Lock前缀方式的内存屏障伪类型来实现的。


    学习指令重排,让我们明白,因为指令重排,多线程开发中,是存在很多有序性和可见性问题的。

    3.happens-before原则

    从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。

    happens-before定义:

    Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.

    如果一个操作 happens-before 第二个操作,则第一个操作对第二个操作是可见的,并且一定发生在第二个操作之前。

    重要的happens-before原则:

    • If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
    • If an action x synchronizes-with a following action y, then we also have hb(x, y).
    • If hb(x, y) and hb(y, z), then hb(x, z).
    • An unlock on a monitor happens-before every subsequent lock on that monitor.
    • A write to a volatile field happens-before every subsequent read of that field.
    • A call to start() on a thread happens-before any actions in the started thread.
    • All actions in a thread happen-before any other thread successfully returns from a join() on that thread.

    理解如下:

    • 线程内部规则:在同一个线程内,前面操作的执行结果对后面的操作是可见的。
    • 同步规则:如果一个操作x与另一个操作y在同步代码块/方法中,那么操作x的执行结果对操作y可见。
    • 传递规则:如果操作x的执行结果对操作y可见,操作y的执行结果对操作z可见,则操作x的执行结果对操作z可见。
    • 对象锁规则:如果线程1解锁了对象锁a,接着线程2锁定了a,那么,线程1解锁a之前的写操作的执行结果都对线程2可见。
    • volatile变量规则:如果线程1写入了volatile变量v,接着线程2读取了v,那么,线程1写入v及之前的写操作的执行结果都对线程2可见。
    • 线程start原则:如果线程t在start()之前进行了一系列操作,接着进行了start()操作,那么线程t在start()之前的所有操作的执行结果对start()之后的所有操作都是可见的。
    • 线程join规则:线程t1写入的所有变量,在任意其它线程t2调用t1.join()成功返回后,都对t2可见。

    学习happens-before原则,让我们了解了JVM本身提供的关于并发安全(主要是有序性和可见性)的保障规则。


    参考文献

    [1] 聊聊高并发(三十五)Java内存模型那些事(三)理解内存屏障
    [2] 内存屏障或内存栅栏【转】
    [3] 全面理解Java内存模型
    [4] happens-before俗解
    [5] Chapter 17. Threads and Locks

    展开全文
  • 1. 访问内存的要素:  a. 总共只有两大要素:一是内存单元的地址,而是内存单元的类型;  b. 内存单元的首地址由DS:[X]确定,而内存单元的类型(字节型还是字型)由寄存器的类型给出,比如访问内存的寄存器的类型...

    1. 内存单元的访问方式(这里的不全,之后会进一步补充):

        1) 总共有四种方式:

            i. [立即数],但是在这种情况下只能作为源操作数(即第二个操作数,比如mov ax, [15]等),此时访问的内存是(ds:立即数),但是不能作为目的操作数(即第一个操作数),如果作为第一个操作数则编译器会将看做是一个普通的立即数而报错,比如mov [15], ax就会看成mov 15, ax而报错!一定要注意这点!

    以下所有方式既可以作为源操作数也可以作为目的操作数,没有任何限制!

            ii. [bx],注意,必须是基地址寄存器bx,不能是其它寄存器,如ax等,都会报错的!此时访问的就是(ds:bx)单元;

            iii. 任意段寄存器(包括cs):[bx],中括号中的偏移地址也必须是bx而不能是其它寄存器,这种表示法就是段前缀表示法,只不过不写段前缀默认ds作为段基,加了段前缀就能精确定位到某一个段上了!

        2) 访问内存单元最重要的关键——确定内存单元的类型(是字节型的还是字型还是双字型的):一般类型是由另一个操作数来确定(即根据寄存器的类型推断出来),比如mov al, [bx]中因为al是字节型的,因此传送的就是8位内存单元,而mov ax, [bx]中ax是16位的,因此传送的是双字,而mov [bx], [bx + 2]由于无法参考出传送的是什么类型的会编译报错!同样mov [bx], 23H中也无法推断出传送数据的类型而编译报错!

    **关于两个操作数都是内存单元的情况后面会补充,这里先不提(其实也是能实现的!但不过需要人为显示指定内存单元的类型)!


    2. 自加指令inc:

        1) 该命令只有一个参数,就是寄存器(也可以是内存单元,但是内存单元需要指定类型,后面会补充,这里先不讲),但可能不能作用于立即数;

        2) 作用是让指定寄存器中的内容自加1,可能会越界,需要注意;

        3) 除了段寄存器不能运用这条之另外其余所有寄存器(包括sp、bp、di、si等,不管是8位、16位还是32位)都可以运用该指令;


    3. 使用loop指令构造循环结构:

        1) 所有循环都需要有一个循环计数变量,loop指令也是一样,该计数变量存放在cx寄存器中;

        2) 执行loop指令的过程:先将cx中的内容-1,然后再判断其是否为0,如果为0则推出循环(其实就是继续执行代码中位于loop后面的语句),如果不为0(不是大于0而是非0)则跳到loop指令中指定的那个标签的位置继续循环(其实就是将cs:ip指向标签处);

    !汇编中的标签在编译后将会被替换成一个地址常量,定义方式和C语言中标签的定义方式相同,都是"标签名:"放在某个语句开头处;

        3) 一般步骤:

    	mov		cx, 循环次数
    tag:  循环起始位置
    	循环体
    	loop	tag


    4. 示例:

        1) 计算2^12:

    assume cs:codesg
    
    codesg segment
    	mov		ax, 1
    	
    	mov		cx, 12
    lop:
    	add		ax, ax
    	loop	lop
    
    	mov		ax, 4C00H
    	int		21H
    codesg ends
    
    end
    注意!对于立即数,如果不加后缀H就表示十进制数,如果加后缀H就表示是16进制数;

        2) 计算123×236:

    assume cs:codesg
    
    codesg segment
    	mov		ax, 0
    	
    	mov		cx, 123
    lop:
    	add		ax, 236
    	loop	lop
    
    	mov		ax, 4C00H
    	int		21H
    codesg ends
    
    end
        3) 将FFFF:0006字节中的值乘以3存入BX中

    assume cs:codesg
    
    codesg segment
    	mov		ax, 0FFFFH
    	mov		ds, ax
    	mov		ax, 0
    	mov		al, ds:[6]
    
    	mov		bx, 0
    	mov		cx, 3
    lop:
    	add		bx, ax
    	loop	lop
    
    	mov		ax, 4C00H
    	int		21H
    codesg ends
    
    end
    !!注意:关于MASM中立即数表示问题的小结:

           *1. 立即数字只能以数字打头而不能以字母打头,否则会编译报错;

           *2. 因此像诸如AB、A00、FFFF等数据都将是非法的,而解决方法就是是在这些数前面加上一个0作为前缀;

           *3. 以后缀辨别进制类型,十进制无后缀,二进制后缀为B,十六进制后缀为H,如果数字中有A~F而没有后缀H也会编译报错!

    !关于书写规范问题的建议:汇编编译器是不区分大小写的,但是通常指令和标记都用小写,而立即数中的字母(A~F以及后缀都是用大写),这样使程序更加直观;

        4) 求FFFF:0开始的13个字节内容之和(保存在DX寄存器中):

    assume cs:codesg
    
    codesg segment
    	mov		ax, 0FFFFH
    	mov		ds, ax
    	mov		bx, 0
    
    	mov		dx, 0
    	mov		ah, 0
    
    	mov		cx, 13
    lop:
    	mov		al, [bx]
    	add		dx, ax
    	inc		bx
    	loop	lop
    
    	mov		ax, 4C00H
    	int		21H
    codesg ends
    
    end
    注意!13个字节内容之和可能会超过8位,因此需要保存在16位寄存器,因此内存取出的字节要先放到ax中中转,然后再拿去和dx加!


    6. 利用P命令和G命令调试循环程序:

        1) 当循环次数非常大时单步调试明显不现实,此时可以使用P或G命令将剩余循环全部执行完(P是Proceed,即继续的意思,G就是Go的一次,但和C语言的goto语句不通,goto是直接跳跃,而G命令不仅跳跃到指定位置,并且将其中所有的代码都执行完);

        3) G:

            i. G命令的参数是偏移地址,不能含段基地址,而段基默认就是CS(且不能改),因此进入调试程序后,可以先用U命令反汇编即将执行的汇编指令,每条汇编指令的偏移地址都将被显示出来,然后再根据显示的地址选择想要到达的目的地偏移地址即可;

            ii. 执行命令后就会从当前指令处一直连续执行到目的指令处,中间所有的代码都会被执行(包括循环);

            iii. 目的指令地址必须大于当前指令地址否则调试将会异常终止而返回Debug程序;

            

            可以看到G不是跳转而是一直连续执行到指定指令出,也就是说中间的所有指令都执行过了,可以从BX中的结果看出中间的循环都执行完了;

        3) P:

            i. 在遇到int指令(即中断指令)时使用P将会成功的结束程序回到Debug,这在之前已经运用过了;

            ii. 当遇到loop指令使用P命令,将会将剩余的循环一次性全部走完,一直走到最后一次loop指令的后面一条指令处;

            iii. 当遇到非上述两种指令的普通指令时,效果就跟T命令一样,仅仅就是“单步继续执行指令”的意思了;

            

    展开全文
  • 我们已经知道,处理器是一台电子计算机的核心,它会在振荡器脉冲的激励下,从内存中获取指令,并发起一系列由该指令所定义的操作。当这些操作结束之后,它接着再取下一条指令...想要了解计算机为什么能够自动计算,...

    学习交流加

    • 个人qq:
      1126137994
    • 个人微信:
      liu1126137994
    • 学习交流资源分享qq群:
      962535112

    我们已经知道,处理器是一台电子计算机的核心,它会在振荡器脉冲的激励下,从内存中获取指令,并发起一系列由该指令所定义的操作。当这些操作结束之后,它接着再取下一条指令。通常情况下,这个过程是连续不断、循环往复的。

    1、寄存器和算数逻辑部件

    电子计算机能能做很多事情。计算天气预报,看电影,听音乐,上网等,实际上都是以数学计算为基础。它之所以能够计算,是因为它特殊的设计。想要了解计算机为什么能够自动计算,请去阅读书籍《穿越计算机的迷雾》。本片文章主要讲解处理器的简单功能。

    如图是一个处理器:

    在处理器的周围,有大量的引脚,这些引脚可以接受从外面来的电信号或者向外发送电信号(与外设进行信号的交互)。有很多引脚是复用的,假如现在我们要进行加法运算,我们就需要重复使用某一些引脚来依次将加数与被加数送入。一旦被加数被送入处理器,代表这个二进制数字的一组电信号就会出现在与该引脚相连的内部线路上(我们称这个内部线路为内部总线)。这是一组高低电平的组合,代表着二进制数中的每一位。当被加数被送进来了,接下来就要将加数送进来,但是此时内部总线上有被加数,所以处理器内部需要有一个电路来将被加数“锁存”。这个将被加数锁存的电路就是寄存器。它是一个存储器件。

    同样,加数也要锁存进另一个寄存器。如图中RA与RB分别锁存的是被加数与加数。此后,被加数与加数将不再受处理器的内部总线的影响。寄存器是双向器件,它会将它锁存的数,在另一端产生一模一样的电信号(注意此时寄存器中依然锁存着之前的电信号,它只是在另一端产生一个一模一样的电信号),传送给相应的算数逻辑部件(ALU)进行计算。算数逻辑部件(ALU)计算出结果后,将结果送出,给另一个寄存器RC锁存,稍后再将RC中的内容通过内部总线送到处理器外部,或者再次送回RA RB寄存器进行其他计算。

    那么,处理器是如何知道什么时候该干什么,各个部件在各个时间点该干什么,以及哪些部件在哪些时候使用内部总线以避免总线冲突呢?这些实际上都是由处理器内部的一个控制器控制的(图中没有画出)。

    处理器总是繁忙的,所有数据都只能在寄存器中寄存一会,就必须被送往别处。早期的寄存器只能存储4比特、8比特或者16比特,分别叫做4位寄存器,8位寄存器和16位寄存器。现在的处理器大多是32位和64位的。

    2、内存储器

    上一节我们知道,处理器的计算过程实际上是借助于寄存器和算数逻辑部件进行的。那么计算的数据是从哪里来的呢?它是从一个可以保存很多数字的电路,叫做存储器。存储器的种类很多,我们常见的U盘,硬盘,磁盘,内存条,甚至寄存器等都是存储器。

    我们这里主要说内存条。由于它通常是和处理器直接相连的,所以叫内存储器或者主存储器。又或者内存或者主存。和寄存器不同,内存用于保存更多的比特。对应用的最多的个人计算机来说,内存是按字节来组织的,单次访问的最小单位是1字节,这是最基本的存储单元。如下图所示是一个内存和内存访问示意图:

    内存中的每一个字节都对应着一个地址。从0地址开始,由于上面的内存是65536字节,所以该内存的最后一个字节是FFFF(都是用的十六进制表示的)。为了访问一个内存,处理器需要给出一个地址。同时访问内存是什么方式?读方式或者写方式,所以处理器需要有一个读写控制。如果是写数据,处理器还需要有一个数据传输的控制。最后,处理器单次访问内存时,是以字节为单位访问还是以字为单位访问等,需要有一个字长控制来告知。

    3、指令和指令集

    从一开始设计处理器,就是想让它自动工作,另外,还需要提供一种机制,来允许程序员决定进行何种工作。处理器的设计者用某些数来表示该处理器该做什么,这些数,我们称为指令或者叫机器指令。

    下面给个实际例子来分析指令在内存中的布局:

    假设现在我们的处理器是从内存的0000地址开始取指令(实际上肯定不是从0地址开始,这里只是假设)。第一条指令B8 5D 00,该指令的意思是将立即数005D传送到RA寄存器,它具有一个字节的操作码B8。由此我们可以看出简单的一个B8 5D 00指令,是一个传送指令,且该指令将两个字节(005D)传送到RA寄存器,并且由于下一条指令的地址是在0003字节处,所以该指令肯定还知道它自己是一条3字节指令,以便下一次处理器可以直接去到0003字节处去取指令。

    对于一些复杂的指令,一个字节的操作码肯定不够用,所以第二条指令8B 1E 3F 00的指令操作码是两字节8B 1E。

    同时我们注意到上述前两条指令中,第一条指令是将一个数005D直接传送到寄存器,第二条指令,是需要从另外一个地址(003F)先取出数据,然后再将数据传送到寄存器。第一种的操作数005D我们称为立即数(表示可以立即将数传送而不需要再去另外一个地址取数据)。

    由以上分析,我们很容易知道,上述的几条指令,首先将两个数放到寄存器中去运算,然后运算结果放到其中一个寄存器中,最终还需要将运算结果再传回到内存中以便该结果作为其他用处时处理器好可以从内存中得到计算结果。最后F4指令停机。

    指令和非指令的二进制数据是一模一样的,在组成内存的电路中,都是一些高低电平的组合。因为处理器是按顺序取指令,并加以执行的,这样的话,如果指令和数据存放在一起,处理器很容易将数据的二进制当成指令,从而导致错误。所以我们需要将指令与数据分开存放。分别存放在不同的区域。存放指令的区域叫做代码区,存放数据的区域叫做数据区(这就是为什么进程的虚拟地址空间中代码和数据区分开的原因)。

    一个处理器能够识别的指令的集合,称为指令集。

    4、Intel 8086处理器

    要想学好编程,就必须要懂底层原理。但是想要彻底学好底层,就要把汇编学好。想要学好汇编,不得不先学好针对8086的汇编技术。

    4.1、 8086的通用寄存器

    8086处理器内部有8个16位寄存器。分别被命名为:AX,BX,CX,DX,SI,DI,BP,SP。通用的意思是他们中大部分可以根据需要用于多种目的。他们的用处,将在后面的文章逐渐给出。

    下图是几个寄存器:
    在这里插入图片描述

    可以看到,有介个寄存器,AX,BX,CX,DX,又可以分为两个8位的寄存器来使用。在后面文章中,我们会看到具体的应用。

    4.2、 程序的重定位问题

    我们知道,处理器是自动取指令和执行指令的,只要每条指令都正确无误,它就能准确的知道下一条指令的地址。所以,指令必须集中在一起,形成一个段,叫做代码段。
    为了做某件事而编写的指令,形成我们所知道的程序。程序中肯定需要操作大量的数据,这大量的数据也应该集中在一起,位于内存中的某个地方,叫做数据段。

    段在内存中的位置并不重要,因为处理器是可控的,我们可以让处理器在内存中的任何一个位置开始取指令并加以执行。

    下面看一个图示:

    假设上面是一个程序片段在内存中的位置,相关指令的用处已经在图中表明。这里不再赘述。但是现在有一个问题,就是我们写的程序(不管是C语言还是C++语言或者Java语言),最终要运行在内存中的位置,是无法确定的。因为你想一想,本身你的电脑中有各种程序在跑了,有qq程序在跑,微信程序在跑,或者还有微博等在跑,他们可能把你的内存都占完了,当你想运行你写的程序的时候,你的程序本想从0100H处开始存放代码段,但是可能这个时候0100H处有其他程序的代码或者指令在运行,此时你的程序只能去另一个位置存放你的代码和数据,假设你的代码下一次运行的时候代码段和数据段在如下位置:

    此时你的程序的代数据段在内存的位置为1000H处,但是你会发现,你的代码段的第一条指令,还是将地址单元0100H处的内容传送到AX寄存器中。但是!!!此时0100H处的内容,并不是我们想要的内容。

    产生这种情况的原因就是我们在程序中使用的是绝对内存地址。这样的程序是无法重新定位的。所以我们需要使用另一种方式访问内存:相对地址或者叫做逻辑地址。在任何时候,程序的重定位都是非常棘手的事情。在8086处理器中,这个问题得到解决。它使用了分段机制。

    4.3、 内存分段机制

    在内存分段中,段,是很多个连续的字节组成的。如图:

    上图有一个7个字节的段。段的起始地址为A532。那么段中的地址从第一个字节开始,他们的地址此时就是段内的偏移地址(0000,0001,0002…)。而他们的实际的物理地址,又恰好等于段地址加上段内的偏移地址。可以用“段地址:偏移地址”,来表示段中的每一个字节的地址。

    为了在硬件一级提供对“段地址:偏移地址”的支持。处理器至少要提供两个段寄存器,分别是代码段寄存器CS,数据段寄存器DS。

    对代码段CS内容的改变,将导致处理器从新的代码段开始执行。当然,在访问数据之前,也是必须要提前设置好数据段寄存器DS的,使DS之指向数据段。

    很重要的一点是,当处理器取指令执行指令的时候,它是把指令中指定的内存地址看成是段内偏移地址的。而不是内存的物理地址。那么当处理器遇到一条访问内存的指令时,它就会将DS中的数据段起始地址加上指令中提供的段内偏移地址得到访问内存所需要的物理地址的。

    如下图所示:

    代码段地址为1020H,数据段地址为1000H,在代码段中有一条指令:A1 02 00,它的功能是将地址0002H处的的一个字传送到寄存器AX。在这里处理器将0002H看成是段内偏移地址,段地址在DS中,应该在执行这条指令之前就已经用别的地址将数据段地址传送到DS寄存器中了。当处理器执行到指令A1 02 00时,处理器会将DS中的内容和指令中指定的便宜地址相加,得到内存中的一个物理地址,这个物理地址就是处理器要去访问的内存地址,就可以从该地址获取一个字:00A0H。

    如果下一次执行该程序,代码段和数据段发生变化,只需要将程序的代码段地址和数据段地址分别传送到CS和DS就可以正常的执行程序了。

    4.4 、8086的内存分段机制

    前面讲了如何从逻辑地址转换到物理地址,以使得程序的运行和它在内存中的位置是无关的。上述策略在很多处理器上应用得到了支持。但是在8086处理器上,由于8086处理器是16位处理器,如果按照正常计算,给它提供16根地址线的话,那么8086处理器就只能用于寻址最多64KB的内存空间(65536字节=2的15次方+1)。在当时的年代64KB的内存还是不够的。所以8086处理器就将16根地址线扩展到20根地址线。从而使得可寻址的空间变为1M(16*64KB)。

    但是有一个问题就是16位的段地址加上16位的段内偏移地址,还是16位的,并不是20位的。所以有一个解决办法就是在计算内存的物理地址时,先将段地址先左移4位,然后加上段内偏移地址,这样就可以得到20位的物理地址,就可以将整个1M的地址空间表示完全。

    8086在进行分段时,并不是每一个地址都可以作为段地址,地址必须是16的倍数,才能作为段地址。

    如下图,是其中的一种情况,我们从0地址开始分段,每段16字节(从任何地址只要这个地址是16的倍数,都可以作为段地址,每一个段内最低有16字节(可以分为65536个段)的内存,最高有65536(可以分为16个段)个字节的内存)

    4.5 、8086 处理器内部组成框图

    最后我们再来看一下8086处理器内部组成框图:

    在这里插入图片描述

    上图中,我们知道8086内部有8个16位通用寄存器,分别为AX,BX,CX,DX,SI,DI,BP,SP。ALU是算数逻辑部件,用于算数逻辑运算或者数据传送。标志寄存器是用于控制各种依赖于标志位的标志来进行相应的指令执行的,这个可以等到以后的文章中进行详细的说明,目前不需要理解。处理器可以自动执行,依赖于控制器的控制。

    指令队列缓冲器,又名:指令预取队列。什么意思呢?就是当你的CPU在忙于做其他事情(一般是指执行那些不需要去内存中取的指令)而并没有去内存中取指令执行时,此时指令队列缓冲器(指令预取队列)就会去内存中取出6个字节的指令放到指令队列缓冲器,那么当CPU该执行内存中的指令时,它就会就近向指令队列缓冲器中去取指令,这样的话就会比去内存中取指令要快很多,毕竟内存还是通过外部总线与内存条相连接的。

    CS是代码段寄存器。DS是数据段寄存器。SS是栈段寄存器(很重要,以后会讲)。ES是额外的段寄存器,它的用处相当于补充段寄存器,当你的DS在使用时,但是你还要访问另一个数据段,那么此时就可以使用ES寄存器(后面的文章中我们就用ES寄存器指向显卡的内存区域,用来寻址显存,从而控制显卡)。IP寄存器,是存代码段的段内偏移地址的。那么指令的地址此时就可以用“CS:IP”来表示。

    上面的与总线控制逻辑相连的16位总线,实际上是16位的数据总线与20位的地址总线的低16位复用的。20位代表的是20位的地址总线是(当然,低16位是与数据总线复用的)。

    5、 总结

    想要写出优秀的应用程序,就必须对系统编程有深入的理解。想要对系统编程有一定的理解,就必须理解操作系统原理。我选择从X86汇编开始学习,逐步深入理解操作系统与计算机系统之间的关系。

    本片文章可以让我们学会

    • 寄存器与算数逻辑部件的简单关系
    • 内存储器
    • 指令和指令集
    • 程序的重定位问题
    • 处理器与内存之间的关系
    • 内存分段机制

    学习探讨加
    qq:1126137994
    微信:liu1126137994

    展开全文
  • ARM 处理器的指令集可以分为 跳转指令、数据处理指令、程序状态寄存器(PSR)处理指令、加载/存储指令、协处理器指令和异常产生指令 六大指令,这里把其它几个指令一起发了出来,可以查看具体的目录,请点击头部左上...

     

                   ARM   处理器的指令集可以分为 跳转指令、数据处理指令、程序状态寄存器(PSR)处理指令、加载/存储指令、协处理器指令和异常产生指令  六大指令,这里把其它几个指令一起发了出来,可以查看具体的目录,请点击头部左上角

     


     

     

    一、跳转指令

     

    跳转指令用于实现程序流程的跳转,在ARM程序中有以下两种方法可以实现程序流程的跳转。
    Ⅰ.使用专门的跳转指令;

     

    Ⅱ.直接向程序计数器PC写入跳转地址值,通过向程序计数器PC写入跳转地址值,可以实现在4GB的地址空间中的任意跳转,在跳转之前结合使用MOV LR,PC等类似指令,可以保存将来的返回地址值,从而实现在4GB连续的线性地址空间的子程序调用。

     

    ARM指令集中的跳转指令可以完成从当前指令向前或向后的32MB的地址空间的跳转,包括以下4条指令:

    1、B指令

    B指令的格式为:
    B{条件} 目标地址
    B指令是最简单的跳转指令。一旦遇到一个B指令,ARM处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。它是24位有符号数,左移两位后有符号扩展为32 位,表示的有效偏移为26 位(前后32MB的地址空间)。以下指令:
    B Label ;程序无条件跳转到标号Label处执行
    CMP R1,#0 ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行

     

    BEQ Label

     

     

    2、BL指令

    BL指令的格式为:
    BL{条件} 目标地址
    BL是另一个跳转指令,但跳转之前,会在寄存器R14中保存PC的当前内容,因此,可以通过将R14的内容重新加载到PC中,来返回到跳转指令之后的那个 指令处执行。该指令是实现子程序调用的一个基本但常用的手段。
    以 下指令:
    BL Label ;当程序无条件跳转到标号Label处执行时,同时将当前的 PC值保存到

     

     

    ;R14(LR)中

     

    3、BLX指令

    BLX指令的格式为: 
    BLX 目标地址

     

    BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态有ARM状态切换到Thumb状态,该指令同时将PC的当前内容保存到寄存 器R14中。因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现子程序的调用和处理器工作状态的切换。同时,子程 序的返回可以通过将寄存器R14值复制到PC中来完成。

     

    4、BX指令

    BX指令的格式为:
    BX{条件} 目标地址
    BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。

     

     

    二、数据处理指令

    数据处理指令可分为数据传送指令、算术逻辑运算指令 和比较指令等。
    数据传送指令用于在寄存器和存储器之间进行数据的双向传输;
    算术逻辑运算指令完成常用的算术与逻辑的运算,该类指令不但将运算结果保存在目的寄存器中,同时更新CPSR中的相应条件标志位;
    比较指令不保存运算结果,只更新CPSR中相应的条件标志位。

     

     

     

    数据处理指令共以下16条。

     

    1、MOV指令(传送)

    MOV指令的格式为:
    MOV{条件}{S} 目的寄存器,源操作数
    MOV指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中S选项决定指令的操作是否影响CPSR中条件标志位的值,当没有S 时指令不更新CPSR中条件标志位的值。
    指令示例:
    MOV R1,R0 ;将寄存器R0的值传送到寄存器R1
    MOV PC,R14 ;将寄存器R14的值传送到 PC,常用于子程序返回

     

    MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位后传送到R1

     

    2、MVN指令(求反)

    MVN指令的格式为:
    MVN{条件}{S} 目的寄存器,源操作数
    MVN指令可完成从另一个寄存器、被移位的寄存器、或将一个立即数加载到目的寄存器。与MOV指令不同之处是在传送之前按位被取反了,即把一个被取反的值 传送到目的寄存器中。其中S决定指令的操作是否影响CPSR中条件标志位的值,当没有S时指令不更新CPSR中条件标志位的值。
    指令示例:

     

    MVN R0,#0 ;将 立即数0取反传送到寄存器R0中,完成后R0=-1

     

    3、CMP指令(比较)

    CMP指令的格式为:
    CMP{条件} 操作数1,操作数2
    CMP指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新CPSR中条件标志位的值。该指令进行一次减法运算,但不存储结果,只 更改条件标志位。 标志位表示的是操作数1与操作数2的关系(大、小、相等),例如,当操作数1大于操作操作数2,则此后的有GT后缀的指令将可以执行。
    指令示例:
    CMP R1,R0 ;将寄存器R1的值与寄存器R0的值相减,并根据 结果设置CPSR的标 
    ;志位

     

    CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根 据结果设置CPSR的标志位

     

    4、CMN指令(负数比较)

    CMN指令的格式为:
    CMN{条件} 操作数1,操作数2
    CMN指令用于把一个寄存器的内容和另一个寄存器的内容或立即数取反后进行比较,同时更新CPSR中条件标志位的值。该指令实际完成操作数1和操作数2相 加,并根据结果更改条件标志位。
    指令示例:
    CMN R1,R0 ;将寄存器R1的值与寄存器R0的值相加,并根据 结果设置CPSR 
    ;的标志位

     

    CMN R1,#100 ;将寄存器R1的值与立即数100相加,并根据 结果设置CPSR的标志位

     

    5、TST指令(测试)

    TST指令的格式为:
    TST{条件} 操作数1,操作数2
    TST指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值。操作数1是要测试的数 据,而操作数2是一个位掩码,该指令一般用来检测是否设置了特定的位。
    指令示例:
    TST R1,#%1 ;用于测试在寄存器R1中是否设置了最低位(%表 示二进制数)
    TST R1,#0xffe ;将寄存器R1的值与立即数0xffe按位与,并根据 结果设置CPSR 

     

    ;的标志位

     

    6、TEQ指令(测试相等)

    TEQ指令的格式为:
    TEQ{条件} 操作数1,操作数2
    TEQ指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR中条件标志位的值。该指令通常用于比较操作数1和操作数2是否相等。
    指令示例:
    TEQ R1,R2 ;将寄存器R1的值与寄存器R2的值按位异或,并根据结果 设置CPSR 

     

    ;的标志位

     

    7、ADD指令(相加)

    ADD指令的格式为:
    ADD{条件}{S} 目的寄存器,操作数1,操作数2
    ADD指令用于把两个操作数相加,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。
    指令示例:
    ADD R0,R1,R2 ; R0 = R1 + R2
    ADD R0,R1,#256 ; R0 = R1 + 256

     

    ADD R0,R2,R3,LSL#1 ; R0 = R2 + (R3 << 1)

     

    8、ADC指令(带进位相加)

    ADC指令的格式为:
    ADC{条件}{S} 目的寄存器,操作数1,操作数2
    ADC指令用于把两个操作数相加,再加上CPSR中的C条件标志位的值,并将结果存放到目的寄存器中。它使用一个进位标志位,这样就可以做比32位大的数 的加法,注意不要忘记设置S后缀来更改进位标志。操作数1应是一个寄存器,操作数2可以是一 个寄存器,被移位的寄存器,或一个立即数。
    以下指令序列完成两个128位数的加法,第一个数由高到低存放在寄存器R7~R4,第二个数由高到低存放在寄存器R11~R8,运算结果由高到低存放在寄 存器R3~R0:
    ADDS R0,R4,R8 ; 加低端的字
    ADCS R1,R5,R9 ; 加第二个字,带进位
    ADCS R2,R6,R10 ; 加第三个字,带进位

     

    ADC R3,R7,R11 ; 加第四个字,带进位

     

    9、SUB指令(相减)

    SUB指令的格式为:
    SUB{条件}{S} 目的寄存器,操作数1,操作数2
    SUB指令用于把操作数1减去操作数2,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即 数。该指令可用于有符号数或无符号数的减法运算。
    指令示例:
    SUB R0,R1,R2 ; R0 = R1 - R2
    SUB R0,R1,#256 ; R0 = R1 - 256

     

    SUB R0,R2,R3,LSL#1 ; R0 = R2 - (R3 << 1)

     

    10、~~~~C指令

    ~~~~C指令的格式为:
    ~~~~C{条件}{S} 目的寄存器,操作数1,操作数2
    ~~~~C指令用于把操作数1减去操作数2,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以 是一个寄存器,被移位的寄存器,或一个立即数。该指令使用进位标志来表示借位,这样就可以做大于32位的减法,注意不要忘记设置S后缀来更改进位标志。该指令可用于有符号数或无符号数的减法运算。
    指令示例:

     

    SUBS R0,R1,R2 ;R0 = R1 - R2 - !C,并根据结果设置CPSR的进位标志位

     

    11、R~~~~指令

    R~~~~指令的格式为:
    R~~~~{条件}{S} 目的寄存器,操作数1,操作数2
    R~~~~指令称为逆向减法指令,用于把操作数2减去操作数1,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位 的寄存器,或一个立即数。该指令可用于有符号数或无符号数的减法运算。
    指令示例:
    R~~~~ R0,R1,R2 ; R0 = R2 – R1
    R~~~~ R0,R1,#256 ; R0 = 256 – R1

     

    R~~~~ R0,R2,R3,LSL#1 ; R0 = (R3 << 1) - R2

     

    12、RSC指令(反向带进位减)

    RSC指令的格式为:
    RSC{条件}{S} 目的寄存器,操作数1,操作数2
    RSC指令用于把 操作数2减去操作数1,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位 的寄存器,或一个立即数。该指令使用进位标志来表示借位,这样就可以做大于32位的减法,注意不要忘记设置S后缀来更改进位标志。该指令可用于有符号数或 无符号数的减法运算。
    指令示例:

     

    RSC R0,R1,R2 ;R0 = R2 – R1 - !C

     

    13、AND指令(逻辑位 与)

    AND指令的格式为:
    AND{条件}{S} 目的寄存器,操作数1,操作数2
    AND指令用于在两个操作数上进行逻辑与运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个 立即数。该指令常用于屏蔽操作数1的某些位。
    指令示例:

     

    AND R0,R0,#3 ;该指令保持R0的0、1位,其余位清零。

     

    14、ORR指令(逻辑位 或)

    ORR指令的格式为:
    ORR{条件}{S} 目的寄存器,操作数1,操作数2
    ORR指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个 立即数。该指令常用于设置操作数1的某些位。
    指令示例:

     

    ORR R0,R0,#3 ;该指令设置R0的0、1位,其余位保持不变。

     

    15、EOR指令(逻辑位 异或)

    EOR指令的格式为:
    EOR{条件}{S} 目的寄存器,操作数1,操作数2
    EOR指令用于在两个操作数上进行逻辑异或运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一 个立即数。该指令常用于反转操作数1的某些位。
    指令示例:

     

    EOR R0,R0,#3 ;该指令反转R0的0、1位,其余位保持不变。

     

    16、BIC指令(位清零)

    BIC指令的格式为:
    BIC{条件}{S} 目的寄存器,操作数1,操作数2
    BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。 操作数2为32位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不 变。
    指令示例:
    BIC R0,R0,#%1011 ;该指令清除R0中的位 0、1、和 3,其余的位保持不变。

     

     

    三、乘法指令与乘加指令

    ARM 微处理器支持的乘法指令与乘加指令共有6条,可分为运算结果为32位和运算结果为64位两类,与前面的数据处理指令不同,指令中的所有操作数、目的寄存器 必须为通用寄存器,不能对操作数使用立即数或被移位的寄存器,同时,目的寄存器和操作数1必须是不同的寄存器。 

     

     

     

    乘法指令与乘加指令共有以下6条:

     

    1、MUL指令(相乘)

    MUL指令的格式为:
    MUL{条件}{S} 目的寄存器,操作数1,操作数2
    MUL指令完成将操作数1与操作数2的乘法运算,并把结果放置到目的寄存器中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操 作数2均为32位的有符号数或无符号数。
    指令示例:
    MUL R0,R1,R2 ;R0 = R1 × R2

     

    MULS R0,R1,R2 ;R0 = R1 × R2,同时设置CPSR中的相关条件标志位

     

    2、MLA指令(带累加的相乘)

    MLA指令的格式为:
    MLA{条件}{S} 目的寄存器,操作数1,操作数2,操作数3
    MLA指令完成将操作数1与操作数2的乘法运算,再将乘积加上操作数3,并把结果放置到目的寄存器中,同时可以根据运算结果设置CPSR中相应的条件标志 位。其中,操作数1和操作数2均为32位的有符号数或无符号数。
    指令示例:
    MLA R0,R1,R2,R3 ;R0 = R1 × R2 + R3

     

    MLAS R0,R1,R2,R3 ;R0 = R1 × R2 + R3,同时设置CPSR中的相关条件标志位

     

    3、SMULL指令

    SMULL指令的格式为:
    SMULL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
    SMULL指令完成将操作数1与操作数2的乘法运算,并把结果的低32位放置到目的寄存器Low中,结果的高32位放置到目的寄存器High中,同时可以 根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数。
    指令示例:
    SMULL R0,R1,R2,R3 ;R0 = (R2 × R3)的低32位

     

    ;R1 = (R2 × R3)的高32位

     

    4、SMLAL指令

    SMLAL指令的格式为:
    SMLAL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
    SMLAL指令完成将操作数1与操作数2的乘法运算,并把结果的 低32位同目的寄存器Low中的值相加后又放置到目的寄存器Low中,结果的高32位同目的寄存器High中的值相加后又放置到目的寄存器High中,同 时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数。
    对于目的寄存器Low,在指令执行前存放64位加数的低32位,指令执行后存放结果的低32位;对于目的寄存器High,在指令执行前存放64位加数的高32位,指令执行后存放结果的高32位。
    指令示例:
    SMLAL R0,R1,R2,R3 ;R0 = (R2 × R3)的低32位 + R0

     

    ;R1 = (R2 × R3)的高32位 + R1

     

    5、UMULL指令

    UMULL指令的格式为:
    UMULL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
    UMULL指令完成将操作数1与操作数2的乘法运算,并把结果的低32位放置到目的寄存器Low中,结果的高32位放置到目的寄存器High中,同时可以 根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的无符号数。
    指令示例:
    UMULL R0,R1,R2,R3 ;R0 = (R2 × R3)的低32位

     

    ;R1 = (R2 × R3)的高32位

     

    6、UMLAL指令

    UMLAL指令的格式为:
    UMLAL{条件}{S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
    UMLAL指令完成将操作数1与操作数2的乘法运算,并把结果的 低32位同目的寄存器Low中的值相加后又放置到目的寄存器Low中,结果的高32位同目的寄存器High中的值相加后又放置到目的寄存器High 中,同 时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的无符号数。
    对于目的寄存器Low,在指令执行前存放64位加数的低32位,指令执行后存放结果的低32位;对于目的寄存器High,在指令执行前存放64位加数的高32位,指令执行后存放结果的高32位。
    指令示例:
    UMLAL R0,R1,R2,R3 ;R0 = (R2 × R3)的低32位 + R0

     

    ;R1 = (R2 × R3)的高32位 + R1

     

    四、程序状态寄存器访问指令

    1、MRS指令

    MRS指令的格式为:
    MRS{条件} 通用寄存器 程序状态寄存器(CPSR或SPSR)
    MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况:
    Ⅰ.当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。
    Ⅱ.当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。
    指令示例:
    MRS R0,CPSR ;传送CPSR的内容到R0

     

    MRS R0,SPSR ;传送 SPSR的内容到R0

     

    2、MSR指令

    MSR指令的格式为:
    MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数
    MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要 操作的位,32位的程序状态寄存器可分为4个域:
    位[31:24]为条件位域,用f表示;
    位[23:16]为状态位域,用s表示;
    位[15:8] 为扩展位域,用x表示;
    位[7:0] 为控制位域,用c表示;
    该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。
    指令示例:
    MSR CPSR,R0 ;传送R0的内容到CPSR
    MSR SPSR,R0 ;传送R0的内容到SPSR

     

    MSR CPSR_c,R0 ;传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域

     

    五、加载/存储指令

    ARM微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储 指令则完成相反的操作。常用的加载存储指令如下:

    1、LDR指令

    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。

     

    2、LDRB指令

    LDRB指令的格式为:
    LDR{条件}B 目的寄存器,<存储器地址>
    LDRB指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。 该指令通常用于从存储器中读取8位的字节数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目 的地址,从而可以实现程序流程的跳转。
    指令示例:
    LDRB R0,[R1] ;将存储器地址为R1的字节数据读入寄存器 R0,并将R0的高24 
    ;位清零。
    LDRB R0,[R1,#8] ;将存储器地址为R1+8的字节数据读入寄存器R0,并将 R0的

     

    ;高24位清零。

     

    3、LDRH指令

    LDRH指令的格式为:
    LDR{条件}H 目的寄存器,<存储器地址>
    LDRH指令用于从存储器中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。 该指令通常用于从存储器中读取16位的半字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作 目的地址,从而可以实现程序流程的跳转。
    指令示例:
    LDRH R0,[R1] ;将存储器地址为R1的半字数据读入寄存器 R0,并将R0的高
    ;16位清零。
    LDRH R0,[R1,#8] ;将存储器地址为R1+8的半字数据读入寄存器R0,并将R0 的
    ;高16位清零。
    LDRH R0,[R1,R2] ;将存储器地址为R1+R2的半字数据读入寄存器R0,并将 R0的

     

    ;高16位清零。

     

    4、STR指令

    STR指令的格式为:
    STR{条件} 源寄存器,<存储器地址>
    STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。 该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
    指令示例:
    STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并 将新地址
    ;R1+8写入R1。

     

    STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。

     

    5、STRB指令

    STRB指令的格式为:
    STR{条件}B 源寄存器,<存储器地址>
    STRB指令用于从源寄存器中将一个8位的字节数据传送到存储器中。该字节数据为源寄存器中的低8位。
    指令示例:
    STRB R0,[R1] ;将寄存器R0中的字节数据写入以R1为地 址的存储器中。

     

    STRB R0,[R1,#8] ;将寄存器R0中的字节数据写入以R1+8为地址的存 储器中。

     

    6、STRH指令

    STRH指令的格式为:
    STR{条件}H 源寄存器,<存储器地址>
    STRH指令用于从源寄存器中将一个16位的半字数据传送到存储器中。该半字数据为源寄存器中的低16位。
    指令示例:
    STRH R0,[R1] ;将寄存器R0中的半字数据写入以R1为地址的 存储器中。

     

    STRH R0,[R1,#8] ;将寄存器R0中的半字数据写入以R1+8 为地址的存储器中。

     

     

    六、批量数据加载/存储指令。

     

    ARM微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令 用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的操作。

    常用的加载存储指令如下:

    LDM(或STM)指令

    LDM(或STM)指令的格式为:
    LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
    LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。其中,{类型}为 以下几种情况:
    IA 每次传送后地址加1;
    IB 每次传送前地址加1;
    DA 每次传送后地址减1;
    DB 每次传送前地址减1;
    FD 满递减堆栈;
    ED 空递减堆栈;
    FA 满递增堆栈;
    EA 空递增堆栈;
    {!}为可选后缀,若选用该后缀,则当数据 传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
    基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
    {∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表 示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。

     

    指令示例:

    STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。
    LDMFD R13!, {R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。

     

     

     

    七、数据交换指令

     

     

    1、SWP指令

    SWP指令的格式为:
    SWP{条件} 目的寄存器,源寄存器1,[源寄存器2]
    SWP指令用于将源寄存器2所指向的存储器中的字数据传送到目的寄存器中,同时将源寄存器1中的字数据传送到源寄存器2所指向的存储器中。显然,当源寄存 器1和目的寄存器为同一个寄存器时,指令交换该寄存器和存储器的内容。
    指令示例:
    SWP R0,R1,[R2] ;将R2所指向的存储器中的字数据传送到R0,同时将R1 中的字数据传送到R2所指向的存储单元。

     

    SWP R0,R0,[R1] ;该指令完成将R1所指向的存储器中的字数 据与R0中的数据交换。

     

    2、SWPB指令

    SWPB指令的格式为:
    SWP{条件}B 目的寄存器,源寄存器1,[源寄存器2]
    SWPB指令用于将源寄存器2所指向的存储器中的字节数据传送到目的寄存器中,目的寄存器的高24清零,同时将源寄存 器1中的字节数据传送到源寄存器2所指向的存储器中。显然,当源寄存器1和目的寄存器为同一个寄存器时,指令交换该寄存器和存储器的内容。
    指令示例:
    SWPB R0,R1,[R2] ;将R2所指向的存储器中的字节数据传送到 R0,R0的高24位清零,同时将R1中的低8位数据传送到R2所指向的存储单元。

     

    SWPB R0,R0,[R1] ;该指令完成将R1所指向的存储器中的 字节数据与R0中的低8位数据交换。

     

     

    八、移位指令

     

    1、LSL(或ASL)


    LSL(或ASL)的格式为:
    通用寄存器,LSL(或ASL) 操作数 
    LSL(或ASL)可完成对通用寄存器中的内容进行逻辑(或算术)的左移操作,按操作数所指定的数量向左移位,低位用零来填充。 其中,操作数可以是通用寄存器,也可以是立即数(0~31)。
    操作示例
    MOV R0, R1, LSL #2 ;将R1中的内容左移两位后传送到R0 中。

     

     

     

    2、LSR


    LSR的格式为:
    通用寄存器,LSR 操作数 
    LSR可完成对通用寄存器中的内容进行右移的操作,按操作数所指定的数量向右移位,左端用零来填充。其中,操作数可以 是通用寄存器,也可以是立即数(0~31)。
    操作示例: 
    MOV R0, R1, LSR #2 ;将R1中的内容右移两位后传送到R0 中,左端用零来填充。

     

     

     

     

    3、ASR


    ASR的格式为:
    通用寄存器,ASR 操作数 
    ASR可完成对通用寄存器中的内容进行右移的操作,按操作数所指定的数量向右移位,左端用第31位的值来填充。其中,操作数可以是通用寄存器,也可以是立 即数(0~31)。
    操作示例:
    MOV R0, R1, ASR #2 ;将R1中的内容右移两位后传送到R0 中,左端用第31位的值来填充。

     

     

     

     

    4、ROR


    ROR的格式为:
    通用寄存器,ROR 操作数 
    ROR可完成对通用寄存器中的内容进行循环右移的操作,按操作数所指定的数量向右循环移位,左端用右端移出的位来填充。其中,操作数可以是通用寄存器,也 可以是立即数(0~31)。显然,当进行32位的循环右移操作时,通用寄存器中的值不改变。
    操作示例:
    MOV R0, R1, ROR #2 ;将R1中的内容循环右移两位后传送到R0 中。

     

     

     

     

    5、RRX


    RRX的格式为:
    通用寄存器,RRX 操作数 
    RRX可完成对通用寄存器中的内容进行带扩展的循环右移的操作,按操作数所指定的数量向右循环移位,左端用进位标志位C来填充。其中,操作数可以是通用寄 存器,也可以是立即数(0~31)。
    操作示例:
    MOV R0, R1, RRX #2 ;将R1中的内容进行带扩展的循环右移两位 后传送到R0中。

     

     

     

     

    九、协处理器指令

     

     

     

    1、CDP指令


    CDP指令的格式为:
    CDP{条件} 协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2。
    CDP指令用于ARM处理器通知ARM协处理器执行特定的操作,若协处理器不能成功完成特定的操作,则产生未定义指令异常。其中协处理器操作码1和协处理 器操作码2为协处理器将要执行的操作,目的寄存器和源寄存器均为协处理器的寄存器,指令不涉及ARM处理器的寄存器和存储器。
    指令示例:
    CDP P3,2,C12,C10,C3,4 ;该指令完成协处理器P3的初始化

     

     

     

     

    2、LDC指令


    LDC指令的格式为:
    LDC{条件}{L} 协处理器编码,目的寄存器,[源寄存器]
    LDC指令用于将源寄存器所指向的存储器中的字数据传送到目的寄存器中,若协处理器不能成功完成传送操作,则产生未定义指令异常。其中,{L}选项表示指 令为长读取操作,如用于双精度数据的传输。
    指令示例:
    LDC P3,C4,[R0] ;将ARM处理器的寄存器R0所指向的存储器中的字数 据传送到协处理器P3的寄存器C4中。

     

     

     

     

     


    3、STC指令


    STC指令的格式为:
    STC{条件}{L} 协处理器编码,源寄存器,[目的寄存器]
    STC指令用于将源寄存器中的字数据传送到目的寄存器所指向的存储器中,若协处理器不能成功完成传送操作,则产生未定义指令异常。其中,{L}选项表示指 令为长读取操作,如用于双精度数据的传输。
    指令示例:
    STC P3,C4,[R0] ;将协处理器P3的寄存器C4中的字数据传送到ARM处理 器的寄存器R0所指向的存储器中。

     

     

     

     


    4、MCR指令


    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中。

     

     

     

     

     

    5、MRC指令


    MRC指令的格式为:
    MRC{条件} 协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2。
    MRC指令用于将协处理器寄存器中的数据传送到ARM处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1和协处理 器操作码2为协处理器将要执行的操作,目的寄存器为ARM处理器的寄存器,源寄存器1和源寄存器2均为协处理器的寄存器。
    指令示例:
    MRC P3,3,R0,C4,C5,6 ;该指令将协处理器P3的寄存器中的数据传送到 ARM处理器寄存器中。

     

     

     

     

     

    十、异常产生指令

     

     

     

    1、SWI指令


    SWI指令的格式为:
    SWI{条件} 24位的立即数
    SWI指令用于产生软件中断,以便用户程序能调用操作系统的系统例程。操作系统在SWI的异常处理程序中提供相应的系统服务,指令中24位的立即数指定用 户程序调用系统例程的类型,相关参数通过通用寄存器传递,当指令中24位的立即数被忽略时,用户程序调用系统例程的类型由通用寄存器R0的内容决定,同 时,参数通过其他通用寄存器传递。 
    指令示例:
    SWI 0x02 ;该指令调用操作系统编号位02的系统例程。

     

     

     

    2、BKPT指令


    BKPT指令的格式为:
    BKPT 16位的立即数
    BKPT指令产生软件断点中断,可用于程序的调试。
    ARM汇编伪指令

    在ARM汇编语言程序里,有一些特殊指令助记符,这些助记符与指令系统的助记符不同,没有相对应的操作码,通常称这些特殊指令助记符为伪指令,他们所完成 的操作称为伪操作。伪指令在源程序中的作用是为完成汇编程序作各种准备工作的,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命就完 成。 
    在ARM 的汇编程序中,有如下几种伪指令:符号定义伪指令、数据定义伪指令、汇编控制伪指令、宏指令以及其他伪指令。

     

     

     

     

     

    一、符号定义(Symbol Definition)伪指令


    符号定义伪指令用于定义ARM 汇编程序中的变量、对变量赋值以及定义寄存器的别名等操作。 
    常见的符号定义伪指令有如下几种: 
    — 用于定义全局变量的GBLA 、GBLL 和GBLS 。 
    — 用于定义局部变量的LCLA 、LCLL 和LCLS 。 
    — 用于对变量赋值的SETA 、SETL 、SETS 。 
    — 为通用寄存器列表定义名称的RLIST 。

     

     

     

     

    1、GBLA、GBLL 和GBLS 

    语法格式: 
    GBLA (GBLL 或GBLS )全局变量名 
    GBLA 、GBLL 和GBLS 伪指令用于定义一个ARM 程序中的全局变量,并将其初始化。其中: 
    GBLA 伪指令用于定义一个全局的数字变量,并初始化为0 ; 
    GBLL 伪指令用于定义一个全局的逻辑变量,并初始化为F(假); 
    GBLS 伪指令用于定义一个全局的字符串变量,并初始化为空; 
    由于以上三条伪指令用于定义全局变量,因此在整个程序范围内变量名必须唯一。 
    使用示例: 
    GBLA Test1 ; 定义一个全局的数字变量,变量名为 Test1。 
    Test1 SETA 0xaa ; 将该变量赋值为0xaa。 
    GBLL Test2 ; 定义一个全局的逻辑变量,变量名为 Test2。 
    Test2 SETL {TRUE} ;将该变量赋值为真。 
    GBLS Test3 ; 定义一个全局的字符串变量,变量名为 Test3。 
    Test3 SETS “Testing” ;将该变量赋值为"Testing”。 

     

    2、LCLA、LCLL 和LCLS 

    语法格式: 
    LCLA (LCLL 或 LCLS )局部变量名 
    LCLA 、LCLL 和LCLS 伪指令用于定义一个ARM 程序中的局部变量,并将其初始化。其中: 
    LCLA伪指令用于定义一个局部的数字变量,并初始化为0 ; 
    LCLL伪指令用于定义一个局部的逻辑变量,并初始化为F(假); 
    LCLS伪指令用于定义一个局部的字符串变量,并初始化为空; 
    以上三条伪指令用于声明局部变量,在其作用范围内变量名必须唯一。 
    使用示例: 
    LCLA Test4 ; 声明一个局部的数字变 量,变量名为Test4。 
    Test3 SETA 0xaa ; 将该变量赋值为0xaa。 
    LCLL Test5 ; 声明一个局部的逻辑变 量,变量名为Test5。 
    Test4 SETL {TRUE} ;将该变量赋值为真。 
    LCLS Test6 ; 定义一个局部的字 符串变量,变量名为Test6。 
    Test6 SETS “Testing” ;将该变量赋值为 "Testing”。 

     

    3、SETA、SETL 和SETS 

    语法格式: 
    变量名 SETA (SETL 或 SETS )表达式 
    伪指令 SETA 、SETL 、SETS 用于给一个已经定义的全局变量或局部变量赋值。 
    SETA伪指令用于给一个数学变量赋值; 
    SETL伪指令用于给一个逻辑变量赋值; 
    SETS伪指令用于给一个字符串变量赋值; 
    其中,变量名为已经定义过的全局变量或局部变量,表达式为将要赋给变量的值。 
    使用示例: 
    LCLA Test3 ; 声明一个局部的数字变量,变量名为 Test3。 
    Test3 SETA 0xaa ; 将该变量赋值为0xaa。 
    LCLL Test4 ; 声明一个局部的逻辑变量,变量名为 Test4。 
    Test4 SETL {TRUE} ;将该变量赋值为真。 

     

    4 、RLIST 

    语法格式: 
    名称 RLIST { 寄存器列表 } 
    RLIST伪指令可用于对一个通用寄存器列表定义名称,使用该伪指令定义的名称可在ARM 指令 LDM/STM中使用。在LDM/STM指令中,列表中的寄存器访问次序为根据寄存器的编号由低到高,而与列表中的寄存器排列次序无关。 
    使用示例: 
    RegList RLIST {R0-R5 ,R8 ,R10} ;将寄存器列表名称定义为 RegList ,可在ARM指令LDM/STM中通过该名称访问寄存器列表。

     

     

    二、数据定义(Data Definition)伪指令


    数据定义伪指令一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。 
    常见的数据定义伪指令有如下几种: 
    — DCB 用于分配一片连续的字节存储单元并用指定的数据初始化。 
    — DCW(DCWU)用于分配一片连续的半字存储单元并用指定的数据初始化。 
    — DCD (DCDU)用于分配一片连续的字存储单元并用指定的数据初始化。 
    — DCFD(DCFDU)用于为双精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。 
    — DCFS(DCFSU)用于为单精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。 
    — DCQ(DCQU)用于分配一片以8字节为单位的连续的存储单元并用指定的数据初始化。 
    — SPACE 用于分配一片连续的存储单元。 
    — MAP 用于定义一个结构化的内存表首地址。 
    — FIELD 用于定义一个结构化的内存表的数据域。

     

     

     

    1、DCB 

    语法格式: 
    标号 DCB 表达式 
    DCB伪指令用于分配一片连续的字节存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为0~255的数字或字符串。DCB 也可用“=”代替。 
    使用示例: 
    Str DCB “This is a test” ;分配一片连续的字节存储单元并初始化。 

     

    2、DCW(或DCWU) 

    语法格式: 
    标号 DCW (或DCWU) 表达式 
    DCW(或DCWU)伪指令用于分配一片连续的半字存储单元并用伪指令中指定的表达式初始化。 
    其中,表达式可以为程序标号或数字表达式。 
    用DCW分配的字存储单元是半字对齐的,而用DCWU分配的字存储单元并不严格半字对齐。 
    使用示例: 
    DataTest DCW 1 ,2 ,3 ;分配一片连续的半字存储单元并初始化。 

     

    3、DCD(或DCDU) 

    语法格式: 
    标号 DCD(或DCDU) 表达式 
    DCD(或DCDU)伪指令用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。DCD也可 用"&” 代替。 
    用DCD分配的字存储单元是字对齐的,而用DCDU分配的字存储单元并不严格字对齐。 
    使用示例: 
    DataTest DCD 4 ,5 ,6 ;分配一片连续的字存储单元并初始化。 

     


    4、DCFD(或DCFDU) 

    语法格式: 
    标号 DCFD(或DCFDU) 表达式 
    DCFD(或DCFDU)伪指令用于为双精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。每个双精度的浮点数占据两个字单元。用 DCFD分配的字存储单元是字对齐的,而用DCFDU分配的字存储单元并不严格字对齐。 
    使用示例: FDataTest DCFD 2E115 ,-5E7 ;分配一片连续的字存储单元并初始化 为指定的双精度数。 

     

    5、DCFS(或DCFSU) 

    语法格式: 
    标号 DCFS(或DCFSU) 表达式 
    DCFS(或DCFSU)伪指令用于为单精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。每个单精度的浮点数占据一个字单元。用 DCFS分配的字存储单元是字对齐的,而用DCFSU分配的字存储单元并不严格字对齐。 
    使用示例: 
    FDataTest DCFS 2E5 ,-5E -7 ;分配一片连续的字存储单元并初始化为 指定的单精度数。 

     

    6、DCQ(或DCQU) 

    语法格式: 
    标号 DCQ(或DCQU) 表达式 
    DCQ(或DCQU)伪指令用于分配一片以8个字节(双字)为单位的连续存储区域并用伪指令中指定的表达式 初始化。 用DCQ分配的存储单元是字对齐的,而用DCQU 分配的存储单元并不严格字对齐。 
    使用示例: 
    DataTest DCQ 100 ;分配一片连续的存储单元并初始化为指定的值。 

     

    7、SPACE 

    语法格式: 
    标号 SPACE 表达式 
    SPACE伪指令用于分配一片连续的存储区域并初始化为0 。其中,表达式为要分配的字节数。 
    SPACE也可用“ % ”代替。 
    使用示例: 
    DataSpace SPACE 100 ;分配连续100字节的存储单元并初始化为0 。 

     

    8、MAP 

    语法格式: 
    MAP 表达式 { ,基址寄存器 } 
    MAP伪指令用于定义一个结构化的内存表的首地址。MAP也可用“^” 代替。 
    表达式可以为程序中的标号或数学表达式,基址寄存器为可选项,当基址寄存器选项不存在时,表达式的值即为内存表的首地址,当该选项存在时,内存表的首地址 为表达式的值与基址寄存器的和。 
    MAP伪指令通常与FIELD伪指令配合使用来定义结构化的内存表。 
    使用示例: 
    MAP 0x100 ,R0 ;定义结构化内存表首地址的值为0x100+R0 。 

     

    9、FILED 

    语法格式: 
    标号 FIELD 表达式 
    FIELD伪指令用于定义一个结构化内存表中的数据域。FILED 也可用“#” 代替。 
    表达式的值为当前数据域在内存表中所占的字节数。 
    FIELD伪指令常与MAP伪指令配合使用来定义结构化的内存表。MAP伪指令定义内存表的首地址,FIELD伪指令定义内存表中的各个数据域,并可以为 每个数据域指定一个标号供其他的指令引用。 
    注意MAP和FIELD伪指令仅用于定义数据结构,并不实际分配存储单元。 
    使用示例: 
    MAP 0x100 ; 定义结构化内存表首地址的值为0x100。 
    A FIELD 16 ; 定义A的长度为16字节,位置为0x100。 
    B FIELD 32 ; 定义B的长度为32字节,位置为0x110。 
    S FIELD 256 ;定义S的长度为256字节,位置为0x130。

     

    三、汇编控制(Assembly Control)伪指令 

    汇编控制伪指令用于控制汇编程序的执行流程,常用的汇编控制伪指令包括以下几条: 
    — IF 、ELSE 、ENDIF 
    — WHILE 、WEND 
    — MACRO 、MEND 
    — MEXIT

     


     

    1、IF、ELSE、ENDIF 

    语法格式: 
    IF 逻辑表达式 
    指令序列 1 
    ELSE 
    指令序列 2 
    ENDIF 
    IF 、ELSE 、ENDIF伪指令能根据条件的成立与否决定是否执行某个指令序列。当IF后面的逻辑表达式为真,则执行指令序列1 ,否则执行指令序列2 。其中,ELSE及指令序列2可以没有,此时,当IF后面的逻辑表达式为真,则执行指令序列1 ,否则继续执行后面的指令。 
    IF 、ELSE 、ENDIF伪指令可以嵌套使用。 
    使用示例: 
    GBLL Test ;声明一个全局的逻辑变量,变量名为Test 
    IF Test = TRUE 
    指令序列 1 
    ELSE 
    指令序列 2 
    ENDIF 

     

    2、WHILE、WEND 

    语法格式: 
    WHILE 逻辑表达式 
    指令序列 
    WEND 
    WHILE 、WEND伪指令能根据条件的成立与否决定是否循环执行某个指令序列。当WHILE后面的逻辑表达式为真,则执行指令序列,该指令序列执行完毕后,再判断 逻辑表达式的值,若为真则继续执行,一直到逻辑表达式的值为假。 
    WHILE 、WEND伪指令可以嵌套使用。 
    使用示例: 
    GBLA Counter ; 声明一个全局的数学变量,变量名为Counter 
    Counter SETA 3 ;由变量Counter 控制循环次数 
    …… 
    WHILE Counter < 10 
    指令序列 
    WEND

     


    3、MACRO、MEND 

    语法格式: 
    $ 标号 宏名 $ 参数 1 ,$ 参数 2 ,…… 
    指令序列 
    MEND 
    MACRO 、MEND伪指令可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。其中,$标号在宏指令被展开时,标号会被替 换为用户定义的符号,宏指令可以使用一个或多个参数,当宏指令被展开时,这些参数被相应的值替换。 
    宏指令的使用方式和功能与子程序有些相似,子程序可以提供模块化的程序设计、节省存储空间并提高运行速度。但在使用子程序结构时需要保护现场,从而增加了 系统的开销,因此,在代码较短且需要传递的参数较多时,可以使用宏指令代替子程序。 
    包含在MACRO和MEND之间的指令序列称为宏定义体,在宏定义体的第一行应声明宏的原型(包含宏名、所需的参数),然后就可以在汇编程序中通过宏名来 调用该指令序列。在源程序被编译时,汇编器将宏调用展开,用宏定义中的指令序列代替程序中的宏调用,并将实际参数的值传递给宏定义中的形式参数。 
    MACRO、MEND伪指令可以嵌套使用。 

     

    4、MEXIT 

    语法格式: 
    MEXIT 
    MEXIT用于从宏定义中跳转出去。

     

     

    四、其他常用的伪指令


    还有一些其他的伪指令,在汇编程序中经常会被使用,包括以下几条: 
    — AREA
    — ALIGN 
    — CODE16 、CODE32 
    — ENTRY 
    — END 
    — EQU 
    — EXPORT(或GLOBAL ) 
    — IMPORT 
    — EXTERN 
    — GET(或INCLUDE ) 
    — INCBIN 
    — RN 
    — ROUT

     

     

     

    1、AREA 

    语法格式: 
    AREA 段名 属性1 ,属性2 ,…… 
    AREA伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用“|”括起来,如:|1_test| 。
    属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下: 
    — CODE 属性:用于定义代码段,默认为READONLY 。 
    — DATA 属性:用于定义数据段,默认为READWRITE 。 
    — READONLY 属性:指定本段为只读,代码段默认为READONLY 。 
    — READWRITE 属性:指定本段为可读可写,数据段的默认属性为READWRITE 。 
    — ALIGN 属性:使用方式为ALIGN表达式。在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为0~31,相应的对齐方式为2 表达式次方。 
    — COMMON 属性:该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的COMMON段共享同一段存储单元。 
    一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。 
    使用示例: 
    AREA Init ,CODE ,READONLY ; 该伪指令定义了一个代码段,段 名为Init ,属性为只读。 

     

    2、ALIGN 

    语法格式: 
    ALIGN { 表达式 { ,偏移量 }} 
    ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对齐方式。其中,表达式的值用于指定对齐方式,可能的取值为2的幂,如1 、2 、4 、8 、16 等。若未指定表达式,则将当前位置对齐到下一个字的位置。偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移 量。 
    使用示例: 
    AREA Init ,CODE ,READONLY ,ALIEN=3 ;指定后面的指令为8 字节对齐。 
    指令序列 
    END 

     

    3、CODE16、CODE32 

    语法格式: 
    CODE16(或CODE32) 
    CODE16伪指令通知编译器,其后的指令序列为16位的Thumb指令。 
    CODE32伪指令通知编译器,其后的指令序列为32位的ARM指令。 
    若在汇编源程序中同时包含ARM指令和Thumb指令时,可用CODE16伪指令通知编译器其后的指令序列为16位的Thumb指令,CODE32伪指令 通知编译器其后的指令序列为32位的ARM指令。因此,在使用ARM指令和Thumb指令混合编程的代码里,可用这两条伪指令进行切换,但注意他们只通知 编译器其后指令的类型,并不能对处理器进行状态的切换。 
    使用示例: 
    AREA Init ,CODE ,READONLY …… 
    CODE32 ; 通知编译器其后的指令为32位的 ARM指令 
    LDR R0 ,=NEXT+1 ;将跳转地址放入寄存器R0 
    BX R0 ; 程序跳转到新的位置执行, 并将处理器切换到Thumb工作状态 
    …… 
    CODE16 ; 通知编译器其后的指令为16位的 Thumb指令 
    NEXT LDR R3,=0x3FF 
    …… 
    END ;

     


    4、ENTRY 

    语法格式: 
    ENTRY 
    ENTRY伪指令用于指定汇编程序的入口点。在一个完整的汇编程序中至少要有一个ENTRY(也可以有多个,当有多个ENTRY时,程序的真正入口点由链 接器指定),但在一个源文件里最多只能有一个ENTRY(可以没有)。 
    使用示例: 
    AREA Init ,CODE ,READONLY 
    ENTRY ; 指定应用程序的入口点 

     

    …… 
    5、END 

    语法格式: 
    END 
    END伪指令用于通知编译器已经到了源程序的结尾。 
    使用示例: 
    AREA Init ,CODE ,READONLY 
    …… 
    END ;指定应用程序的结尾 

     


    6、EQU 

    语法格式: 
    名称 EQU 表达式 { ,类型 } 
    EQU伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define 。其中EQU可用“*”代替。名称为EQU伪指令定义的字符名称,当表达式为32位的常量时,可以指定 表达式的数据类型,可以有以下三种类型: 
    CODE16 、CODE32 和DATA 
    使用示例: 
    Test EQU 50 ; 定义标号Test 的值为50。 
    Addr EQU 0x55 ,CODE32 ; 定义Addr的值为0x55 ,且该处为32位的ARM指令。 

     


    7、EXPORT(或GLOBAL) 

    语法格式: 
    EXPORT 标号 {[WEAK]} 
    EXPORT伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。EXPORT 可用GLOBAL代替。标号在程序中区分大小写,[WEAK] 选项声明其他的同名标号优先于该标号被引用。 
    使用示例: 
    AREA Init ,CODE ,READONLY 
    EXPORT Stest ;声明一个可全局引用的标号Stest 
    END

     


    8、IMPORT 

    语法格式: 
    IMPORT 标号 {[WEAK]} 
    IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。标 号在程序中区分大小写,[WEAK] 选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为0 ,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。 
    使用示例: 
    AREA Init ,CODE ,READONLY 
    IMPORT Main ;通知编译器当前文件要引用标号Main,但Main在其他源文件中定 义。 
    END 

     


    9、EXTERN 

    语法格式: 
    EXTERN 标号 {[WEAK]} 
    EXTERN伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,如果当前源文件实际并未引用该标号,该 标号就不会被加入到当前源文件的符号表中。标号在程序中区分大小写, [WEAK] 选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为0 ,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。 
    使用示例: 
    AREA Init ,CODE ,READONLY 
    EXTERN Main ;通知编译器当前文件要引用标号Main,但Main在其他源文件中定 义。 
    END 

     


    10、GET(或INCLUDE) 

    语法格式: 
    GET 文件名 
    GET伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。可 以使用INCLUDE代替GET。 
    汇编程序中常用的方法是在某源文件中定义一些宏指令,用EQU定义常量的符号名称,用MAP和FIELD定义结构化的数据类型,然后用GET伪指令将这个 源文件包含到其他的源文件中。使用方法与C 语言中的"include” 相似。
    GET伪指令只能用于包含源文件,包含目标文件需要使用INCBIN伪指令 
    使用示例: 
    AREA Init ,CODE ,READONLY 
    GET a1.s ; 通知编译器当前源文件包含源文件a1.s 
    GET C:\a2.s ; 通知编译器当前源文件包含源文件C:\a2.s 
    END

     


    11、INCBIN 

    语法格式: 
    INCBIN 文件名 
    INCBIN伪指令用于将一个目标文件或数据文件包含到当前的源文件中,被包含的文件不作任何变动的存放在当前文件中,编译器从其后开始继续处理。 
    使用示例: 
    AREA Init ,CODE ,READONLY 
    INCBIN a1.dat ; 通知编译器当前源文件包含文件a1.dat 
    INCBIN C:\a2.txt ;通知编译器当前源文件包含文件C:\a2.txt 
    END 

     


    12、RN 

    语法格式: 
    名称 RN 表达式 
    RN伪指令用于给一个寄存器定义一个别名。采用这种方式可以方便程序员记忆该寄存器的功能。其中,名称为给寄存器定义的别名,表达式为寄存器的编码。 
    使用示例: 
    Temp RN R0 ;将R0定义一个别名Temp 

     


    13、ROUT 

    语法格式: 
    {名称} ROUT 
    ROUT伪指令用于给一个局部变量定义作用范围。在程序中未使用该伪指令时,局部 变量的作用范围为所在的AREA,而使用ROUT后,局部变量的作为范围为当前ROUT和下一个ROUT之间。

     

    14. BNE 与 BEQ

    TST R0, #0X8
    BNE SuspendUp ;BNE指令是“不相等(或不为0)跳转指令”: 
    LDR R1,#0x00000000
    先进行and运算,如果R0的第四位不为1,则结果为零,则设置zero=1(继续下面的LDR指令);
    否则,如果R0的第四位为1,zero=0(跳到SuspendUp处执行)。
    tst 和bne连用: 先是用tst进行位与运算,然后将位与的结果与0比较,如果不为0,则跳到bne紧跟着的标记(如bne sleep,则跳到sleep处)。
    tst 和beq连用: 先是用tst进行位与运算,然后将位与的结果与0比较,如果为0,则跳到beq紧跟着的标记(如bne AAAA,则跳到AAAA处)

     

     

     

    展开全文
  • 电脑使用过程中“xx指令引用的xx内存。该内存不能written或read”的解决方案!
  • 内存不能“read”或“written”的解决方案 有些人运行程序的时候会弹出该内存不能“read”的错误提示。希望以下文章能对大家有所帮助。 使用Windows操作系统的人有时会遇到这样的错误信息,运行某些程序的时候,...
  • 内存和IO访问

    千次阅读 2016-11-14 01:11:17
    内存和IO访问
  • C优化篇之优化内存访问

    千次阅读 2014-02-01 21:55:21
    2)调整代码使程序集中顺序地访问内存。 一、减少内存访问的措施包括: a.充分利用寄存器  充分利用寄存器缓存数据,是减少内存访问的思路之一。C程序编译后哪些元素由寄存器存储,哪些又会放进内存,取决于CPU以及
  • 内存中对象的创建、对象的结构以及访问方式。 一、对象的创建 在语言层面上,对象的创建只不过是一个new关键字而已,那么在虚拟机中又是一个怎样的过程呢? (一)判断类是否加载。虚拟机遇到一条new指令的时候,...
  • C For Linux之内存访问-内存简介

    千次阅读 2013-01-17 14:31:27
    1.1、 计算机为什么需要内存 存储器是计算机系统中非常重要的组成部分。计算机中的存储器分为两类:内存储器的外存储器(也叫辅助存储器)。 所谓外存储器在PC机中一般指硬盘、U盘、光盘等,而在嵌入式系统中...
  • 实现Java程序在各种平台下都能达到一致的内存访问效果,具体体现在其在程序中遵循的各种变量(包含实例字段、静态字段、不变字段和数组对象)的访问规则(可以理解将变量从内存中取出和存储到内存的底层细节)。...
  • 内存不能“read”或“written”的解决方案  有些人运行程序的时候会弹出该内存不能“read”的错误提示。希望以下文章能对你有所帮助。  使用Windows操作系统的人有时会遇到这样的错误信息,运行某些程序的...
  • 寄存器比内存访问速度快的原因

    千次阅读 2016-10-13 17:29:08
    CPU访问内存的过程中会包含访问寄存器的操作。 以上是看完文章后的个人理解,放上原文如下。http://www.cnblogs.com/lcw/p/3371877.html 【系统】寄存器比内存访问速度快的原因 计算机的存储层次 ...
  • 那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7,4(int) + 2(short) + 1(char) = 7 。经过内存对齐后,结构体的空间反而增大了。 在解释内存对齐的作用前,...
  • 2、防止指令重排   此外需注意volatile并不保证操作的原子性。 (一)内存可见性 1 概念  JVM内存模型:主内存和线程独立的工作内存 Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个...
  • JAVA虚拟机(JVM)划重点 第章 Java内存区域与内存溢出异常 之 虚拟机对象Java对象的创建 Java对象的创建 1、类加载过程 虚拟机遇到一个new指令时,首先检查这个指令的参数能否在常量池中定位到一个类的符号...
  • 寄存器(内存访问)---汇编学习笔记

    千次阅读 2018-07-21 14:38:03
    寄存器(内存访问) 序言 第章,我们主要从CPU如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关的寄存器...这一章,我们从访问内存的角度继续学习几个寄存器。 3.1 内存中字的存储...
  • 一、内存为什么要对齐 虽然所有的变量都是保存在特定地址的内存中,但最好还是按照内存对齐的要求来存储。这主要出于两个方面的原因考虑: 平台原因: 不是所有的硬件平台(特别是嵌入式系统中使用的低端处理器)...
  • 2)程序计数器为什么是线程私有的? 3)程序计数器特点 2、Java虚拟机栈:1)Java虚拟机栈特点 、2)局部变量表、3)栈帧、4)动态连接 3、本地方法栈 4、Java堆 5、方法区 6、运行时常量池 7、直接内存 、...
  • 类加载检查 普通对象的创建过程:虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那么必须...
  • 在使用动态分配的应用程序中,有时会有这样的情况出现:程序试图读写一块“应该可用”的内存,但不知为什么,这个预料中可用的指针已经失效了。有可能是“忘记了”向操作系统要求分配,也可能是程序自己在某个时候...
  • 内存与I/O访问

    千次阅读 2012-04-06 11:20:57
    本章节带大家一起来探讨一下Linux驱动中的内存与I/O访问 CPU与内核和I/O I/O空间:在X86处理器中存在着I/O空间的概念,I/O空间是相对于内存空间而言的。 它通过特定的指令in、out来访问 指令格式:IN 累加器...
  • 目录 填空题: 选择题: 简答题: ... 1. 在动态分区式内存分配算法中,倾向于... 在请求调页系统中的调页策略有 预调页策略,它是以预测基础的;另一种是 请求调页策略 由于较易实现,故目前使用较多。 3. ...
  • 「“0X00000000”指令引用的“0x00000000”内存,该内存不能“read”或“written”」,然后应用程序被关闭。 如果去请教一些「高手」,得到的回答往往是「Windows就是这样不稳定」之类的义愤和不屑。其实,这...
  • 在C语言中,指针变量的值就是一个内存地址,&运算符的作用也是取变量的内存地址,请看下面的代码: #include #include int a = 1, b = 255;int main(){ int *pa = &a; printf("pa = %#X, &b = %#X\n", pa, &b);...
  • 纯DOS下内存的管理—实模式下访问4GB内存DOS操作系统最早设计时,PC机的硬件系统只支持1M字节的寻址空间,所以DOS只能管理最多1M字节的连续内存空间。在这1M内存中,又只有640K被留给应用程序使用,它们被称为常规...
  • DMA(direct memory access)直接内存访问

    千次阅读 2017-07-26 19:21:54
    这里的关键词在 Direct (直接),与传统的相对低效的,需要通过 CPU 来访问内存(此 indirect,间接)的方式相对。 1. 基本概念 辅存狭义上是平时讲的硬盘,准确地说,是外部存储器(需要通过 I/O 系统与之交换...
  • 全面理解Java内存模型

    万次阅读 多人点赞 2016-09-21 18:39:21
    Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。如果我们要想深入了解Java并发编程,就要先理解好Java内存...
  • 禁止指令重排优化 高效并发的原则 可见性有序性和原子性 先行发生Happens-Before这部分内容,跟并发有关我们知道,多任务处理,在现代操作系统几乎是必备功能。让计算机同时去做几件事情,不仅因为CPU运算能力太强大

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 204,056
精华内容 81,622
关键字:

为什么指令二次访问内存