单位说明
在计算机中最小的信息单位,称之为位(bit,又称比特)。 存储器中所包含存储单元的数量称为存储容量,其计量基本单位是字节(Byte。简称B)
- 数据传输是以大多是以“位”(bit,又名“比特”)为单位。一个位就代表一个0或1 。 1bit = 1b
- 数据存储是以“字节”(Byte)为单位。1B = 8bit = 8b
- 1KB = 1024B = 1024 * 8b = 8Kb
总线 (8086处理器16bit CPU)
- 地址总线: 它的宽度决定了CPU的寻址能力。8086的地址总线宽度是20,所以寻址能力是1MB。N根地址总线,那么它可以寻2^N个内存单元,即1总线宽度 = 21B = 2B( 2^20B = 1024B 1024B = 1024KB = 1MB)

- 数据总线: 它的宽度决定了CPU的单次数据传送量,也就是数据传送速度,8086的数据总线宽度是16,所以单次最大传递2个字节的数据
- 控制总线: 它的宽度决定了CPU对其他器件的控制能力、能有多少种控制
寄存器
寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
- 8086有14个寄存器,每个寄存器都是16位的,每个存放2个字节

通用寄存器
- AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)
通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间

- CPU首先会将红色内存空间的值放到AX寄存器中:mov ax,红色内存空间
- 然后让AX寄存器与1相加:add ax,1
- 最后将值赋值给内存空间:mov 蓝色内存空间,ax
AX、BX、CX、DX这4个通用寄存器都是16位的,如下图所示

- 上一代8086的寄存器都是8位的,为了保证兼容, AX、BX、CX、DX都可分为2个独立的8位寄存器来使用H代表高位寄存器
L代表低位寄存器


字和字节
在汇编的数据存储中,有2个比较常用的单位
- 字节:byte,1个字节由8bit组成,可以存储在8位寄存器中
- 字:word,1个字由2个字节组成,这2个字节分别称为字的高字节和低字节
比如数据20000(4E20H,0100111000100000B),高字节的值是78,低字节的值是32

- 1个字可以存在1个16位寄存器中,这个字的高字节、低字节分别存储在这个寄存器的高8位寄存器、低8位寄存器中
8086寻址方式
- CPU访问内存单元时,要通过内存单元的地址,所有的内存单元都有唯一的地址,叫做物理地址
- 8086有20位地址总线,可以传送20位的地址,1MB的寻址能力
- 但它又是16位结构(数据总线)的CPU,它内部能够一次性处理、传输、暂时存储的地址为16位。如果将地址从内部简单地发出,那么它只能送出16位的地址,表现出来的寻址能力只有64KB
- 存在的问题:CPU的寻址能力是1MB,但数组总线是16位,一次性能处理的数据为64KB
- 解决方案: 8086采用一种在内部用2个16位地址合成的方法来生成1个20位的物理地址


| 物理地址 | 段地址 | 偏移地址 |
|---|---|---|
| 0x32AF1 | ||
| 0x32AF1 | 0x3200 | 0x0AF1 |
| 0x32AF1 | 0x32AF | 0x0001 |
| 0x32AF1 | 0x32A0 | 0x00F1 |
| 0x32AF1 | 0x3000 | 0x2AF1 |
内存分段管理
- 8086是用“基础地址(段地址×16) + 偏移地址 = 物理地址”的方式给出物理地址
为了开发方便,我们可以采取分段的方法来管理内存,比如:

