《Assembly Language Step by Step》笔记——第四章 寻址(1)
1. 汇编语言学习的难点
在尝试学习汇编语言之前,你至少应当理解整个计算机的体系结构。
阅读这些关于现代计算机系统资料是必要的:
1. 英文版 http://duartes.org/gustavo/blog/category/internals
2. 中文翻译 http://blog.csdn.net/drshenlei/article/category/551407
就算你学遍了一个指令集中的每一条指令,也不能代表你就学会了汇编语言
CPU的真正工作,也是汇编语言编程的最大挑战,在于对内存中指令和数据的寻址
寻址是一件困难的事情,在x86体系结构下尤甚
x86体系下内存模型分为三种:
1. 最古董的实模式扁平模型(real mode flat model)
2. 已经退休了的实模式分段模型(real mode segmented model)
3. 在Win XP/Vist/7 还有Linux上盛行的保护模式扁平模型(protected mode flat model)
如果你对以上三种模型还搞不清楚的话,先回去仔细看看下面这篇文章吧:
http://blog.csdn.net/drshenlei/article/details/4261909
2. 历史的回眸
要理解为虾米会有这么多模型出现,那话说起来就长啦!第2节从历史的深处说起,主要讲述x86体系结构下各种模式出现的缘由
2.1 Intel 8080处理器
让我们回退到70年代吧!1974年Intel推出了8080处理器,8080处理器是一款8位处理器,也就是说,一次只能处理8个比特的信息。但是呢,它的地址总线却有16位!那个年代,16位的地址总线有点吓人哦!因为在那个RAM贵的吓死人的年代,大多数的机器,内存也就是4K到8K之间,16位的总线寻址能力达到了64K!
所以说,Intel设计师的设计总是会超前的,就像现在的Intel Core2处理器,地址总线有33位,可以寻址64GB的内存,但是现在大部分计算机内存不也就2~4G吗?
话说回来,那会使用8080的操作系统主要是CP/M-80,CP/M-80有点与众不同,操作系统内核是加载到内存顶部的(高址处),这样做很大程度是为了瞬时程序(Transient Program)能有一个一致的内存起点(类似于用户程序,在需要时才从磁盘中加载进来运行),好比我们Linux的用户程序,其线性地址的起点都是0x00000000,不过这里的起点是0x100H就是第256字节处。而从0开始的头256个字节用于存储其他信息比如程序的磁盘I/O缓冲区。示例图如下:
2.2 Intel 8086
CP/M-80的内存模型已经够简单了,瞬时程序的编写者只要将程序开头声明为从0100H地址开始就行。
然后Intel严格地按照摩尔定律的指示,制造出了16位的处理器8086,这个16位指的是数据引脚的位数,一次能处理16比特位的信息。但是,这个处理器有20条地址引脚,能寻址达1MB空间。
当Intel制造出第一款16位的处理器8086时,为了更以前保持兼容,能让8080上的程序很容易地移植在8086上,就不得不做些trick了,你想,以前的内存地址是16位,现在是20位,差了整整4位耶!一个公司的产品,总是需要向后兼容的吧,总不能说你今天买了台Intel Core 2的机器,昨天在你奔3上跑的魔兽世界现在却怎么也起不来了吧?这怎么行。
让我们来看看这些trick是怎么做的。首先先来了解一下“段”这个概念。嗯,1MB的寻址空间,刚好是16个64K的内存区块,随便把CP/M-80的程序放在这16个块中的任意一个不就可以了吗?而且,内存区块起始地址也不用非得刚好和这16个64K内存区块对齐,长度也不用非得是64K,只要满足了CP/M-80程序要求的长度就行了嘛。这些区块,就可以称之为段!
既然提到了段,那么让我们来看看段的本质是什么:
段就是在CPU能寻址和使用的内存区中的一个块。
在实模式分段模型环境下,一个段以16字节为对齐边界,段长至少一个字节,至多64K。一个1M的内存区,最多可以分为64K个段,每段长16字节,最少也有16个段,每段长64K。为什么是这样,下面会有解释。
了解完“段”的概念,再让我们看一下8086中CS寄存器的作用吧。CS寄存器是16位的,里面存的值就是指向1M内存地址空间中某一个段的起始地址值。
假设当前CS的值是0x1000,当前程序用逻辑地址0x1234去访问一条指令时,会将CS向左移4位,同时加上0x1234,这时真实物理地址就是0x11234。
物理地址 = 段值*16 + 逻辑地址(偏移量)
这样,只要精心设计好CS的值,原来的CP/M-80程序就可以畅通无阻地运行在8086机器上啦。
而且,每一个段都必定是16字节对齐的(段值 * 16),长度最大64K(16位逻辑地址,最大64K寻址空间)。这种寻址模式,就成为实模式分段模型啦。计算之后的地址也是物理地址,就是说实模式了,又用到了分段,那就是分段模型了。
虽然这样做从短期来看无疑是很好的,可是长远来看真是个糟糕透顶的设计!因为,在不改变CS内容的情况下,CPU只能寻址64K大小的范围,当程序大小超过64K,需要在不同的段之间跳来跳去的时候,这个设计真让开发者抓狂蛋疼啊!
在这里不得不提的一个地方,就是人们很少能注意到,段是可以重叠的!
比如起始地址0x00000,长度为64K的段,和起始地址0x00010,长度为32K的段,后者被前者包含,但它们2个是可以并存的。
下面来看看MyByte指向的那个字节,由于段可以重叠,那么这个字节的地址表示方式就有多种多样了。如果用 “段值:偏移”来表示的话,就可能有以下3种表达方式了
2.3 段寄存器及其作用
我们知道,从8086开始到286,386及其后续处理器,都有一些段寄存器陆续登上历史舞台,它们分别是:
CS code segment 代码段寄存器。这是用来对机器指令寻址的,也就是说该段寄存器指向的内存块存储的是机器指令。
DS data segment 数据段寄存器。这是用来对变量或者其他数据寻址用的。
SS stack segment 栈区段寄存器。
ES extra segment 额外段寄存器,一般用来存储额外的段信息的寄存器
FS,GS 和 ES作用一样,不过它们是从386才开始出现的
2.4 通用寄存器
最开始在8086上,通用寄存器只有AX, BX, CX, DX, SP, BP, SI, DI 8个,AX, BX, CX, DX这四个寄存器。在1986年Intel为x86体系结构扩展到了32位,这些寄存器也就成了EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI, 不过,你还是可以通过AX, BX这些符号去访问EAX,EBX寄存器的低16位,就像以前那样。
写到这里发现日志已经过长了,不符合大家的阅读习惯,下篇文章再介绍其他几个寄存器