汇编语言
书籍电子版 提取码: b62a
书上内容比较全,初学者多看看王爽老师的书。有不懂的再来查。
笔记是简略版,有检测点和实验的答案,还有自己的理解。
前言
在学之前,搞清楚本书的重心:
通过学习关键指令来深入理解机器工作的基本原理,培养底层编程意识和思想。
读者应具备以下知识:
- 二进制、十六进制的知识
- 一门高级语言基本编程基础(顺序,选择,循环)
第一章:基本知识
位、字、字长、字节
- 位(bit,b):是计算机中最小的数据单位,一个位的值只可能是0或1
- 字节(Byte,B):8位是一个字节
- 字长:CPU在单位时间可以处理的最大二进制的位数。8位CPU,也就是单位时间内最大可以处理一字节。
- 字(Word,W):计算机处理数据时,一次存取、加工、传送的数据的长度称为字。一个字通常是由多个字节构成。字长为32的CPU,一个字等于4个字节。显然,字与字长有关,可以理解为:一个字 = 字长/8。
机器语言
- 机器语言是机器指令的集合
- 机器指令展开来讲就是一台机器可以正确执行的命令。
汇编语言的产生
- 汇编语言的主体是汇编指令
- 汇编指令与机器指令的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式。
- 汇编指令是机器指令的助记符。
汇编语言的产生
机器只认识二进制,机器语言就是一堆二进制码。所有的高级语言写的源程序最终都会变成二进制,给机器运行。
机器语言人难写又难读,所以就用另一种人比较好看的语言来做这些事情,通过编译器最终转换成机器语言。
这个转换过程叫做翻译。
寄存器:CPU中可以存储数据的器件,一个CPU中有多个寄存器。
汇编语言的组成
- 汇编指令(机器码的助记符,有对应的机器码)
- 伪指令(没有对应的机器码,由编译器执行,计算机不执行)
- 其它符号(由编译器识别,没有对应的机器码)
汇编语言的核心是汇编指令,它决定了汇编语言的特性。
存储器
CPU是计算机的核心部件,它控制整个计算机的运作并时进行运算,要想让一个CPU工作,就必须向它提供指令和数据。
指令和数据在存储器中存放, 也就是平时所说的内存。计算机中的硬件一般都有固定的存储,显卡,网卡,BIOS都有。
存储单元
存储器被划分为若干个存储单元,每个存储单元从0开始编号。
微机存储器的容量是以字节为最小单位来计算的,一个存储单元就是1Byte,即8个二进制位。也就是说64KB就是10^16字节,字节单位已经是最小单位了,不能再细分了。
B=>KB=>MB=>GB=>TB
指令和数据
在计算机的内存和磁盘上,只有二进制数。这些二进制数有两种可能,要么是数据,要么是指令。
CPU对存储器的读写
- 存储单元的地址(地址信息)
- 器件的选择,读或写命令(控制信息)
- 读或写的数据(数据信息)
地址线发出操作3这个地址,控制线说是读,数据线将08读入CPU。
同理:地址线发出操作3这个地址,控制线说是写,数据线将一个数据写进3这个内存单元。
三大总线
-
地址总线:
-
CPU是通过地址总线来指定存储单元的*(定位的是Byte,不是bit)*
尤其要注意这里,能寻址的最小单元是字节单元,也就是说,这个单元上的二进制能表达256种可能。
-
地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址
一个CPU有N根地址总线,则可以说这个CPU的地址总线的宽度为N
其最多可以寻找2的N次方个内存单元(2^N byte)
-
-
控制总线
- CPU对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合
- 有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制,其宽度决定了CPU对外部器件的控制能力
内存的读或写命令是由几根控制线综合发出的:
- 其中有一根名为读信号输出控制线负责由CPU向外传送读信号,CPU向该控制线上输出低电平表示将要读取数据
- 有一根名为写信号输出控制线负责由CPU由外传送写信号
可以粗略的理解为每一条线对应一个东西,发送0、1进行读写。
-
数据总线
- CPU和内存或其它器件之间的数据传送是通过数据总线来进行的
- 数据总线的宽度决定了CPU和外界的数据传送速度
数据总线与地址总线不同了,他的一根线就表示一位。8位数据(1byte)就要8根线,而地址线找地址一根就能找两种内存单元(byte)。
注意点
地址总线寻址是从存储(内存)单元寻址的,从寻址来说,存储单元就是最小可编址单位,一般就是1Byte。一根就是1Byte。N根就是2^N个Byte。
数据总线一根就是一位。
内存编址:存储器由许多可存放一段信息的单元(位置)组成,每个单元都有一个编号,程序可以通过这个编号来访问这个单元,这个编号就是这个单元的地址。
显然地址总线就是通过这个编号来访问每一个地址单元的,而地址线一根就是两种状态,通过组合又组成了多种状态来找到这个编号。 如果有6个单元就至少需要3根线(2^3=8)。
检测: 检测点1.1
内存地址空间
一个CPU地址总线宽度为10,那么可以寻址1024个内存单元(Byte),这个1024个可寻址的单元就构成了CPU的内存地址空间。
主板
主板上有一些主要部件和一些核心器件,这些器件通过三大总线相连。CPU、存储器、外围芯片组、扩展卡槽等
接口卡
计算机系统中,所有可用程序控制其工作的设备,都要受到CPU的控制,但是CPU并不能直接控制这些设备,直接控制这些的是在扩展卡槽上的接口卡,扩展卡槽通过总线与CPU相连,所以接口卡也相连。CPU可以直接控制这些接口卡,来间接的控制这些外部设备。
CPU ——> 接口卡——>外设
各类存储器芯片
一台PC机中,装有多个存储器芯片。
物理上来看这些芯片是独立的,从读写属性上分为两类
- RAM 可读写,掉电内容丢失
- ROM 只读
从功能和连接上分为
随机存储器RAM
用于存放CPU使用的绝大部分数据和程序,主随机存储器一般由两个位置上的RAM组成
- 装在主板上RAM
- 插在扩展插槽上的RAM
装有BIOS(Basic Input/Output System)的ROM
BIOS是由主板和各类接口卡厂商提供的软件系统,可以通过它利用该硬件设备进行最基本的输入与输出。
主板有主板的BIOS
显卡有显卡的BIOS
如果网卡有ROM,那其中就可以存储网卡的BIOS
接口卡上的RAM
某些接口卡需要对大批量输入、输出数据进行暂时存储,在其上装有RAM。
典型的是显卡上的显存,显示卡随时将显存中的数据向显示器上输出,将需要的内空写入显存,就会出现在显存中。
内存地址空间
上面的存储器在物理上是独立的,但是下面两点是相同的
- 都和CPU的总线相连
- CPU对它们进行读或写的时候都通过控制线发出内存读写命令
CPU在操控它们时,把它们都当作了内存看待,把它们总的看作一个由若干存储单元组成的逻辑存储器,这个存储器就是内存地址空间
所有物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。CPU在这段地址空间中读写数据,实际上就是在对应的物理存储器中读写数据。
内存地址空间大小受CPU地址总线宽度的限制,8086CPU地址总线宽度为20,所以8086PC内存地址空间为1MB,80386地址总线为32,则PC内存地址空间为4GB。
第二章:寄存器
一个典型的CPU由运算符、控制器、寄存器等器件构成,这些器件靠内部总线相连。
前一章说的总线相对于CPU内部而言其实是外部总线,而内部也有一个总线,实现了CPU内部各个器件之间的联系,外部总线实现CPU和主板上其他器件的联系。
在CPU内部
- 运算器进行信息处理
- 寄存器进行信息存储
- 控制器控制各种器件进行工作
- 内部总线连接各种器件,在它们之间进行数据传送
对于汇编程序员来说,CPU中的主要部件是寄存器。寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器中内容来实现对CPU的控制。
这里的寄存器是在CPU中的
8086CPU
8086CPU中的所有寄存器都是16位的
通用寄存器
AX、BX、CX、DX
为了兼容上一代的8位寄存器, 这4个寄存器都可以分为两个可独立使用的8位寄存器。
以AX为例高8位AH,低8位AL。
上面表中AX最高位少了一个0
字在寄存器中的存储
8086CPU:
- 字节:Byte,8bit
- 字:两个字节,就是16位,分别是高位字节和低位字节
几条汇编指令
汇编指令和寄存器名称不区分大小写。
检测: 检测点2.1
物理地址
CPU访问内存单元时,要给出内存单元的地址,所有内存单元给同的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,这个地址就是物理地址。
CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。而这个物理地址,不同的CPU有不同的形成方式。
8086CPU(16位结构的CPU)
16位的CPU(16位机,字长为16位),意味着:
- 运算器一次最多可以处理16位数据
- 寄存器最大宽度为16位
- 寄存器和运算器之间的最大通路为16位
也就是说,在8086内部,能够一次性处理、传输、暂时存储的信息的最大长度是16位的。
8086CPU给出物理地址的方法
8086CPU有20位的地址总线,达到1MB的寻址能力,8086CPU又是16位的结构,如果从内部发出的话,也只能发出16位,8086CPU用段地址+偏移地址的方法来形成一个20位的物理地址。
物理地址=段地址*16 + 偏移地址
乘16也就意味着16进制数左移一位,也就是16位的2进制数变成了20 位的2进制数,再加上这个偏移地址,就形成了可以确定的20位的物理地址。
段的概念
要注意的是,内存其实没有分段,是CPU搞出来的段地址。
下面两张图:
可以认为左边的10000H-100FFH组成一个段,起始地址为10000H,段地址为1000H,大小为100FFH-10000H = 100H
可以认为右边的10000H-1007FH组成一个段,10080H-100FFH组成一个段,大小都为80H。
可以看出,起始地址=段地址*16,必然是16的倍数。
在8086CPU中,偏移地址是16位,最大寻址是64KB,0000H-FFFFH,想要两个段接得上,段的长度应该是64KB。
总结
在8086PC机中,存储单元的地址用两个元素来描述,段地址和偏移地址。
数据在21F60H内存单元中,一般不这么说,要说:
- 数据在2000:1F60单元中
- 数据在2000段中的1F60H单元
检测: 检测点2.2
段寄存器
8086CPU在访问内存时需要段地址与偏移地址,送入地址加法器合成物理地址。这里就是由寄存器提供地址,段寄存器提供段地址。
8086CPU有4个段寄存器:
- CS(Code Segment)
- DS(Data Segment)
- SS(Stack Segment)
- ES(Extra Segment)
CS和IP
IP(instruction point):指令指针寄存器
8086PC机中,CS中的内容为M,IP中的内容为N,8086将从M*16 + N的内存单元开始,读取一条指令并执行。
IP执行完当前指令后会自动移到下一条指令的位置。如果当前指令是3个字节那就加3,如果是两个字节就加2。
8086CPU工作过程:
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器
- IP = IP+所读取的指令的长度, 从而指向下一条指令
- 执行指令。转到步骤1,重复这个过程
在8086CPU加电启动或复位,CS = FFFFH,IP = 0000H,FFFF0H是开机后的第一条指令。
CPU将CS、IP中的内容当作指令的段地址和偏移地址
修改CS、IP
程序员可以通过改变CS、IP中的内容来控制CPU执行目标指令。
jmp 段地址:偏移地址
用指令中给出的段地址修改CS,偏移地址修改IP
jmp 偏移地址
改变IP
CPU只认被CS:IP指向的内存单元中的内容为指令。
检测: 检测点2.3
实验1
第三章:寄存器(内存访问)
第二章从CPU如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关寄存器以及一些指令。
这一章从访问内存角度学习寄存器。
内存中的存储
数据和程序在内存中,都是二进制文件,能访问的地址也就是一个字节,用16进制来表示。CPU中,用16位寄存器来存储一个字,高8位存放高字节,低8位放低字节。
上图的0,1,2,3……就是能访问到的地址,用16进制来表示一个字节,比如20H,4EH等等。
由于内存单元是字节单元,则一个字就要用两个连续的内存单元来存。图中0、1两个内存单元存放数据4E20H,0是低地址单元,1是高地址单元。
:字单元:存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成,高地址内存单元中存放字型数据的高位字节,低地址内存单元存放字型数据的低位字节。
以起始地址为N的字单元简称N地址字单元。
DS和address
CPU要读写一个内存单元时,必须先给出这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址组成。
8086CPU中有一个DS寄存器用于存放段地址(注意DS寄存器也应该是16位的,不是20位的)
mov bx, 1000H
mov ds, bx
mov al, [0]
//将1000:0中的数据读到al中
为什么不能是
mov ds 1000H
因为8086CPU不支持将数据直接送入段寄存器(设计缺陷)
传送多少的数据是由最后的通用寄存器决定的,如果是ax、bx就传16位,最大也就只能传16位,而如果是al、ah,那就传送8位。
字的传送
8086CPU是16位结构,一次传送16位数据。
mov bx, 1000H
mov ds, bx
mov ax, [0]
mov [0], cx
16进制数一个数字代表四个二制数,4个数就是16个二进制数,也就是16位。
高位在高地址,低位在低地址
mov、add、sub指令
- mov 指令可以对寄存器,数据,内存单元之间的随意赋值
- add、sub指令不可以用于段寄存器(以s结尾的寄存器segment)
数据段
可以将一组长度为N<=64KB、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间。
ds中存放数据段的段地址,再自己根据需要访问段中的单元。
栈
栈是一个存储空间,据有特殊的访问方式。先进后出,First in last out
由图形来看的话就也是一段空间,如下图,但是注意这段空间高地址在下面,低地址在上面。
检测: 检测点3.1
CPU提供的栈机制
ss:sp在任意时刻都指向栈顶元素,push入栈,pop出栈
在刚开始时,栈的长度是sp所决定的,ss为段地址,也就是低地址,在图上来说就是在上方,而sp始终指向栈顶,当栈为空时,栈顶应该在栈的最底部的下面。也就是最高地址的后面。按上图10000H~1000FH来说,ss=1000H,sp=10H。长度就是16byte。
栈顶越界问题
如果一直push肯定总有一天会满栈然后越界,如果一直pop,肯定总有一天会空栈然后也越界。要求编程人员自己注意。
push、pop指令
- push
push 寄存器 将寄存器中的数据入栈
- sp = sp-2,先让sp指向当前栈顶前面的单元,也就是将要送入数据的单元
- 将寄存器中的内容送入ss:sp指向的内存单元,此时ss:sp就指向新栈顶
- pop
pop 寄存器 将栈中的数据送入寄存器
-
将ss:sp指向的内存单元中的数据送入到寄存器中
-
sp = sp+2,让sp指向当前栈顶下面的内存单元,以这个单元为新的栈顶
8086CPU是16位的CPU,所以一次操作16位二进制数据,也就是4位16进制数据。一个内存单元中的数据是一个字节,也就是8位数据,在push和pop时,一次改变两个内存单元。
栈段
栈段就是当作栈来使用的一段空间。显然sp<=64KB(ffff),所以栈段最大不超过64KB
比如 10010H~1001FH,这是一个16字节的内存单元。