本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
学习教材:《汇编语言(第4版)》王爽著 此笔记是书中内容+自我总结,方便查阅和复习 请支持原著
一、简介
CPU内的寄存器用于存储信息,在CPU里通过内部总线与其余器件相连接
程序员通过对寄存器存储内容的改变可以间接操控CPU的运行
intel8086CPU包含14个寄存器,它们有各自的名字:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PWS,它们的共性是都是16位寄存器,可以存放两个字节的数据
二、通用寄存器
在14个寄存器中:AX、BX、CX、DX这四个寄存器通常用于存放一般数据,又被称为通用寄存器
它们的具有相同的逻辑结构——如同16个格子,它们从0-15编号,由低到高,因此:相对更靠近0号的位被称为较低位,相对更靠近15号的位被称为较高位,每个位具有两种状态,连在一起从而构成一个16位的数据。
特别的,由于8086的前代CPU(intel8080、intel8085)使用的是8位寄存器,为了保证向下兼容,每个16位寄存器可以被拆分成两个独立、互不干扰的8位寄存器使用
例如:对于AX而言,处于较低位的那一半称为AL(Low),处于较高位的那一半称为AH(High),其它同理
三、十六进制和二进制
由于单个寄存器可以存放一个8位二进制数据,并且CPU寄存器由n个8位寄存器构成,用大规模的二进制描述数据就显得不易识别
根据数制的知识可知,二进制转十六进制只需要分隔每四位转换即可得到十六进制数据,如(1110 1001)B=(E 9 )H,而恰好一个寄存器是8位,这样就可以很直观地把一个寄存器中的数据转化成一个2位十六进制数据
因此常用十六进制表示寄存器的数据,通常会有H作为后缀,如10H表示十六进制数10,即十进制数16
四、简单的汇编指令及注意事项
指令忽略大小写
| 汇编指令 | 操作 |
|---|---|
| mov ax,1 | 将1存入16位寄存器ax |
| mov al,1 | 将1存入8位寄存器al |
| mov ax,bx | 将寄存器bx的数据存入寄存器ax |
| add ax,bx | 将寄存器ax的数据与寄存器bx的数据相加,结果存入寄存器ax |
注意:
- 对于两个寄存器的操作必须保证操作对象的位数相同。如
mov ax,al是错误的,因为它们的位数不同;mov al,2710H是错误的,因为8位寄存器最大存储值是FFH - 对于AX里的AL和AH而言,虽然整体上是一个数据,但是拆分后它们是独立、互不干扰的,若想存储高于8位的数据,超出部分不能在8位寄存器中保存。如
add al,2710H,这时候al里的数据只有后两位10H,且整个ax里的数据只有10H;如add ah,2710H,这时候ah里的数据只有后两位10H,由于未对al操作,默认al为00H,因此整个ax里的数据是1000H
测试:最多使用4条指令计算2的4次方
由于2^4=={[(2+2)+(2+2)]+[(2+2)+(2+2)]},因此可以对2自身迭代计算得到结果,即:
mov ax,2 #ax=2
add ax,ax #ax=2+2=4
add ax,ax #ax=4+4=8
add ax,ax #ax=8+8=16
五、物理地址的计算
对于16位CPU的特性有以下三条:
- 运算器一次最多可以处理16位数据
- 寄存器最大宽度为16位
- 内部总线宽度为16位
由于内存单元地址在送上总线之前需要在CPU内处理传输和存放,这决定了intel8086CPU只能处理最大16位
但是,8086有总共20位地址总线。为了更好的利用全部位宽,8086在存储地址时将其拆分成两个16位的部分存储在两个寄存器内,调用时通过内置的加法器运算得到20位地址
详细地:地址的一部分称为段地址,一部分称为偏移地址
对于合成地址的操作:段地址 * 16+偏移地址=物理地址
对段地址*16等效于将段地址做二进制位运算左移4位、做十六进制位运算左移1位。举个例子,就像在十进制下,将10左移一位得到了100,这左移相当于乘以进制基数10,同理右移一位得到1,相当于除以进制基数10
由于物理地址对合成原理只要保证最后结果正确且段地址和偏移地址不超过16位,因此,物理地址对分解是不唯一的
假设要存储的物理地址为12345H,那么段地址可以是1234H,偏移地址是0005H。在地址加法器内首先将段地址*16,即十六进制下左移一位得到12340H,再与偏移地址相加就可以得到物理地址12345H
这时候该地址可以描述为内存单元1234:0005
测试:有一数据存放在内存20000H单元中,求段地址SA的合法最大最小值
物理地址=段地址 * 16+偏移地址
对于最大值:令偏移地址为最小值0,得到段地址 * 16=20000H,对其右移一位得到段地址SA=2000H
对于最小值:令偏移地址为最大值FFFFH,得到段地址 * 16=20000H-FFFFH=10001H。由于该值右移一位是1001.1H,得到的不是整数,因此不能作为最大值(地址均为整数),因此逐渐减小偏移地址,直到偏移地址是FFF0H,得到段地址 * 16=20000H-FFF0H=10010H,对其右移一位得到段地址SA=1001H
因此:SA max=2000H,SA min=1001H
六、段寄存器
由于8086对地址表示成段地址和偏移地址的形式,我们可以将一串操作或是数据划归为一个段存储,通过改变偏移地址来访问每一个操作或数据值。
如:将mov ax,bx划分成段1,将add ax,bx划分为段2,每个段又分出三部分,每部分存储一个操作对象元。如同:| mov ax bx | add ax bx |
在8086中:寄存器CS为代码段寄存器,寄存器IP为指令指针寄存器,即对应存储段地址和偏移地址
对每段代码读取完毕后,IP会加上前一段代码的长度,跳转到下一段代码的地址,从而准备对下一段代码的读取,接着才会执行读取完毕的代码
以下是取值的全过程
- CS和IP的地址传入地址加法器
- 在地址加法器内计算出物理地址
- 地址传入输入输出控制电路
- 控制电路将物理地址传入地址总线
- 地址的值传入数据总线
- 数据传入输入输出控制电路
- 数据由输入输出控制电路传入指令缓冲器
- IP偏移地址加上这段数据段所占内存单元的长度,即指向下一段数据
- 数据由指令缓冲器传入执行控制器,进行执行操作
不同于通用寄存器的操作,8086对CS和IP有其他的指令来改变它们的值。对通用寄存器使用mov,对CS和IP使用jmp
若需要同时改变CS和IP,使用jmp abcd:efgh,即指定段地址和偏移地址来完成
若只需要改变IP,如递增读取,使用jmp ax,ax指代任一合法寄存器,表示将该寄存器的值修改IP
可以类比为特殊的mov操作,但是实际上并不正确
测试:下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax,bx #ax=bx
sub ax,ax #ax=ax-ax
jmp ax #ip=ax
由上文提到:在每次将数据传入指令缓冲器后,在执行操作之前,IP会修改值,因此总共有4次对IP的修改
第一次:mov传入缓冲器后,IP指向sub 第二次:sub传入缓冲器后,IP指向jmp 第三次:jmp传入缓冲器后,IP指向jmp后的代码(未知) 第四次:执行jmp,修改IP,最终IP=0
七、代码段
我们可以将长度为N(N<=64KB)的一组代码存放在一组地址连续、起始地址是16的倍数的内存单元中,从而定义了一个代码段。只要我们将CS、IP指向这一段内存,那么这一段内存就是一个代码段,可以被执行