- 地址10000H~100FFH的内存单元组成一个段,该段的起始地址(基础地址)为10000H,段地址为1000H,大小为100H
- 地址10000H~1007FH、10080H~100FFH的内存单元组成2个段,它们的起始地址(基础地址)为:10000H和10080H,段地址为1000H和1008H,大小都为80H
在编程时可以根据需要,将若干连续地址的内存单元看做一个段,用段地址×16定为段的起始地址(基础地址),用偏移地址定位段中的内存单元
- 段地址×16必然是16的倍数,所以一个段的起始地址(基础地址)也一定是16的倍数
- 偏移地址为16位,16位地址的寻址能力为64KB,所以一个段的长度最大为64KB
段寄存器
- 8086在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址
- 段地址在8086的段寄存器中存放
- 8086有4个段寄存器:CS、DS、SS、ES,当CPU需要访问内存时由这4个段寄存器提供内存单元的段地址
- CS (Code Segment):代码段寄存器
- DS (Data Segment):数据段寄存器
- SS (Stack Segment):堆栈段寄存器
- ES (Extra Segment):附加段寄存器
CS和IP
- CS为代码段寄存器,IP为指令指针寄存器。它们指示了CPU当前要读取指令的地址
- 任意时刻,8086CPU都会将CS:IP指向的指令作为下一条需要执行的指令
- 8086CPU的工作过程
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲寄存器
- IP = IP + 上一条指令的长度,从而指向下一条指令
- 执行指令。转向步骤1,重复整个过程
JMP指令
- CPU从何处执行指令是由CS、IP中的内容决定的。我们可以通过改变CS、IP的内容来控制CPU执行目标指令
- mov(传送指令)可以用来修改大部分寄存器的值,但不能用于设置CS、IP的值
- 8086提供了另外的指令来修改CS、IP的值,这些指令统称为转移指令,最简单的是jmp指令
若想同时修改CS和IP的内容,使用
jmp 段地址: 偏移地址来完成1
2
3
4
5
6
7
8
9
10jmp 2AE3:3
CS: 0x2AE3
IP: 0x0003
则从内存地址0x2AE33处读取指令
jmp 3:0B16
CS: 0x0003
IP: 0x0B16
则从内存地址0x00B46 (CS*16+IP) 处读取指令若指向修改IP的内容,使用
jmp 某一合法寄存器来完成、1
2
3
4
5jmp ax -> 执行前: ax = 1000H, CS = 2000H, IP = 0003H
执行后: ax = 1000H, CS = 2000H, IP = 1000H
jmp bx -> 执行前: bx = 1003H, CS = 2000H, IP = 1000H
执行后: bx = 1003H, CS = 2000H, IP = 1003H
DS和[address]
- CPU要读写一个内存单元时,必须要先给出这个内存单元的地址,在8086中,内存地址由段地址和偏移地址组成
8086中有一个DS段寄存器,通常用来存放要访问数据的段地址
1
2
3mov bx,1000H
mov ds,bx
mov al,[0]- 上面3条指令的作用是将内存中10000H(1000:0)的数据,赋值给al的过程
- mov al:[address] 作用是将DS:[address]中的值给al
- al是8位的寄存器,所以将一个字节的数据给al
- 8086不支持将数据直接送入段寄存器中,mov ds,1000H是错误的
写几条指令,将al中的数据送入内存单元1000H中
1
2
3mov bx, 1000H
mov ds, bx
mov [0], al8086CPU在编程时,可以将一组内存单元定义为一个段。我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16倍数的内存单元当做专门存储数据的内存空间,称为数据段。比如用123B0H~123B9H这段内存空间来存放数据,我们就可以认为123B0H~123B9H是一个数据段,它的段地址为123BH,长度为10字节。用DS存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元
完整的汇编
1 | assume cs:code |
- 汇编语言由2类指令组成
- 汇编指令,如mov、add、sub等。有对应的机器指令,可以被编译为机器指令,最终被CPU执行
- 伪指令,如assume、 segment、ends、end等。没有对应的机器指令,由编译器解析,最终不被CPU执行
- db(define byte) 自定义字节; dw(define word)自定义字
- 汇编指令,如mov、add、sub等。有对应的机器指令,可以被编译为机器指令,最终被CPU执行
- 注释以分号开头
- segment和ends的作用是定义一个段,segment代表一个段的开始,ends代表一个段的结束
- 一个有意义的汇编程序中,至少要有一个段作为代码段存放代码
- assume用作代码段的code段和CPU中的cs寄存器关联起来
- 编译器遇到end时,就结束对源程序的编译
中断
从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,跳到中断处理程序的入口点,进行中断处理
- 中断是由于软件的或硬件的信号,使得CPU暂停当前的任务,转而去执行另一段子程序。也就是说,在程序运行过程中,系统出现了一个必须由CPU立即处理的情况,此时,CPU暂时中止当前程序的执行转而处理这个新情况的过程就叫做中断
- 中断的分类
- 硬中断(外中断),由外部设备(比如网卡、硬盘)随机引发的,比如当网卡收到数据包的时候,就会发出一个中断
- 软中断(内中断),由执行中断指令产生的,可以通过程序控制触发
- 可以通过指令int n产生中断
- n是中断码,内存中有一张中断向量表,用来存放中断码对应中断处理程序的入口地址
- CPU在接收到中断信号后,暂停当前正在执行的程序,跳转到中断码对应的中断向量表地址处,去执行中断处理程序
- 常见中断
- int 10h用于执行BIOS中断
- int 3是“断点中断”,用于调试程序
- int 21h用于执行DOS系统功能调用,AH寄存器存储功能号
1 | ;hello world! 字符串的输出 |
栈
- 栈:是一种具有特殊的访问方式的存储空间(后进先出, Last In Out Firt,LIFO)
- 8086会将CS作为代码段的段地址,将CS:IP指向的指令作为下一条需要取出执行的指令
- 8086会将DS作为数据段的段地址,mov ax,[address]就是取出DS:address的内存数据放到ax寄存器中
- 8086会将SS作为栈段的段地址,任意时刻,SS:SP指向栈顶元素
- 8086提供了PUSH(入栈)和POP(出栈)指令来操作栈段的数据。比如push ax是将ax的数据入栈,pop ax是将栈顶的数据送入ax
push ax
- SP = SP - 2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
- 将ax中的内容送入 SS:SP 指向的内存单元处,SS:SP此时指向新栈顶

