注意:通常,每一种类型的CPU都有它自己唯一的机器语言。这是为什么为Mac写的程序不能在IBM类型PC机运行的一个原因。
8086 16位寄存器
16位通用寄存器有:AX,BX,CX,DX.他们可拆分为两个8位寄存器,例如:AX
H代表高位,L代表低位。但是AH和AL不能独立于AX是十分重要的.
16位指针寄存器:SI 和DI 。通常它们都是当作指针来使用,但是在许多情况下也可以像通用寄存器一样使用。但是,它们不可以分解成8位寄存器。
16位BP和SP 寄存器用来指向机器语言堆栈里的数据,被各自称为基址寄存器(BP)和堆栈指针寄存器(SP)。
段寄存器:CS,DS,SS 和ES 。它们指出程序不同部分所使用的内存。CS代表代码段,DS 代表数据段,SS 代表堆栈段和ES代表附加段。ES当作一个暂时段寄存器来使用。
它们指出程序不同部分所使用的内存。CS代表代码段,DS 代表数据段,SS 代表堆栈段和ES代表附加段。ES当作一个暂时段寄存器来使用。
指令的执行:
1,指令指针寄存器(IP) 与CS寄存器一起使用来跟踪CPU下一条执行指令的地址。通常,当一条指令执行时,IP提前指向内存里的下一条指令。
2,FLAGS寄存器储存了前面指令执行结果的重要信息。这些结果在寄存器里以单个的位储存。例如:如果前面指令执行结果是0,Z位为1,反之为0。并不是所有指令都修改FLAGS里的位,查看附录里的表看单个指令是如何影响FLAGS寄存器的。
80386 32位寄存器
在原有的基础上拓展:
16位AX寄存器扩展成了32位,为了向后兼容,AX依然表示16位寄存器而EAX 用来表示扩展的32位寄存器。兼容:EAX(32位)=(16位)+AX(16位)。就像是AX(16位)=AH(8位)+AL(8位),AX相当于EAX中的低位。但是没有直接访问EAX 高16位的方法。
其它的扩展寄存器是EBX,ECX,EDX(通用寄存器),ESI 和EDI (指针寄存器)。
许多其它类型的寄存器同样也扩展了。BP变成了EBP;SP 变成了ESP;FLAGS变成了EFLAGS;P变成了EIP;
但是,不同于指针寄存器和通用寄存器,在32位保护模式下只有这此寄存器的扩展形式被使用。
**
在80386里,段寄存器依然是16位的。这儿有两个新的段寄存器:FS和GS。它们名字并不代表什么。它们是附加段寄存器(像ES一样)。术语中字的一个定义为CPU数据寄存器的大小。
当80386开发出来后,它被决定依旧保持这个字定义不改变,即使寄存器的大小已经改变了。
计算机实模式和保护模式
实模式
在实模式下,内存被限制为仅有1M字节(220 字节)。有效的地址从00000到FFFFF (十六进制)。这些地址需要用20位的数来表示。显然,一个20位的数不适合任何一个8086的16位寄存器。Intel通过利用两个16位数值来决定一个地址的方法来解决这个问题。开始的16位值称为段地址(selector)。
段地址的值必须存储在段寄存器中。第二个16位值称为偏移地址(offset)。用32位段地址:
偏移地址表示的物理地址可以由下面的公式计算:
16 * selector + offset
在十六进制中乘以16是非常容易的,只需要在数的右边加0。例如:表示为047C:0048的物理地址通过这样得到:
实际上,段地址的值是一节的首地址(看表1.2)。
真实的段地址有以下的缺点:
1、一个段地址只能指向64K内存(16位偏移的上限)。如果一个程序拥有大于64K的代码那又怎么办呢?在CS里的一个单一的值不能满足整个程序执行的需要。程序必须分成小于64K的段(segment)。当执行从一段移到另一段时,CS里的值必须改变。同样的问题发生在大量的数据和DS 寄存器之间。这样使用是非常不方便的!
2、每个字节在内存里并不只有唯一的段地址。物理地址04808可以表示为:047C:0048,047D:0038,047E:0028 或047B:0058。这将使段地址的比较变得复杂。
16位保护模式
在80286的16位保护模式下,段地址的值与实模式相比解释得完全不同。在实模式下,一个段地址的值是物理内存里的一节的首地址。在保护模式下,一个段地址的值是一个指向描述符表的指针。两种模式下,程序都是被分成段。在实模式下,这些段在物理内存的固定位置而且段地址的值表示段开始处所在节的首地址。在保护模式下,这些段不是在物理内存
的固定的地址。事实上,它们根本不一定需要在内存中。
保护模式使用了一种叫做虚拟内存的技术。虚拟内存的基本思想是仅仅保存程序现在正在使用的代码和数据到内存中。其它数据和代码暂时储存在硬盘中直到它们再次需要时。当一段从硬盘重新回到内存中,它很有可能放在不同于它移动到硬盘之前时的位置的内存中。所有这些都由操作系统透明地执行。程序并不需要因为要让虚拟内存工作而使用不同的书写方法。
在保护模式下,每一段都分配了一条描述符表里的条目。这个条目拥有系统想知道的关于这段的所有信息。这些信息包括:现在是否在内存中;如果在内存中,在哪;访问权限(例如: 只读)。段的条目的指针是储存在段寄存器里的段地址值。
16位保护模式的一个大的一 个 非 常 著 名 的PC专 缺点是偏移地址依然是16位数。这个的后果就是段的大小依然限制为最大64K。这会导致使用大的数组时会有问题。
32位保护模式
80386引入了32位保护模式。386 32位保护模式和286 16位保护模式之间
最主要的区别是:
1. 偏移地址扩展成了32位。这就允许偏移地址范围升至4G。因此,段的大小也升至4G。
- 段可以分成较小的4K大小的单元,称为内存页。虚拟内存系统工作在页的方式下,代替了段方式。这就意味着一段在任何一个时刻只有部分可能在内存中。在28616位保护模式下,要么整个段在内存中,要么整个不在。这样在32位模式下允许的大的段的情况下很不实用。
在Windows 3.x系统中,标准模式为286 16位保护模式而增强模式为32位保护模式。Windows 9X,Windows NT/2000/XP,OS/2和Linux都运行在分页管理的32位保护模式下。
中断
有时候普通的程序流必须可以被要求快速反应的处理事件中断。电脑提供了一个称为中断的结构来处理这些事件。例如:当一个鼠标移动了,硬件鼠标中断现在的程序来处理鼠标移动(移动鼠标,等等)。中断导致控制权转移到一个中断处理程序。中断处理程序是处理中断的程序。每种类型的中断都分配了一个中断号。在物理内存的开始处,存在一张包含中断处理
程序段地址的中断向量表。中断号是这张表中最基本的指针。
外部中断由CPU的外部引起。(鼠标就是这一类型的例子。)许多I/O设备引起中断(例如:键盘,时钟,硬盘驱动器,CD-ROM和声卡)。内部中断由CPU的内部引起,要么是由一个错误引起,要么由中断指令引起。错误中断称为陷阱。由中断指令引起的中断称为软中断。DOS使用这些类型的中断来实现它的API(应用程序接口)。许多现代的操作系统(如:Windows和UNIX)使用一个基于C的接口。
许多中断处理程序当它执行完成时,将控制权返回给被中断的程序。它们恢复寄存器,里面的值与中断发生之前的值相同。因此,被中断的程序就像没有任何事情发生一样运行(除了它失去了一些CPU周期)。陷阱通常不返回。通常它们中止程序。
8086CPU对存储器的读写
CPU在与存储器交换信息时,必须对以下3类信息进行交互:
1,地址信息(存储单元的地址)
2,控制信息(其间的选择,读,写等命令)
3,数据信息(读或写的数据)
这些信息通过链接CPU的导线来传递,导线的集合称为总线,导线是信号交互的通道:
1,地址总线
2,控制总线
3,数据总线
交互模型如下:
物理地址的计算:
物理地址=段地址x16+偏移地址
CS和IP:
CS为代码段寄存器,设其内容为M
IP为指针寄存器,设其内容为N
则8086CPU将从内存 M x16+N 单元开始,读取一条 指令 并执行
(单元地址) (单元内容)
注:指令进入指令缓冲器后,IP里的值自动增加
修改CS和IP:
jmp 2AE3:3 执行后 CS:2AE3H IP:0003H
可以用某一合法寄存器修改IP值:
jmp ax 相当于 mov IP ax ,但是 mov IP ax 不存在,只是含义相同
寄存器内存的访问
字单元(地址字单元)
字节单元:有8bit(位)数据构成为1字节的字节单元
字单元:**有一个高位字节单元和一个低位字节单元构成的结构称为字单元,通常以起始字节编号N,
命名为,N地址字单元
DS和[address]
DS(ds):段寄存器,用来存放段地址;在汇编指令
mov al [0] 中
[0]是一个偏移地址,al是AX的低位寄存器,段地址自动从ds寄存器中去读取,所以这条命令代表着
将 ds:0 内存中的数据存入寄存器al中
注:在8086CPU中,是不允许直接将数据写入ds寄存器中,方法之一是通过通用寄存器(AX,..,DX)中转数据
例如:
需要将第10000H内存里的数据写入al,则完整的指令如下:
mov dx 1000H
mov ds dx
mov al [0]
若要将al中的数据写入第10000H内存中,则完整指令如下:
mov dx 1000H
mov ds dx
mov [0]
注意:在8086CPU中,[0]指得是字地址单元,要从读取两个字节单元,按照先低位后高的顺序开始读取
sub ax bx 含义:ax=ax-bx
add ds ax 在8086CPU下是不允许的,ds寄存器只能通过其他寄存器进行转移操作,不能进行运算操作。
8086CPU中的栈:
基本结构和数据结构中的栈相似,但是在基本的存储上可能会和其他CPU不同,特别注意在栈传输数据时,需要以一个字为单位进行传输时,在8086CPU中,地位数据在地位单元中,高位数据在高位单元中。
但是,CPU是如何找到栈顶呢?一个SS段寄存器和一个SP寄存器组成的地址,SS:SP就是指向栈顶的地址,且在任意时刻SS段寄存器和SP寄存器都指向栈顶地址。
在入栈时,SP寄存器先减后存入栈
在出栈时,先将栈的元素存入目标中,再对SP进行加运算
8086CPU栈顶超界指的是,当push和pop是超出原先界定的范围,从而改写了或消除了与栈相连地址部分的关键数据,进而引发问题。
一个特殊的例题
题目分析,易混思想,若SS=10000H,栈空时则指针地址应该在20000,SP则无法确定;
但是在运算下,SP应该进行SP-2运算,且优先运算所以SP=0000H,在表面上SS:SP为10000H,无法满足题目条件,但是SP进行地一个push操作使得栈不为空,(0000-0002)=FFFE,此时地址为SS:SP=1FFFE满足题目条件。
总结:当栈为空时,SS:SP地址并不在逻辑上的栈地址,如题所示。
[bx]和loop
[bx]:
表示一个偏移地址,与ds段寄存器里的值连用
例如:
mov ax [bx]
代表将地址为 ds:[bx] 起始地址,在ax放入2个字节单元(1字单元),也就是将 ds:[bx] 和 ds:[bx+1]放入内存单元
例如:
mov al [bx]
代表将地址为ds:[bx]地址单元(1个字节单元)的内容放入al中
符号说明:用()表示所选符号或者地址单元的内容,(ax)代表ax寄存器的内容等等
用idata表示常量
inc bx 于指令 mov bx 1 一样,都代表着 bx=bx+1
注:在汇编语言中,数据不能以字母开头,例如:A000,在编写排版时候应写未:0A000
loop:
(1) mov cx idata
(2)s: add dx ax
(3) loop s
顺序为先给循环条件数cx初始化,即(1)
执行循环语句,即(2)
先执行cx=cx-1,判断cx是否为0,若不为0,则执行标记语句s;若为0,则执行下一句语句,即(3)
Debug和masm编译器对指令的不同处理
在不同的编译器下
汇编指令解释可能不同,例如:
mov ax [0] 在某些汇编编译器下会解释为: mov ax 0
那么在masm编译器下如何去运用[idata]作为偏移地址呢?
答案之一将[idata]的内容存入一个通用寄存器,然后用[寄存器]来表示偏移地址
例如:
mov ax 1000H
mov ds ax
mov bx 0
mov al [bx]
在masm编译器代表着将1000:0000内存存入al寄存器中
答案之二是在[idata]前一个段寄存器ds,例如:
mov ax 1000H
mov ds ax
mov al ds:[0]
这种方法意义同答案一是一样的
包含多个段的程序
汇编语言在使用数据,栈时需要提前申明数据空间,例子代码为:
assume cs:code
code segment
dw idata
dw idata(栈空间)
start mov ax,bx
mov cx,idata
s: ……………
……………
loop s
mov ax,4c00H
int 21H
code ends
end start
用dw申明的空间,dw idata存入code的空间里,具体在code空间的开头部分
start 标记着代码执行的开始部分,end start标志着代码结束执行
数据处理的两个基本问题
bx,di,si,bp
只有bx,di,si,bp四个寄存器,加上[..]表示偏移地址
若
mov ax,[cx]
则会语法错误
只要[..]出现bp,段地址没有显性指出,则默认的段地址寄存器是ss
dd,dw,db,dup
dw 定义一个数据类型长度为word (16位二进制)
db 定义一个数据类型长度为byte (8位二进制)
dd 定义一个数据类型长度为2 word(32位二进制)
dup配合上述符号使用
例如:
db 3 dup(0)
定义了3个字节数据,内容为0的数据
db 3 dup(1,2,3)
定义了9个字节数据,内容为1,2,3重复3次
db 3 dup(‘abc’,‘ABC’)
定义了18个字节数据,内容为abcABC重复3次