《80x86汇编语言程序设计教程》 第一章讲的是cpu的发展历史从8位cpu发展到如今的64位CPU,但是intel的cpu一直是向下兼容的也就是说现在的cpu还是兼容8位汇编的。
我们首先来讲一下8086 CPU的基础设定,这些设定是8086 CPU硬件设计决定的所以只要记住就是了,对我们而言没什么为什么,如果真要问为什么是这样那就只能去问设计CPU的工程师咯。
一、先行基础
计算机世界中数据都是二进制的,这样一根数据线就可以表示1位,通电就是1,没通电就是0(此种表述不准确, 详情请看评论区)。
但是二进制数据对于我们来说实在太恶心了,于是我们表述的时候又一般将其转化为16进制数来表示,具体的转化请看 这里 和 这里。
二、总线介绍
首先计算机就是用来计算的机器,计算由两部分组成数据和算法(算法可以理解为解题步骤实际上也是一种数据)。计算的流程就是先取得数据和算法然后计算出结果最后将计算结果输出。这里就涉及到三个过程 取数据,计算, 输出数据。因为cpu是不存储数据的,数据都存储在内存中,所以整个流程就扩展为:
1. 告诉内存我要取哪里的数据
2. 取得数据之后进行计算
3. 再次告诉内存要将计算结果存储到哪里
提取一下上述的过程,一共存在三种信息 命令信息(读取/存储) 地址信息 数据信息。
因此CPU有三条称为总线的数据线与内存连接,即用于发送地址的地址总线,用于发送命令的命令总线,用于传输数据的数据总线。
一般而言我们不关注命令总线有多少位,因为我们直接使用CPU提供的命令即可不需要知道命令总线的细节。而地址总线和数据总线就和我们息息相关了。地址总线的宽度(宽度决定了能在这根线上传输的数字的最大值)决定了我们可以访问多大的内存,而数据总线的宽度则决定了我们一次可以传输多少数字,当然命令总线的宽度则决定我们可以使用多少种命令啦。详细解释一下,比方说地址总线是4位的,也就是说可以传输4位,二进制范围就是0-1111 用十进制表示就是0-15,那就是只能表示16个数字,也就代表最多表示16个字节的地址。同样如果数据总线也只有4位的话一次传输的数据就是4位的只能传输0-15以内的数字,如果实际数据31二进制为11101那么就要分两次传输0001 和 1101。
那么8086 CPU的数据总线的宽度是16位代表我们一次可以传输16位的数据。而地址总线是20位的这就代表我们可以访问2^20次方个数据单元。内存中以8位为一个单位也就是一个字节为单位,20位地址总线就表示可以访问2^20个字节也就是1M的数据。
三、内部寄存器
接下来我们讲讲CPU的内部,CPU内部一共设有14个寄存器,寄存器就是临时存储数据的设备,他们是16位的正好对应了16位的数据总线,所以也就是说CPU一次可以存取一个寄存器的数据。这些寄存器分别是:
4个数据寄存器AX BX CX DX
3个指针寄存器SP BP IP
2个地址寄存器 SI DI
1个标志寄存器 FLAG
4个段寄存器 SS DS ES CS
为了兼容8位汇编这四个数据寄存器每个又可以拆出两个8位的寄存器 AX对应AL AH, BX对应BL BH, CX对应CL CH, DX对应DL DH。注意AX和AH AH是重合的,AH和AL共同组成了AX,AH为AX的高8位AL为AX的低8位,改变AH和AL都会跟着改变AX,其他3个也是这样对应的。
我一开始学的时候只是死记他们的中文名称搞的好痛苦,其实知道他们的作用后根据英文名称都几乎都不需要特意去记忆了,那么接下来我们来来一一介绍他们吧。
(一) 数据寄存器
首先是4个数据寄存器,以前我一直以为ABCD就是顺序来的没啥意义,认真看书之后才知道原来还有这么巧的事情。
AX 累加寄存器(Accumulator)
BX 基地址寄存器(Base)
CX 计数寄存器(Count)
DX 数据寄存器(Data)
首先既然叫数据寄存器那么就是存储数据的,你可以用他们来临时存储任何你想存储的数据,至于他们的名字是应为他们都和对应的指令有绑定关系,就是说特定的指令会用到特定的寄存器,这个以后讲到指令的时候再说。
(二) 指针寄存器和段寄存器
接下来是3个指针寄存器和4个段寄存器,把他们放在一起介绍是有原因。在上面的介绍中你是否注意到了数据总线和寄存器都是16位的而地址总线却是20位的,这意味着一个完整的地址需要至少两个寄存器的数据拼凑出来。 拼凑的公示是这样的 20位地址 = 16位数 X 16 + 另一个16位数,没错这就对应了4个段寄存器和3个指针寄存器,CPU这样处理 20位地址 = 段寄存器 x 16 + 指针寄存器。这个公式的理念就是段式内存管理,内存是分段来管理的,将内存分成一段一段,然后在指定段内的偏移地址。这样说可能比较抽象我们举个例子, 比如 字母表abcdefg我们描述e的位置的时候可以这样描述 "第5个字母" ,也可以这样描述"从c开始第3个字母"更或者"从d开始第2个字母"。后面的两种表述方式就是CPU使用的描述方式,唉~我希望我表述的足够清楚。反正就是先指定一个基础的地址然后描述和他的距离。你可能也意识到了一个地址的表述可以有很多种就好比340这个位置 340 = 20 x 16 + 20 ,340 = 10 x 16 +180 , 340 = 0 x 16 + 340 没错就是这么随意,也就是说CPU并没有给段地址的选定加上任何条件,唯一的条件就是段地址要乘16,也就说每个段的开头的地址都是 段地址 X16 + 0 所以段地址的开头总是16的整数倍,我们称之为16位对齐。这么做看起来好像很烦其实非常好用,这个以后会介绍到。
然后这七个寄存器都是CPU专用的都有自己的专门的职能
CS 代码段寄存器(Code Segment) 它是和 IP 指令指针寄存器(Instruct Point) 一起来组成地址的,CS x 16 + IP, CPU自动使用这个地址来获取下一条要执行的指令
DS 数据段寄存器(Data Segment) 它的伙伴就是BP 基址指针寄存器(Base Point) 这个比较特殊不是简单的 DS x 16 + BP了,这个之后在寻址方式的时候会介绍
SS 堆栈段寄存器(Stack Segment) 它的伙伴就是SP 堆栈指针寄存器(Stack Ponint) 堆栈是一个比较特殊用途的存储结构
ES 扩展段寄存器(Extand Segment) 他就比较可怜了没有固定搭配的伙伴
这里扩展一下堆栈,堆栈是一个特殊的存储结构,详细的解释见百度百科,他在内存中一般也是用来临时存储数据的,往这个结构存储数据叫做压栈,取数据叫做出栈。他的特点就是先进后出,就像一个堆满的仓库一样,想要拿到最早放进去的东西就必须先把外面的东西拿出来。具体来说就是SS给出一个堆栈的起始位置,而SP则指定了当前位置。 比如初始化 SS = 100 SP =10 ,你也可以将这两个数据 理解为栈的地址在100 X 16 + 0处,容量是10。 那么第一次压栈数据那么数据就存储在1610这个位置,这个时候SP会--,SP就变成9 再次压栈就数据就存储在 1609,然后是1608,1607...直到1600,sp=0这个时候就表明这个堆栈满了。这里有个搞脑子的地方就是数据的存储方向是从高地址往低地址移动的也就是数据的存储位置是倒着数的 10 9 8 7 6... 而不是我们习惯的 1 2 3 4 5... 这样数的好处就是只用两个寄存器就可以表示出位置和容量以及当前位置,SP=0就表示满了。
(三) 地址寄存器
两那个地址寄存器SI DI
SI 源地址寄存器(Source Index)
DI 目的地址寄存器(Destination Index)
这两个家伙会搀和到DS 和 BP这对好基友中去,一起参与数据存储地址的表示,在寻址方式的章节中详细介绍。
(四) 标志寄存器
标志寄存器厉害了,它是最特殊的,因为它是按位起作用的,就是说它的每个位都有独特的意义,如下表:
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
OF | DF | IF | TF | SF | ZF | AF | PF | CF |
这些标志可以分为两组,一组是运算结果标志,另一组是状态控制标志
1. 运算结果标志
他们主要是受指令执行结果影响
OF 溢出标志位(Overflow Flag Flag) 有符号数加减的时候数据是否超出能表示范围,比如 8位数据 127 到 -128,那么如果计算 120 + 120 = 240 超过表示范围这就叫溢出OF就变1否则就是0,每次运算都会影响一次。
SF 符号标志位(Sign Flag) 表示运算结果的正负,正数结果SF=0,负数结果SF=1
ZF 零标志位(Zero Flag) 表示运算结果是否为0,结果为0 ZF=1,结果部位0 ZF=0
AF 辅助进位标志位(Auxiliary Flag) 比较难理解,简单的说就是后半段的计算是否影响了前半段的计算,举例 1010 + 1001 那么前半段是 10 + 10 后半段是 10 + 01,那么10 + 01是不影响前面的10+10的计算的,但是如果是 1010+1011 那么就不一样了,10+11结果是 101,需要向前面进位 所以前面的计算会变成10+10+1,对前半段产生影响了,同样减法也是一样的,减法就是是否借位了,CPU有8位计算和16位计算,8位计算就是 后4位时候对前4位产生影响,16位计算就是后8位是否对前8位产生影响,产生影响AF=1,没影响就是AF=0
PF 奇偶标志位(Parity Flag) 表示运算结果中使用二进制表示时1的数量是否是偶数,举了例子比如结果是 1010那么1的个数是2那么就是偶数,1011中1的个数是3所以是奇数 偶数个1 PF=1, 奇数个1 PF= 0
CF 进位标志/Carry Flag 和AF比较像,AF是半段的进位/借位标志,CF是全段的进位/借位标志。最高位的计算时候是否产生进位/借位,注意区分这里的最高位不是数的最高位,而是计算用的寄存器的最高位。比如前面的说的AL 和 AH是8位寄存器,假设是1+1 那么 2进制表示就是 AL=00000001 AH=00000001 ,00000001+0000001=00000011,那么最高位始终都是0,没有进位,如果发生进位/借位CF=1 否则就是0。
2. 状态控制标志
他们控制了CPU的一些行为
DF 方向标志位(Direction Flag) 和字符创相关的指令有关,字符串就是一连串的字符,DF标志只是字符串的存储方向,比如hello 当DF=1的时候就是告诉CPU 在内存中从低到高是这样存储的olleh, 当DF=0的时候就是告诉CPU从低到高是hello这样存储的。具体在说到指令的时候会详细将到。
IF 中断标志位(Interrupt Flag) 是否允许中断(中断就是打断CPU指令的正常执行后面讲到),IF=1表示允许中断,IF=0表示不允许中断
TF 跟踪标志位(Trace Flag) 单补执行,如果TF=1 那么CPU执行完一条指令后就会进入单布中断。