王爽著的《汇编语言》(第3版)于2013年出版,虽然是2013年出版的,但书中部分内容感觉已过时:
(1). 基于intel 8086 CPU介绍,intel 8086是英特尔公司上个世纪生产的芯片,是16位的,早已停产;
(2). 现在PC机上的intel CPU都是intel core i5, i7等,大部分都是64位的,书中介绍的代码段在现在的PC机上基于vs根本无法编译,而且有些语法在现代汇编语言中应该也不在支持。
以下是对书中内容的摘记:注:基于intel 8086 CPU
1. 基础知识:汇编语言是直接在硬件之上工作的编程语言。
1.1 机器语言:是机器指令的集合。机器指令展开来讲就是一台机器可以正确执行的命令。电子计算机的机器指令是一列二进制数字。计算机将之转变为一列高低电平,以使计算机的电子器件受到驱动,进行运算。
上面所说的计算机指的是可以执行机器指令,进行运算的机器。现在,在PC机中,有一个芯片来完成上面所说的计算机的功能。这个芯片就是CPU(Central Processing Unit, 中央处理单元),CPU是一种微处理器。
每一种微处理器,由于硬件设计和内部结构的不同,就需要用不同的电平脉冲来控制,使它工作。所以每一种微处理器都有自己的机器指令集,也就是机器语言。
早期的程序设计均使用机器语言。程序员们将用0、1数字编程的程序代码打在纸带或卡片上,1打孔,0不打孔,再将程序通过纸带机或卡片机输入计算机,进行运算。
1.2 汇编语言的产生:汇编语言的主体是汇编指令。汇编指令和机器指令的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式。例如:机器指令1000100111011000表示把寄存器BX的内容送到AX中。汇编指令则写成mov ax,bx。
寄存器,简单地讲是CPU中可以存储数据的器件,一个CPU中有多个寄存器。AX是其中一个寄存器的代号,BX是另一个寄存器的代号。
计算机能读懂的只有机器指令,那么如何让计算机执行程序员用汇编指令编写的程序呢?这时,就需要有一个能够将汇编指令转换成机器指令的翻译程序,这样的程序我们称其为编译器。程序员用汇编语言写出源程序,再用汇编编译器将其编译为机器码,由计算机最终执行。其工作过程如下图所示:
1.3 汇编语言的组成:汇编语言发展至今,有以下3类指令组成:
(1).汇编指令:机器码的助记符,有对应的机器码。
(2).伪指令:没有对应的机器码,由编译器执行,计算机并不执行。
(3).其它符号:如+、-、*、/等,由编译器识别,没有对应的机器码。
汇编语言的核心是汇编指令,它决定了汇编语言的特性。
1.4 存储器:CPU是计算机的核心部件,它控制整个计算机的运作并进行计算。要想让一个CPU工作,就必须向它提供指令和数据。指令和数据在存储器中存放,也就是我们所说的内存。磁盘不同于内存,磁盘上的数据或程序如果不读到内存中,就无法被CPU使用。
1.5 指令和数据:在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。CPU在工作的时候把有的信息看作指令,有的信息看作数据,为同样的信息赋予了不同的意义。
1.6 存储单元:存储器被划分成若干个存储单元,每个存储单元从0开始顺序编号。电子计算机的最小信息单位是bit,也就是一个二进制位。8个bit组成一个Byte。微型机存储器的存储单元可以存储一个Byte,即8个二进制位。微机存储器的容量是以字节为最小单位来计算的。对于大容量的存储器一般还用以下单位来计量容量:1KB、1MB、1GB、1TB。
1.7 CPU对存储器的读写:存储器被划分成多个存储单元,存储单元从零开始顺序编号。这些编号可以看作存储单元在存储器中的地址。CPU要从内存中读数据,首先要指定存储单元的地址。也就是说它要先确定它要读取哪一个存储单元中的数据。另外,在一台微机中,不只有存储器这一种器件。CPU在读写数据时还要指明,它要对哪一个器件进行操作,进行哪种操作,是从中读出数据,还是向里面写入数据。
可见,CPU要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行下面3类信息的交互:存储单元的地址(地址信息);器件的选择,读或写的命令(控制信息);读或写的数据(数据信息)。
CPU是通过导线将地址、数据和控制信息传到存储芯片中。在计算机中专门有连接CPU和其它芯片的导线,通常称为总线。总线从物理上来讲,就是一根根导线的集合。根据传送信息的不同,总线从逻辑上又分为3类,地址总线、控制总线和数据总线。
1.8 地址总线:CPU是通过地址总线来指定存储器单元的。可见地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。
一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。
1.9 数据总线:CPU与内存或其它器件之间的数据传送是通过数据总线来进行的。数据总线的宽度决定了CPU和外界的数据传送速度。8根数据总线一次可传送一个8位二进制数据(即一个字节)。16根数据总线一次可传送两个字节。
1.10 控制总线:CPU对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合。有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。所以,控制总线的宽度决定了CPU对外部器件的控制能力。
1.11 内存地址空间(概述):举例来讲,一个CPU的地址总线的宽度为10,那么可以寻址1024个(2^10)内存单元,这1024个可寻到的内存单元就构成这个CPU的内存地址空间。
1.12 主板:在每一台PC机中,都有一个主板,主板上有核心器件和一些主要器件,这些器件通过总线(地址总线、数据总线、控制总线)相连。这些器件有CPU、存储器、外围芯片组、扩展插槽等。扩展插槽上一般插有RAM内存条和各类接口卡。
1.13 接口卡:计算机系统中,所有可用程序控制其工作的设备,必须受到CPU的控制。CPU对外部设备都不能直接控制,如显示器、音响、打印机等。直接控制这些设备进行工作的是插在扩展插槽上的接口卡。扩展插槽通过总线和CPU相连,所以接口卡也通过总线同CPU相连。CPU可以直接控制这些接口卡,从而实现CPU对外设的间接控制。简单地讲,就是CPU通过总线向接口卡发送命令,接口卡根据CPU的命令控制外设进行工作。
1.14 各类存储器芯片:一台PC机中,装有多个存储器芯片,这些存储器芯片从物理连接上看是独立的、不同的器件。从读写属性上看分为两类:随机存储器(RAM)和只读存储器(ROM)。随机存储器可读可写,但必须带电存储,关机后存储的内容丢失;只读存储器只能读取不能写入,关机后其中的内容不丢失。这些存储器从功能和连接上又可分为以下几类:
(1).随机存储器:用于存放CPU使用的绝大部分程序和数据,主随机存储器一般由两个位置上的RAM组成,装在主板上RAM和插在扩展槽上的RAM。
(2).装有BIOS(Basic Input/Output System, 基本输入/输出系统)的ROM:BIOS是由主板和各类接口卡(如显卡、网卡等)厂商提供的软件系统,可以通过它利用该硬件设备进行最基本的输入输出。在主板和某些接口卡上插有存储相应BIOS的ROM。例如,主板上的ROM中存储着主板的BIOS(通常称为系统BIOS);显卡上的ROM中存储着显卡的BIOS;如果网卡上装有ROM,那其中就可以存储网卡的BIOS。
(3).接口卡上的RAM:某些接口卡需要对大批量输入、输出数据进行暂时存储,在其上装有RAM。最典型的是显示卡上的RAM,一般称为显存。显示卡随时将显存中的数据向显示器上输出。换句话说,我们将需要显示的内容写入显存,就会出现在显示器上。
1.15 内存地址空间:各类存储器芯片,在物理上是独立的器件,但是在以下两点上相同:都和CPU的总线相连;CPU对它们进行读或写的时候都通过控制线发出内存读写命令。这也就是说,CPU在操作它们的时候,把它们都当作内存来对待,把它们总的看作一个由若干存储单元组成的逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间。在汇编中,我们所面对的是内存地址空间。如下图所示:所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
内存地址空间的大小受CPU地址总线宽度的限制。8086CPU的地址总线宽度为20,可以传送2^20个不同的地址信息(大小从0至2^20-1)。即可以定位2^20个内存单元,则8086PC的内存地址空间大小为1MB。同理,80386CPU的地址总线宽度为32,则内存地址空间最大为4GB。不同的计算机系统的内存地址空间的分配情况是不同的。
小结:
(1).汇编指令是机器指令的助记符,同机器指令一一对应。
(2).每一种CPU都有自己的汇编指令集。
(3).CPU可以直接使用的信息在存储器中存放。
(4).在存储器中指令和数据没有任何区别,都是二进制信息。
(5).存储单元从零开始顺序编号。
(6).一个存储单元可以存储8个bit,即8位二进制数。
(7).1Byte=8bit; 1KB=1024Byte; 1MB=1024KB; 1GB=1024MB。
(8).每一个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出3种总线的宽度标志了这个CPU的不同方面的性能:地址总线的宽度决定了CPU的寻址能力;数据总线的宽度决定了CPU与其它器件进行数据传送时的一次数据传送量;控制总线的宽度决定了CPU对系统中其它器件的控制能力。
2. 寄存器
一个典型的CPU由运算器、控制器、寄存器等器件构成,这些器件靠内部总线相连。前一章所说的总线,相对于CPU内部来说是外部总线。内部总线实现CPU内部各个器件之间的联系,外部总线实现CPU和主板上其它器件的联系。简单地说,在CPU中,运算器进行信息处理;寄存器进行信息存储;控制器控制各种器件进行工作;内部总线连接各种器件,在它们之间进行数据的传送。
寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器中的内容来实现对CPU的控制。不同的CPU,寄存器的个数、结构是不相同的。8086CPU有14个寄存器,每个寄存器有一个名称。这些寄存器是:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。
2.1 通用寄存器:
8086CPU的所有寄存器都是16位的,可以存放两个字节。AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,被称为通用寄存器。一个16位寄存器可以存储一个16位的数据。这4个通用寄存器都可分为两个可独立使用的8位寄存器来用:AX可分为AH和AL;BX可分为BH和BL;CX可分为CH和CL;DX可分为DH和DL。
2.2 字在寄存器中的存储:
出于对兼容性的考虑,8086CPU可以一次性处理以下两种尺寸的数据:
(1).字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
(2).字:记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节。
2.3 几条汇编指令:通过汇编指令控制CPU进行工作。
在写一条汇编指令或一个寄存器的名称时不区分大小写。
在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的。
2.4 物理地址:CPU访问内存单元时,要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,我们将这个唯一的地址称为物理地址。
CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。在CPU向地址总线上发出物理地址之前,必须要在内部先形成这个物理地址。不同的CPU可以有不同的形成物理地址的方式。
2.5 16位结构的CPU:
概括地讲,16位结构描述了一个CPU具有下面几方面的结构特性:
(1).运算器一次最多可以处理16位的数据;
(2).寄存器的最大宽度为16位;
(3).寄存器和运算器之间的通路为16位。
8086是16位结构的CPU,这也就是说,在8086内部,能够一次性处理、传输、暂时存储的信息的最大长度是16位的。内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放,对于16位CPU,能一次性处理、传输、暂时存储16位的地址。
2.6 8086CPU给出物理地址的方法:
8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。8086CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。从8086CPU的内部结构来看,如果将地址从内部简单地发出,那么它只能送出16位的地址,表现出的寻址能力只有64KB。8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址,如下图所示:
当8086CPU要读写内存时:
(1).CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
(2).段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
(3).地址加法器将两个16位地址合成为一个20位的物理地址;
(4).地址加法器通过内部总线将20位物理地址送入输入输出控制电路;
(5).输入输出控制电路将20位物理地址送上地址总线;
(6).20位物理地址被地址总线传送到存储器。
地址加法器采用物理地址=段地址*16+偏移地址的方法用段地址和偏移地址合成物理地址。
2.7 “段地址*16+偏移地址=物理地址”的本质含义:CPU在访问内存时,用一个基础地址(段地址*16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。更一般地说,8086CPU的这种寻址功能是”基础地址+偏移地址=物理地址”寻址模式的一种具体实现方案。8086CPU中,段地址*16可看作是基础地址。
2.8 段的概念:其实,内存并没有分段,段的划分来自于CPU,由于8086CPU用”基础地址(段地址*16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用段的方式来管理内存。
CPU访问内存单元时,必须向内存提供内存单元的物理地址。8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。CPU可以用不同的段地址和偏移地址形成同一个物理地址。在8086PC机中,存储单元的地址用两个元素来描述,即段地址和偏移地址。
2.9 段寄存器:8086CPU在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址。段地址在8086CPU的段寄存器中存放。8086CPU有4个段寄存器:CS、DS、SS、ES。当8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。
2.10 CS和IP:是8086CPU中两个最关键的寄存器,它们指示了CPU当前要读取指令的地址。CS为代码段寄存器,IP为指令指针寄存器。
在8086CPU机中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU将从内存M*16+N单元开始,读取一条指令并执行。也可以这样表述:8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
8086CPU的工作过程可以简要描述如下:
(1).从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
(2).IP=IP+所读取指令的长度,从而指向下一条指令;
(3).执行指令。转到步骤(1),重复这个过程。
在8086CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS=FFFFH,IP=0000H,即在8086PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行,FFFF0H单元中的指令是8086PC机开机后执行的第一条指令。
在内存中,指令和数据没有任何区别,都是二进制信息,CPU在工作的时候把有的信息看作指令,有的信息看作数据。
2.11 修改CS、IP的指令:
在CPU中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的控制。CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通过改变CS、IP中的内容来控制CPU执行目标指令。
8086CPU大部分寄存器的值,都可以用mov指令来改变,mov指令被称为传送指令。但是,mov指令不能用于设置CS、IP的值,因为8086CPU没有提供这样的功能。能够改变CS、IP的内容的指令被统称为转移指令,如jmp指令。
若想同时修改CS、IP的内容,可用形如”jmp 段地址:偏移地址”的指令完成。此指令的功能为:用指令中给出的段地址修改CS,偏移地址修改IP。若想仅修改IP的内容,可用形如”jmp 某一合法寄存器”的指令完成,此指令的功能为:用寄存器中的值修改IP。
2.12 代码段:对于8086PC机,在编程时,可用根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N<=64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中,我们可以认为,这段内存是用来存放代码的,从而定义了一个代码段。
小结:
(1).段地址在8086CPU的寄存器中存放。当8086CPU要访问内存时,由段寄存器提供内存单元的段地址。8086CPU有4个段寄存器,其中CS用来存放指令的段地址。
(2).CS存放指令的段地址,IP存放指令的偏移地址。8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
(3).8086CPU的工作过程:
A.从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
B.IP指向下一条指令。
C.执行指令。(转到步骤A,重复这个过程。)
(4).8086CPU提供转移指令修改CS、IP的内容。
3. 寄存器(内存访问)
3.1 内存中字的存储:CPU中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。在内存中存储时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,这个字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。
字单元,即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。
3.2 DS和[address]:CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址组成。8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。
mov al,[0]:mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中,指令执行时,8086CPU会自动从ds中取出。8086CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器,所以”mov ds,1000H”这条指令是非法的。只能用一个寄存器来进行中转,即先将1000H送入一个一般的寄存器,如bx,再将bx中的内容送入ds。
3.3 字的传送:因为8086CPU是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是说可以一次性传送一个字。只要在mov指令中给出16位的寄存器就可以进行16位数据的传送了。
3.5 数据段:对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
3.6 栈:是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。栈有两个基本的操作:入栈和出栈。入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先从栈中取出。栈的这种操作规则被称为LIFO(Last In First Out, 后进先出)。
3.7 CPU提供的栈机制:8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,在基于8086CPU编程的时候,可以将一段内存当作栈来使用。
8086CPU提供入栈和出栈指令,最基本的两个是PUSH(入栈)和POP(出栈)。比如,”push ax”表示将寄存器ax中的数据送入栈中,”pop ax”表示从栈顶取出数据送入ax。8086CPU的入栈和出栈操作都是以字为单位进行的。
8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址。入栈时,栈顶从高地址向低地址方向增长。
3.8 栈顶超界的问题:8086CPU用SS和SP指示栈顶的地址,并提供push和pop指令实现入栈和出栈。
当栈满的时候再使用push指令入栈,或栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。8086CPU不保证我们对栈的操作不会越界,我们在编程的时候要自己操心栈顶超界的问题。
3.9 push、pop指令:指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。
push、pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令的访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。同时,push和pop指令还要改变SP中的内容。push和pop指令同mov指令不同,CPU执行mov指令只需一步操作,就是传送,而执行push、pop指令却需要两步操作:执行push时,CPU的两步操作是:先改变SP,后向SS:SP处传送。执行pop时,CPU的两步操作是:先读取SS:SP处的数据,后改变SP。注意,push、pop等栈操作指令,修改的只是SP。
3.10 栈段:对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N<=64KB)的一组地址连续、起始地址为16的倍数的内存单元,当作栈空间来用,从而定义了一个栈段。要将SS:SP指向我们定义的栈段。
任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的地址为栈最底部的字单元的地址+2。push、pop等指令在执行的时候只修改SP,所以栈顶的变化范围是0~FFFFH,从栈空的时候的SP=0,一直压栈,直到栈满时SP=0;如果再次压栈,栈顶将环绕,覆盖了原来栈中的内容。所以一个栈段的容量最大为64KB。
段的综述:我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。我们可以用一个段存放数据,将它定义为”数据段”;我们可以用一个段存放代码,将它定义为”代码段”;我们可以用一个段当作栈,将它定义为”栈段”。我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问。对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令。对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。可见,不管我们如何安排,CPU将内存中的某段内容当作代码,是因CS:IP指向了那里;CPU将某段内存当作栈,是因为SS:SP指向了那里。一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。关键在于CPU中寄存器的设置,即CS、IP、SS、SP、DS的指向。
小结:
(1).字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。
(2).用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。
(3).[address]表示一个偏移地址为address的内存单元。
(4).在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
(5).mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。
(6).8086CPU提供了栈操作机制,方案如下:在SS、SP中存放栈顶的段地址和偏移地址;提供入栈和出栈指令,它们根据SS:SP指示的地址,按照栈的方式访问内存单元。
(7).push指令的执行步骤:SP=SP-2;向SS:SP指向的字单元中送入数据。
(8).pop指令的执行步骤:从SS:SP指向的字单元中读取数据;SP=SP+2.
(9).任意时刻,SS:SP指向栈顶元素。
(10).8086CPU只记录栈顶,栈空间的大小我们要自己管理。
(11).用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。
(12).push、pop实质上是一种内存传送指令。
4. 第一个程序
4.1 一个源程序从写出到执行的过程:
(1):编写汇编源程序。
(2):对源程序进行编译链接:使用汇编语言编译程序对源程序文件中的源程序进行编译,产生目标文件;再用链接程序对目标文件进行链接,生成可在操作系统中直接运行的可执行文件。
(3):执行可执行文件中的程序。
4.2 源程序
伪指令:在汇编语言源程序中,包含两种指令,一种是汇编指令,一种是伪指令。汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。而伪指令没有对应的机器指令,最终不被CPU所执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。一个段必须有一个名称来标识。一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。一个有意义的汇编程序至少要有一个段,这个段用来存放代码。
end是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。
assume:这条伪指令的含义为”假设”。它假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联。通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。
标号:汇编源程序中,除了汇编指令和伪指令外,还有一些标号。一个标号指代了一个地址。
4.3 编辑源程序:可以用任意的文本编辑器来编辑源程序,只要最终将其存储为纯文本文件即可。
4.8 谁将可执行文件中的程序装载进入内存并使它运行?在DOS中运行一个程序的时候,是由command将程序从可执行文件中加载入内存,并使其得以执行。
5. [BX]和loop指令
注:使用一个描述性的符号”()”来表示一个寄存器或一个内存单元中的内容。
[bx]和[0]有些类似,[0]表示内存单元,它的偏移地址是0,段地址在ds中。[bx]同样也表示一个内存单元,它的偏移地址在bx中,段地址在ds中。
5.2 Loop指令:格式是:”loop 标号”,CPU执行loop指令的时候,要进行两步操作,(1).(cx)=(cx)-1;(2).判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。通常我们用loop指令来实现循环功能,cx中存放循环次数。在汇编语言中,标号代表一个地址。
用cx和loop指令相配合实现循环功能的3个要点:
(1).在cx中存放循环次数;
(2).loop指令中的标号所标识地址要在前面;
(3).要循环执行的程序段,要写在标号和loop指令的中间。
在汇编源程序中,数据不能以字母开头。
5.4 Debug和汇编编译器masm对指令的不同处理:
对于masm汇编编译器,在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用”[…]”来表示内存单元,如果在”[]”里用一个常量idata直接给出内存单元的偏移地址,就要在”[]”的前面显式地给出段地址所在的段寄存器。如果在”[]”里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中。当然,也可以显式地给出段地址所在的段寄存器。
5.6 段前缀:指令”mov ax,[bx]”中,内存单元的偏移地址由bx给出,而段地址默认在ds中。我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。比如:”mov ax,ds:[bx]”,这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的”ds:”, “cs:”, “ss:”, “es:”,在汇编语言中称为段前缀。
6. 包含多个段的程序
6.1 在代码段中使用数据:
end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。我们若要CPU从何处开始执行程序,只要在源程序中用”end 标号”指明就可以了。
在汇编源程序中,可以定义许多的段。
7. 更灵活的定位内存地址的方法
7.1 and和or指令:
and指令:逻辑与指令,按位进行与运算。通过该指令可将操作对象的相应位设为0,其它位不变。
or指令:逻辑或指令,按位进行或运算。通过该指令可将操作对象的相应位设为1,其它位不变。
7.3 以字符形式给出的数据:在汇编程序中,用’…’的方式指明数据是以字符的形式给出的,编译器将把它们转换为相对应的ASCII码。
7.5 [bx+idata]:表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata)。
7.7 SI和DI:si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。
7.8 [bx+si]和[bx+di]:它们的含义相似。[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)(即bx中的数值加上si中的数值)。
7.9 [bx+si+idata]和[bx+di+idata]:它们的含义相似。[bx+si+idata]表示一个内存单元,它的偏移地址为(bx)+(si)+idata(即bx中的数值加上si中的数值再加上idata)。
7.10 不同的寻址方式的灵活应用:一般来说,在需要暂存数据的时候,我们都应该使用栈。栈空间在内存中,采用相关的指令,如push、pop等,可对其进行特殊的操作。
8. 数据处理的两个基本问题
8.1 bx, si, di和bp:
(1).在8086CPU中,只有这4个寄存器可以用在”[…]”中来进行内存单元的寻址,如”mov ax, [bx+si]”。
(2).在[…]中,这4个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di,比如”mov ax, [bp+di+idata]”。
(3).只要在[…]中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中。
8.2 机器指令处理的数据在什么地方:CPU内部、内存、端口
8.3 汇编语言中数据位置的表达:
(1).立即数(idata):对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编语言中称为立即数,在汇编指令中直接给出,如”mov bx, 2000h”.
(2).寄存器:指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名,如”mov ax, bx”.
(3).段地址(SA)和偏移地址(EA):指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中。存放段地址的寄存器可以是默认的,比如:”mov ax, [bx]” 等指令,段地址默认在ds中;”mov ax, [bp]” 等指令,段地址默认在ss中。存放段地址的寄存器也可以是显性给出的,如:”mov ax, ds:[bp]”.
8.4 寻址方式:当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存单元的方法一般被称为寻址方式。8086CPU有多种寻址方式,总结如下图所示:
8.5 指令要处理的数据有多长:8086CPU的指令,可以处理两种尺寸的数据,byte和word。所以在机器指令中要指明,指令进行的是字操作还是字节操作。
(1).通过寄存器名指明要处理的数据的尺寸,如”mov bx, ds:[0]”,”mov al, bl”.
(2).在没有寄存器名存在的情况下,用操作符”X ptr”指明内存单元的长度,X在汇编指令中可以为word或byte,如”mov word ptr ds:[0], 1”,”inc byte ptr ds:[0]”.
(3).其它方法:有些指令默认了访问的是字单元还是字节单元,比如,”push [1000H]”就不用指明访问的是字单元还是字节单元,因为push指令只进行字操作。
8.7 div指令:除法,使用div做除法的时候应注意以下问题:
(1).除数:有8位和16位两种,在一个寄存器或内存单元中。
(2).被除数:默认放在AX或DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
(3).结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
8.8 伪指令dd:db和dw定义字节型数据和字型数据。dd是用来定义dword(double word, 双字)型数据的。
8.9 dup:是一个操作符,在汇编语言中同db、dw、dd等一样,也是由编译器识别处理的符号。它是和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复。
9. 转移指令的原理:
可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括地讲,转移指令就是可以控制CPU执行内存中某处代码的指令。
8086CPU的转移行为有以下几类:
(1).只修改IP时,称为段内转移,比如:”jmp ax”.
(2).同时修改CS和IP时,称为段间转移,比如:”jmp 1000:0”.
由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移:
(1).短转移IP的修改范围为-128~127.
(2).近转移IP的修改范围为-32768~32767.
8086CPU的转移指令分为以下几类:无条件转移指令(如jmp);条件转移指令;循环指令(如loop);过程;中断。
9.1 操作符offset:在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。
9.2 jmp指令:无条件转移指令,可以只修改IP,也可以同时修改CS和IP。jmp指令要给出两种信息:(1)转移的目的地址;(2)转移的距离(段间转移、段内短转移、段内近转移)。
9.3 依据位移进行转移的jmp指令:
jmp short 标号(转到标号处执行指令):这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为-128~127,也就是说,它向前转移时可以最多越过128个字节,向后转移可以最多越过127个字节。jmp指令中的”short”符号,说明指令进行的是短转移。jmp指令中的”标号”是代码段中的标号,指明了指令要转移的目的地,转移指令结束后,CS:IP应该指向标号处的指令。
jmp short 标号:(IP)=(IP)+8位位移,段内短转移。
jmp near ptr 标号:(IP)=(IP)+16位位移,段内近转移。
9.4 转移的目的地址在指令中的jmp指令:
jmp far ptr 标号:段间转移,又称为远转移。far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
9.6 转移地址在内存中的jmp指令:有两种格式:
(1).jmp word ptr 内存单元地址(段内转移):从内存单元地址处开始存放着一个字,是转移的目的偏移地址。内存单元地址可用寻址方式的任一格式给出。
(2).jmp dword ptr 内存单元地址(段间转移):从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。内存单元地址可用寻址方式的任一格式给出。
9.7 jcxz指令:为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128~127.如果(cx)=0,转移到标号处执行,当(cx)!=0时,什么也不做(程序向下执行)。
9.8 loop指令:为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128~127.
9.9 根据位移进行转移的意义:”jmp short 标号”, “jmp near ptr 标号”, “ jcxz 标号”, “loop 标号”等几种汇编指令,它们对IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。在它们对应的机器码中不包含转移的目的地址,而包含的是到目的地址的位移。
9.10 编译器对转移位移超界的检测:注意,根据位移进行转移的指令,它们的转移范围受到转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将报错。
10. CALL和RET指令:
call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。
10.1 ret和retf:ret指令用栈中的数据,修改IP的内容,从而实现近转移。retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。
10.2 call指令:CPU执行call指令时,进行两步操作:(1)将当前的IP或CS和IP压入栈中;(2)转移。call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同。
10.3 依据位移进行转移的call指令:”call 标号”:将当前的IP压栈后,转到标号处执行指令。
10.4 转移的目的地址在指令中的call指令:”call far ptr 标号”:实现的是段间转移。
10.5 转移地址在寄存器中的call指令:CPU执行”call 16位 寄存器”时,相当于进行:”push IP;jmp 16位 寄存器”。
10.6 转移地址在内存中的call指令:有两种格式:(1)”call word ptr 内存单元地址”:相当于进行”push IP; jmp word ptr 内存单元地址”;(2)”call dword ptr 内存单元地址”:相当于进行”push CS; push IP; jmp dword ptr 内存单元地址”。
10.7 call和ret的配合使用:call指令转去执行子程序之前,call指令后面的指令的地址将存储在栈中,所以可在子程序的后面使用ret指令,用栈中的数据设置IP的值,从而转到call指令后面的代码处继续执行。
10.8 mul指令:是乘法指令,使用mul做乘法的时候,注意以下两点:(1)两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位寄存器或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位寄存器或内存字单元中。(2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中放。
10.9 模块化程序设计:利用call和ret指令,我们可以用简洁的方法,实现多个相互联系、功能独立的子程序来解决一个复杂的问题。
10.10 参数和结果传递的问题:用寄存器来存储参数和结果是最常使用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:调用者将参数送入参数寄存器,从结果寄存器中取到返回值;子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
10.11 批量数据的传递:我们将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的返回结果,也可用同样的方法。除了用寄存器传递参数外,还有一种通用的方法是用栈来传递参数。
10.12 寄存器冲突的问题:在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。可用栈来保存寄存器中的内容。
11. 标志寄存器
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理器,个数和结构都可能不同)具有以下3种作用:(1)用来存储相关指令的某些执行结果;(2)用来为CPU执行相关指令提供行为依据;(3)用来控制CPU的相关工作方式。这种特殊的寄存器在8086CPU中,被称为标志寄存器。8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)。标志寄存器和其它寄存器不一样,其它寄存器是用来存放数据的,都是整个寄存器具有一个含义。而标志寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。8086CPU的标志寄存器的结构如下图所示:标志寄存器的1、3、5、12、13、14、15位在8086CPU中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11位都具有特殊的含义。
11.1 ZF标志:标志寄存器的第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0。如果结果为0,那么zf=1;如果结果不为0,那么zf=0.注意,在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令。
11.2 PF标志:标志寄存器的第2位是PF,奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如果1的个数为偶数,pf=1,如果为奇数,那么pf=0.
11.3 SF标志:标志寄存器的第7位是SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,sf=1;如果非负,sf=0.计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。某些指令将影响标志寄存器中的多个标记位,这些被影响的标记位比较全面地记录了指令的执行结果,为相关的处理提供了所需的依据。比如指令”sub al,al”执行后,ZF、PF、SF等标志位都要受到影响,它们分别为:1、1、0.
11.4 CF标志:标志寄存器的第0位是CF,进位标志位。一般情况下,在进行无符号运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从最高位的借位值。
11.5 OF标志:在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。标志寄存器的第11位是OF,溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。如果发生溢出,OF=1;如果没有,OF=0.一定要注意CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位。
11.6 adc指令:是带进位加法的指令,它利用了CF位上记录的进位值。adc指令比add指令多加了一个CF的值。adc指令和add指令相配合就可以对更大的数据进行加法运算。
11.7 sbb指令:是带借位减法指令,它利用了CF位上记录的借位值。sbb指令执行后,将对CF进行设置。利用sbb指令可以对任意大的数据进行减法运算。
11.8 cmp指令:比较指令,相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。其它相关指令通过识别这些被影响的标志寄存器位来得知比较结果。同add、sub指令一样,CPU在执行cmp指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算。
11.9 检测比较结果的条件转移指令:”转移”指的是它能够修改IP,而”条件”指的是它可以根据某种条件,决定是否修改IP。比如,jcxz就是一个条件转移指令,它可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。所有条件转移指令的转移位移都是[-128, 127]。除了jcxz之外,CPU还提供了其它条件转移指令,大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改IP。这些条件转移指令通常都和cmp相配合使用,就好像call和ret指令通常相配合使用一样。
因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种,即根据无符号数的比较结果进行转移的条件转移指令(它们检测zf、cf的值)和根据有符号数的比较结果进行转移的条件转移指令(它们检测sf、of和zf的值)。
常用的根据无符号数的比较结果进行转移的条件转移指令有:je、jne、jb、jnb、ja、jna。根据有符号数的比较结果进行转移的条件转移指令的工作原理和无符号的相同,只是检测了不同的标志位。
11.10 DF标志和串传送指令:标志寄存器的第10位是DF,方向标志位。在串处理指令中,控制每次操作后si、di的增减。df=0,每次操作后si、di递增;df=1,每次操作后si、di递减。
movsb指令:是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减。
movsw指令:是将ds:si指向的内存单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2。
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用。
8086CPU提供两条指令对df位进行设置:
(1)cld指令:将标志寄存器的df位置0.
(2)std指令:将标志寄存器的df位置1.
11.11 pushf和popf:pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。pushf和popf为直接访问标志寄存器提供了一种方法。
12 内中断
任何一个通用的CPU,比如8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。中断信息可以来自CPU的内部和外部。
12.1 内中断的产生:对于8086CPU,当CPU内部有下面的情况发生的时候,将产生相应的中断信息:(1).除法错误,比如,执行div指令产生的除法溢出;(2).单步执行;(3).执行into指令;(4).执行int指令。
8086CPU用称为中断类型码的数据来标识中断信息的来源。中断类型码为一个字节型数据,可以表示256种中断信息的来源。
12.2 中断处理程序:CPU收到中断信息后,需要对中断信息进行处理。而如何对中断信息进行处理,可以由我们编程决定。我们编写的,用来处理中断信息的程序被称为中断处理程序。
12.3 中断向量表:CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。中断向量表就是中断向量的列表。所谓中断向量,就是中断处理程序的入口地址。展开来讲,中断向量表,就是中断处理程序入口地址的列表。中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口。CPU只要知道了中断类型码,就可以将中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址。
中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。在中断向量表中,一个表项存放一个中断向量,也就是一个中断处理程序的入口地址,对于8086CPU,这个入口地址包括段地址和偏移地址,所以一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。
12.4 中断过程:可以用中断类型码,在中断向量表中找到中断处理程序的入口。找到这个入口地址的最终目的是用它设置CS和IP,使CPU执行中断处理程序。用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。CPU硬件完成这个工作的过程被称为中断过程。CPU收到中断信息后,要对中断信息进行处理,首先将引发中断过程。硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。
8086CPU在收到中断信息后,所引发的中断过程:
(1)(从中断信息中)取得中断类型码;
(2)标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中);
(3)设置标志寄存器的第8位TF和第9位IF的值为0;
(4)CS的内容入栈;
(5)IP的内容入栈;
(6)从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。
12.5 中断处理程序和iret指令:CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
iret指令通常和硬件自动完成的中断过程配合使用。在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP,而iret的出栈顺序是IP、CS、标志寄存器,刚好和其相对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。
12.6 除法错误中断的处理:当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的中断信息,CPU将检测到这个信息,然后引发中断过程,转去执行0号中断所对应的中断处理程序。
汇编编译器可以处理表达式。
12.11 单步中断:基本上,CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1。CPU提供单步中断功能的原因就是,为单步跟踪程序的执行过程提供了实现机制。
12.12 响应中断的特殊情况:一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。可是,在有些情况下,CPU在执行完当前指令后,即便是发生中断,也不会响应。
13 int指令
13.1 int指定:格式为”int n”,n为中断类型码,它的功能是引发中断过程。CPU执行int n指令,相当于引发一个n号中断的中断过程。可以在程序中使用int指令调用任何一个中断的中断处理程序。int指令的最终功能和call指令相似,都是调用一段程序。
14. 端口
各种存储器都和CPU的地址线、数据线、控制线相连。CPU在操控它们的时候,把它们都当作内存来对待,把它们总地看做一个由若干存储单元组成的逻辑存储器,这个逻辑存储器我们称其为内存地址空间。在PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还有以下3种芯片:(1)各种接口卡(比如网卡、显卡)上的接口芯片,它们控制接口卡进行工作;(2)主板上的接口芯片,CPU通过它们对部分外设进行访问;(3)其它芯片,用来存储相关的系统信息,或进行相关的输入输出处理。在这些芯片中,都有一组可以由CPU读写的寄存器。这些寄存器,它们在物理上可能处于不同的芯片中,但是它们在以下两点上相同:(1)都和CPU的总线相连,当然这些连接是通过它们所在的芯片进行的;(2)CPU对它们进行读或写的时候都通过控制线向它们所在的芯片发出端口读写命令。可见,从CPU的角度,将这些寄存器都当作端口,对它们进行统一编址,从而建立了一个统一的端口地址空间。每一个端口在地址空间中都有一个地址。
14.1 端口的读写:在访问端口的时候,CPU通过端口地址来定位端口。因为端口所在的芯片和CPU通过总线相连,所以,端口地址和内存地址一样,通过地址总线来传送。在PC系统中,CPU最多可以定位64KB个不同的端口,则端口的地址的范围为0~65535.
对端口的读写不能用mov、push、pop等内存读写指令。端口的读写指令只有两条:in和out,分别用于从端口读取数据和往端口写入数据。注意,在in和out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据。访问8位端口时用al,访问16位端口时用ax。
14.3 shl和shr指令:逻辑移位指令。
shl是逻辑左移指令,它的功能为:(1)将一个寄存器或内存单元中的数据向左移位;(2)将最后移出的一位写入CF中;(3)最低位用0补充。如果移动位数大于1时,必须将移动位数放在cl中。
shr是逻辑右移指令,它和shl所进行的操作刚和相反:(1)将一个寄存器或内存单元中的数据向右移位;(2)将最后移出的一位写入CF中;(3)最高位用0补充。如果移动位数大于1时,必须将移动位数放在cl中。
15. 外中断
15.1 接口芯片和端口:外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;CPU向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。CPU还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。可见,CPU通过端口和外部设备进行联系。
15.2 外中断信息:当CPU外部有需要处理的事情发生的时候,比如说,外设的输入到达,相关芯片将向CPU发出相应的中断信息。CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。在PC系统中,外中断源一共有以下两类:可屏蔽中断、不可屏蔽中断。
(1).可屏蔽中断:是CPU可以不响应的外中断。CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置。当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;如果IF=0,则不响应可屏蔽中断。
(2).不可屏蔽中断:是CPU必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
指令系统总结:8086CPU提供以下几大类指令:
(1). 数据传送指令:比如mov、push、pop、pushf、popf、xchg等都是数据传送指令,这些指令实现寄存器和内存、寄存器和寄存器之间的单个数据传送。
(2). 算术运算指令:比如add、sub、adc、sbb、inc、dec、cmp、imul、idiv等都是算术运算指令,这些指令实现寄存器和内存中的数据的算数运算。它们的执行结果影响标志寄存器的sf、zf、of、cf、pf、af位。
(3). 逻辑指令:比如and、or、not、xor、shl、shr、sal、sar、rol、ror、rcl、rcr等都是逻辑指令。除了not指令外,它们的执行结果都影响标志寄存器的相关标志位。
(4). 转移指令:可以修改IP,或同时修改CS和IP的指令统称为转移指令。转移指令分为以下几类:
无条件转移指令,比如jmp;
条件转移指令,比如jcxz、je、jb、ja、jnb、jna等;
循环指令,比如loop;
过程,比如call、ret、retf;
中断,比如int、iret;
(5). 处理机控制指令:这些指令对标志寄存器或其它处理机状态进行设置,比如cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock等都是处理机控制指令。
(6). 串处理指令:这些指令对内存中的批量数据进行处理,比如movsb、movsw、cmps、scas、lods、stos等。若要使用这些指令方便地进行批量数据的处理,则需要和rep、repe、repne等前缀指令配合使用。
16. 直接定址表
16.1 描述了单元长度的标号:数据标号
16.2 在其它段中使用数据标号:注意,在后面加有”:”的地址标号,只能在代码段中使用,不能在其它段中使用。
16.3 直接定址表:可以通过依据数据,直接计算出所要找的元素的位置的表。
16.4 程序入口地址的直接定址表:可以在直接定址表中存储子程序的地址,从而方便地实现不同子程序的调用。
17. 使用BIOS进行键盘输入和磁盘读写
17.2 使用int 16h中断例程读取键盘缓冲区:BIOS的”int 9”中断例程和”int 16h”中断例程是一对相互配合的程序,int 9中断例程向键盘缓冲区中写入,int 16h中断例程从缓冲区中读出。它们写入和读出的时机不同,int 9中断例程是在有键按下的时候向键盘缓冲区中写入数据;而int 16h中断例程是在应用程序对其进行调用的时候,将数据从键盘缓冲区中读出,并将已读取的键盘输入从缓冲区中删除。
GitHub:https://github.com/fengbingchun/CUDA_Test