第二章 寄存器
2.6 CS、IP与代码段
内存中的信息都是以二进制码的形式存放的,那么我们到底怎么区分这些信息是指令码还是一般的数据呢?
8086CPU是这样处理规定的:设定两个寄存器CS和IP(为什么要用两个寄存器?因为要解决内部总线16位,但地址总线20位的差异问题),这两个寄存器的值共同来决定一个具体的内存单元,并将从这个内存单元开始的一段连续地址当作是“代码段”,也就是说这一块区域被人为的规定为是存储指令的。并且CS和IP分别保存着要执行的下一条指令的内存单元起始地址的段地址和偏移地址。
所谓的“代码段”的长度是自己定的,8086CPU只是有寄存器时刻保存着下一条指令的地址,若是要执行指令,那么一定是从CS:IP处开始执行。当然,具体寄存器的值也是可更改的,反正说来说去就一句话,你的具体实现8086不管,它只是给了你实现的规则和方法。
下一个知识点是: 8086PC机执行指令的一般步骤。
- CS和IP寄存器的内容送入地址加法器中运算得出物理地址;
- 这个物理地址再由总线输入输出控制通过20位的地址总线传送到内存,找到相应内存单元;
- 再将内存区域的相应指令码通过数据总线传送回CPU,这段地址码将被暂时保存到指令缓冲器中;
- 此时根据读取的指令字节数,IP将自动加上相应字节数以便指向下个指令所在的内存区域的起始偏移地址;
- 指令被执行,若还要运行指令,重回第一步的步骤。
关于预习时出现CPU如何识别指令字节数的问题,在这说说自己的猜测(网上没找到一致的答案):在最先的一字节的操作码中,就预设定好一个数据范围,其代表着一些指令是存在操作数,便要扩充指令字节数。于是在取指的过程中首先判断操作码的大小,再来判断指令是多少字节的。这点也不必多虑,这是与指令格式设计相关的问题,不是本课程研究范围。
2.7 jmp指令
既然CS:IP处的内存内容是下一条要执行的指令码,那么改变CS和IP的值便能改变程序要执行的下一条指令,怎么做到改变CS和IP的值呢?
有很多方法,并且将这些能改变CS和IP值的指令统称为转移指令。书上介绍了一种最简单的指令——jmp指令。
jmp指令有两种具体方式:
- jmp 段地址:偏移地址,它代表着一次性修改CS和IP两个寄存器的内容。
- jmp 某一合法寄存器,它代表着用这个合法寄存器内容只改变IP的值。
目前阶段这个合法寄存器只能是通用寄存器。
注意:类似mov cs,ax这种指令是存在的,但是现阶段它就是一条毫无意义的指令并且性质非常恶劣,它有没有什么别的作用仍待考证。用mov指令是不能送入一个指定值给cs的,换句话说就是mov指令不能正确地修改cs的内容(可以“变”)。
为什么说这是条恶性指令?执行完类似mov cs,ax指令后,不管ax的内容是什么,cs和ip的内容都会变成固定值f000和1060,再接着执行下去就变成中断处理。
那么我们能不能改变f000:1060内存单元的数据呢?再来看看8086内存划分就知道了:
f000:1060内存区域在ROM空间中,ROM是只读内存不可更改,也就是说执行完mov
cs,ax这个中断程序是必定会执行的,程序又回到这条指令上,就变成了恶性死循环。总结下来,这条指令毫无意义!不过“存在即合理”,8086CPU为什么要保留这类指令呢?大概是对恶意篡改CS寄存器的程序员的一种惩罚???
还有一种改变CS和IP内容的方法是:用调试命令r 寄存器名来实现。这种就不是指令的维度了,而是到了程序调试的维度。
第三章 寄存器(内存访问)
3.1 内存中字的存储
字的概念在多种CPU中都普遍存在,一般多少位的CPU那么它定义的一个字就是这么多位。比如:8086CPU是16位的,因此在8086PC机中一个字就是16位,即两字节。
内存中字的存储:低位字节存放在低地址内存单元,反之亦然。
3.2 用DS和[address]实现字的传送
刚才才学了“代码段”的概念,现在所谓的“数据段”在本质上和代码段是一模一样的!
在8086CPU中,用DS寄存器保存数据段的段地址,但是不同点是:没有用一个固定的寄存器保存偏移地址。
为什么呢?其实也很好理解,数据的存储和使用并不是有连续关系的,即我现在用的一串数据并不是一定连续存储在一起,如果设定了这么个保存偏移地址的寄存器,这个寄存器内容还是需要改来改去(不单单是累加,是跳转),那么设定一个数据指针寄存器是没有意义的,故就不设定了。
但是我们还是需要知道数据在哪个内存单元啊,没有偏移地址这个物理地址怎么找?于是8086CPU为我们建立了一个新的公式:[XXXX] 。[]就是数据的偏移地址的标识符,而具体内容就是XXXX四位十六进制数。(hex)
更为重要的是:8086CPU可以从内存中,一次性读取一个字的内容!
可以的意思是可以,不是一定。现假设ds内容已设好,举例:
- mov al,[0] --> 传递的是一个字节。
- mov ax,[0] --> 传递的是一个字。
读取规则还是满足:低位字节保存在低地址···
3.3 DS与数据段
这一节内容有点与标题不符,DS和数据段的概念没啥好讲的了,这一节帮我们整理总结了mov add sub 指令的正确用法。
那就先来总结一下mov指令的正确使用:
- mov 通用寄存器,给定数据 --> CPU通用寄存器送入一个给定数值。
mov 段寄存器,给定数据–> 无论什么段寄存器都是非法的,Error!- mov 指针寄存器,给定数据 --> CPU指针寄存器送入一个给定数值。(SP、BP是可行的,只有IP是非法的,Error!指令指针的改变有严格规范!)
- mov 通用寄存器,通用寄存器 --> CPU内部两通用寄存器间的交换。
mov 段寄存器,段寄存器 --> Error !- mov 指针寄存器,指针寄存器 --> 有IP就非法。
- mov 通用寄存器,段寄存器 --> CPU内部通用寄存器送入段寄存器的内容。(CS是可以正确使用的)
- mov 段寄存器,通用寄存器 --> CPU内部段寄存器送入通用寄存器的内容。(虽然CS是合法的,但并没有起到正确的作用,故还是错的!到这明白了,可能设定mov cs,ax这类指令只是为了格式风格上的统一吧。但是为啥ip就直接非法了呢??哈哈哈,还是没明白。)
- mov 通用寄存器,指针寄存器 --> CPU内部通用寄存器送入指针寄存器的内容。(只有IP是非法的,Error!指令指针的改变有严格规范!)
- mov 指针寄存器,通用寄存器 --> CPU内部指针寄存器送入通用寄存器的内容。(只有IP是非法的,Error!指令指针的改变有严格规范!)
- mov 通用寄存器,内存单元 --> CPU通用寄存器送入内存字(或字节)单元的信息。
- mov 内存单元,通用寄存器 --> 内存字(或字节)单元的信息送入CPU通用寄存器。
- mov 段寄存器,内存单元 --> CPU段寄存器送入内存字单元的信息。(虽然CS是合法的,但并没有起到正确的作用。)
- mov 内存单元,段寄存器 --> 内存字单元的信息送入CPU段寄存器。(CS是正确的)
- 等等
mov指令在任何情况下,不可能出现IP寄存器。
add和sub指令更简单,不可能出现段寄存器和IP寄存器。
3.4 栈及栈操作的实现
栈不过是种只能在一端进行操作的数据结构罢了,其本质和“代码段”、“数据段”是一致的,就不多说了。
其中SS:SP时刻记录着栈的栈顶物理地址。
为方便操作入栈和出栈,8086CPU定义了push和pop指令来实现。
需要注意的是:
- 任意时刻,SS:SP都指向栈顶的物理地址。
- 防止栈溢和栈空操作是需要程序员自己的处理的,这也很好理解。
- 为满足低字节放在低地址,那么就要以最大物理地址最为栈底。
3.5 关于“段”的总结
“段”就是为了程序员和机器更好的分别和处理数据,而自己划分出的一个个“区域”。说个极端点的现象:所有RAM内存空间可以是一个数据段,也可以是一个代码段,还可以是个栈!