2 访问寄存器和内存
2.1 寄存器和数据存储
8086的CPU和内存

其中EU(Execution Unit)和BIU(Bus Interface Unit)
8086有14个寄存器
- 通用寄存器AX,BX,CX,DX都可以分为高位和低位
- CS:IP是存放指令的地址组合
所有寄存器都是16位的,可以存放两个字节,上述四个都可以分为两个独立的八位寄存器使用(AX=AH+AL)
8086是16位字长(1个字),一次可以处理一个寄存器的内容,一个寄存器存储一个字
2.2 MOV和ADD指令
| 汇编指令 | 控制CPU | 高级语言 |
|---|---|---|
| mov ax, 18 | 把18送入ax | ax = 18 |
| mov ax, bx | 把bx的值送入ax | ax = bx |
| add ax, 8 | ax的值加8 | ax += 8 |
| add ax, bx | ax的值加bx的值 | ax += bx |
汇编指令不区分大小写
例:ax=8226h,bx=8226h,执行add ax, bx,得到ax=?
解:ax=044ch,本来是1044ch,但是ax只有16位,去掉msb
例:ax=00c5h,bx=8293h,执行add al, bl,得到ax=?
解:ax=0058h,本来是0158h,但是al只有8位,去掉msb
2.3 确定物理地址
所有的内存单元构成的存储空间是线性的(C++的数组)
8086的寻址能力是\(2^{20}\),但是它是16位机,只能一次处理16bit,需要使用地址加法器合成物理地址
合成方法:段地址*16+偏移地址=物理地址

都是16进制表示,*16表示末尾加一个0,转为2进制就是多0000,变成了20bit

8086处理物理地址的过程

2.4 内存分段表示
内存本身是连续的,段的划分来自CPU(就是段地址的划分)
段地址由于16,所以*段的起始地址一定是16的倍数,在二进制中表现为0000结尾;偏移地址为16bit,故每个段最大长度\(2^{16}=64\)kB
不同的段地址和偏移地址可以表示一个物理地址,如2000h*16+1F60h=2100h*16+0F60h
2.5 Debug常见命令
R(Register)查看、改变寄存器的内容;R 寄存器名 改变指定寄存器内容(-r ax /endl ax 1234,ax=1234)
D(Data)查看内存内容,列出预设地址内存的128个字节(8*16,一个地址2bit)的内容
- -d 段地址:偏移地址 (结尾偏移地址)列出指定位置的内存内容
E(Edit)改变内存内容
- -e 段地址:偏移地址 12 34 将起始位置开始的两个地址内容改为12 34
U将内存中的机器指令翻译为汇编指令
A以汇编指令的格式在内存里写指令
- -a 段地址:偏移地址 汇编指令
T执行机器指令
- -t 执行CS:IP处的指令,每次执行一条
2.6 CS、IP和代码段
CS:代码段寄存器
IP:指令指针寄存器
CS:IP:CPU将该地址指向的内容当作指令(不当作数据)
8086CPU的指令读取和执行

- 从CS:IP指向的单元读取指令,指令进入指令缓冲器
- IP += 上一段指令的长度(如 mov ax, 8长3个字节,ip从0100变为0103)
- 执行这段指令
重复上面三个步骤(一直-t)
2.7 JMP指令
用于修改CS和IP的值,改变程序执行的顺序
修改CS/IP的方式
- -rcs+值,-rip+值;但这是debug内部调试手段
- mov cs,2000H;错误,特殊的寄存器不能用mov
- 转移指令jmp
- jmp 段地址:偏移地址:修改CS:IP,用于段间程序跳跃
- jmp 某个值或寄存器:仅修改IP,用于段内程序跳跃
例:如图,从20000H开始执行,执行的序列是?

- mov ax, 6622H;ax=6622H
- jmp 1000:3;段间跳跃
- mov ax, 0000H;ax=0000H
- mov bx, ax;bx=0000H
- jmp bx;段内跳跃
- mov ax, 0123H;ax=0123H
- mov ax,0000H;ax=0000H,开始循环
2.8 内存中字的储存
16bit=2byte=1word,1word的高八位放在高字节,低八位放在低字节
如"4E20H,0012H",存放方式是
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| 20H | 4EH | 12H | 00H |
字节型和字型
字节型指一个字节;字型指从这个字节开始的2个字节,这个字节为低八位
如上的内存,1地址存放的字节型数据是“4EH”,1地址存放的字型数据是“124EH”
但是,每次读内存段开始的一个字节
图中就是偶数开始,故读0地址的字型数据要1次,读1地址的字型数据要2次
2.9 用ds寄存器和[address]实现字的传送
ds(data segment)寄存器存放的是当前指向的段地址信息,[address]表示偏移地址
下面代码:
mov bx, 1000H
mov ds, bx
mov al, [0] #等价于 mov al, ds:[0]
由于mov无法直接操作ds,需要一个中间变量;bx表示base,常用来存放地址的中间变量
上面对代码将段地址写为1000H,将1000H:0000H的数据写入al中(也可以用ax)
例:

注意ax是字型变量,al是字节型变量,故所有的mov操作都将内存地址视为字型变量的低地址
数据段
默认由ds寄存器的值决定,偏移地址由[address]决定
数据累加问题
看以下的区别:
mov ax, 123BH mov ax, 123BH
mov ds, ax mov ds, ax
mov al, 0 mov ax, 0
add al, [0] add ax, [0]
add al, [1] add ax, [2]
add al, [2] add ax, [4]
左边是用一个字节累加,是字节型累加;右边用2个字节累加,是字型累加
2.10 mov和add的指令形式
| mov指令 | 示例 | add指令 | 示例 |
|---|---|---|---|
| mov 寄存器, 数据 | mov ax, 8 | add 寄存器, 数据 | add ax, 8 |
| mov 寄存器, 寄存器 | mov ax, bx | add 寄存器, 寄存器 | add ax, bx |
| mov 寄存器, 内存单元 | mov ax, [0] | add 寄存器, 内存单元 | add ax, [0] |
| mov 内存单元, 寄存器 | mov [0], ax | add 内存单元, 寄存器 | add [0], ax |
| mov 段寄存器, 寄存器 | mov ds/cs, ax | / | / |
sub用法与add相同
2.11 栈与栈操作的实现
栈:先进后出
栈段(stack segment, ss)是专门开辟的一个区域,用于暂存数据(如函数调用等)
栈的基本操作:push入栈,pop出栈
8086中栈的操作
CPU对栈的指定:SS寄存器存放栈顶的段地址,SP(stack pointer)存放栈顶的偏移地址
栈指令操作:push ax和pop ax,没有push al和pop al
任意时刻,SS:SP指向栈顶元素;栈顶的地址最低
指定栈空间
mov ax, 1000H
mov ss, ax
mov sp, 0010H
ss不能mov,但是sp可以
栈空间范围:SS:0000H——SS:SP-1,由于SP最大64K,一个栈最大空间为64KB
栈段和数据段可以共享段地址
push和pop指令
push ax:将ax值送入SS:SP;SP -= 2
pop ax:将SS:SP中的值送入ax;SP += 2
push和pop无法保证指针不出栈,要自己注意