pop ax
- 将SS:SP指向的内存单元处的数据送入ax中
- SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶

通过栈交换ax, bx内容
1 | assume cs:code |
栈段
- 对于8086来说,在编程时,可以根据需要,将一组内存单元定义为一个段
- 我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16倍数的内存单元,当做栈空间来使用,称为栈段。比如用10010H~1001FH这段内存空间当做栈来使用,我们就可以认为10010H~1001FH是一个栈段,它的段地址为1001H,长度为16字节
- 用SS存放栈段的段地址,用SP存放栈顶的偏移地址
1 | assume cs:code,ss:stack,ds:data |
栈顶指针,指向0x07114内存空间
Loop指令
- loop指令和cx寄存器配合使用,用于循环操作类似高级语言的for,while
使用格式:
1
2
3
4mov cx,循环次数
标号:
循环执行的程序代码
loop 标号loop指令执行流程
- 先将cx寄存器的值 - 1, cx = cx - 1
- 判断cx 的值; 如果不为零执行标号的代码,又执行步骤1;如果为零执行loop后面的代码
1 | ;通过loop求2的6次方的和64 |
结果: ax: 0x0040h = 64

Call和ret指令
示例1
1 | assume cs:code,ds:data |
call print 之后,将下一条指令地址(07118h-07119h)的偏移地址0008h入栈, 根据print标号指向的内存地址的偏移地址赋值给IP(0x000C),然后执行print函数
print 函数结束后,调用ret,将下一条指令偏移地址出栈,赋值给IP
示例2:外平栈
函数执行前后, SP不变
1 | assume cs:code,ss:stack |
示例3:内平栈
函数执行前后, SP不变
1 | assume cs:code,ss:stack |
示例4(重要):完整函数调用过程,对bp进行现场保护
完整函数调用过程:
- push参数(64位cpu 任性使用寄存器)
- call指令调用(将下一条指令地址入栈)
- 保护bp寄存器,将sp赋值给bp
- 提升sp指针,作为局部变量空间(sp 减去值)
- 保护寄存器
- 业务逻辑
- 恢复寄存器
- 恢复sp(sp指向bp/sp 加上值)
- 恢复bp(pop bp)
- 返回(ret)
1 | C语言: |


| SP | BP | BX | CX | DX | |
|---|---|---|---|---|---|
| 调用函数前 | 0x0028 | 0x1002 | 0x1003 | 0x1004 | 0x1005 |
| 调用函数后 | 0x0028 | 0x1002 | 0x1003 | 0x1004 | 0x1005 |