50 KiB
概述
软件基于数据结构,数据结构又基于计算机,计算机由操作系统运行,计算机的硬件基于计算机组成原理,计算机之间通过计算机网络连接,这就是四个的关系。计算机组成原理是关于计算机硬件的最底层知识。
- 计算机系统=硬件+软件。
- 软件分为:
- 系统软件:用来管理整个计算机系统,如操作系统、数据库管理系统($DBMS$)、标准程程序库、网络软件、语言处理程序、服务程序。
- 应用软件:按任务需要编制成的各种程序。
计算机的发展
硬件的发展
| 发展阶段 | 时间 | 逻辑元件 | 速度(次/秒) | 内存 | 外存 |
|---|---|---|---|---|---|
| 第一代 | 1946-1957 | 电子管 | 几千-几方 | 汞延迟线、磁鼓 | 穿孔卡片、纸带 |
| 第二代 | 1958-1964 | 晶体管 | 几万-几干万 | 磁芯存储器 | 磁带 |
| 第三代 | 1964-1971 | 中小规模集成电路 | 几十万-几百万 | 半导体存储器 | 磁带、磁盘 |
| 第四代 | 1972-现在 | 大规模、超大规模集成电路 | 上千万-万亿 | 半导体存储器 | 磁盘、磁带、光盘、半导体存储器 |
- 第一代使用纸带磁带编程。
- 第二代出现了面向过程的程序设计语言$FORTRAN$,有了操作系统雏形。
- 第三代主要用于科学计算等专业用途,高级语言快速发展,开始有了分时系统。
- 第四代开始出现$CPU$、$PC$,如$Windows$、$MacOS$等。
软件的发展
- 机器语言:唯一可以被计算机识别和执行的语言。
- 汇编语言:必须经过汇编程序的翻译。
- 高级语言:转换为汇编程序或直接翻译为机器语言。
计算机的分类
电子计算机分为:
- 电子模拟计算机。
- 电子数字计算机:
- 专用计算机。
- 通用计算机:巨型机、大型机、中型机、小型机、微型机、单片机。
按指令和数据流分为:
- 单指令流和单数据流系统($SISD$),即传统冯·诺依曼体系结构。
- 单指令流和多数据流系统($SIMD$),包括阵列处理器和向量处理器系统。
- 多指令流和单数据流系统($MISD$),这种计算机实际上不存在。
- 多指令流和多数据流系统($MIMD$),包括多处理器和多计算机系统。
计算机系统层次结构
计算机结构
冯诺伊曼结构
- 计算机由五大部件组成:
- 输入设备:将信息转换成机器能识别的形式。
- 存储器:存放数据和程序。
- 运算器:算术运算和逻辑运算。
- 控制器:协调其他部件与解析存储器中的程序或指令。
- 输出设备:将结果转换为人类熟悉的形式。
- 指令和数据以同等地位存于存储器,可按地址寻访。
- 指令和数据用二进制表示。
- 指令由操作码(指令序列号,用来表示处理的指令)和地址码(操作数据存储的地址)组成。
- 存储程序:指将指令以二进制代码的形式事先输入计算机的主存储器,然后按其在存储器中的首地址执行程序的第一条指令,以后就按该程序的规定顺序执行其他指令,直至程序执行结束。
- 以运算器为中心:输入/输出设备与存储器之间的数据传送通过运算器完成。
现代计算机结构
- 计算机由两个部分组成:
- 主机:
- $CPU$:
- 运算器。
- 控制器。
- 主存。
- $CPU$:
- $I/O$设备:
- 辅存。
- 输入设备。
- 输出设备。
- 主机:
- 以存储器为中心。
硬件构成
I/O设备
- 输入设备:将程序和数据以机器所能识别和接受的信息形式输入计算机。
- 输出设备:将计算机处理的结果以人们所能接受的形式或其他系统所要求的信息形式输出
存储器
用于存放程序和数据。分为主存和辅存,$CPU$直接访问主存,只有调入主存才能被$CPU$访问。
存储器结构:
- 存储体:存储数据的主体,按地址存取。(相联存储器按内容访存)
- $MAR$:($Memory,Address,Register$)存储地址寄存器,用于访存地址,经过存放地址译码后所选的存储单元。$MAR$位数反映存储单元个数的幂值,与$PC$(程序计数器)长度相等(因为都是指向内存地址),如$MAR$有$10$位,则有$2^{10}=1024$个存储单元。
- $MDR$:($Memory,Data,Register$)存储数据寄存器,用于暂存要从存储器中读或写的信息。$MDR$位数=存储字长,一般为字节的二次幂的整数倍。(存储字长不等于数据字长,数据字长是数据总线一次性传输的信息位数)
- 时序控制逻辑:产生存储器操作时所需的各种时序信号。
存储器相关概念:
- 现代$MAR$和$MDR$被划分进入了$CPU$。
- 存储单元:每个存储单元存放一串二进制代码。
- 存储字($word$):存储单元中二进制代码的组合,即一个存储元存放的数据。
- 存储字长:存储单元中二进制代码的位数,为$1B$或字节的偶数倍。
- 存储元:即存储二进制的电子元件,每个存储元可存储$1bit$。
寄存器和高速缓冲寄存器$Cache$都集成在$CPU$上,离$CPU$越近速度越快,所以存取速度上寄存器>$Cache$>内存。
$CPU$包括运算器和控制器,不包括存储器(存储器不同于寄存器)。
运算器
用于算术运算和逻辑运算。
- $ALU$:算术逻辑单元,是核心单元,通过内部复杂的电路实现算数运算、逻辑运算。
- $ACC$:累加器,用于存放操作数,或运算结果。
- $MQ$:乘商寄存器,在乘、除运算时,用于存放操作数或运算结果。
- $X$:操作数寄存器,通用的操作数寄存器,用于存放操作数。
- $PSW$:程序状态寄存器,也称标志寄存器,用于存放$ALU$运算得到的一些标志信息或处理机的状态信息,如结果是否溢出、有无产生进位或借位、结果是否为负等。
- 还有变址寄存器($IX$),主要用于存放存储单元在段内的偏移量,基址寄存器($BR$),用来存放操作数或中间结果,以减少对存储器的访问次数的数据寄存器。这些都不一定具备。
| 加 | 减 | 乘 | 除 | |
|---|---|---|---|---|
| ACC | 被加数、和 | 被减数、差 | 乘积高位 | 被除数、余数 |
| MQ | 乘数、乘积低位 | 商 | ||
| X | 加数 | 减数 | 被乘数 | 除数 |
控制器
- $CU$:控制单元,分析指令,给出控制信号。
- $IR$:指令寄存器,存放当前执行的指令,内容来自主存的$MDR$。指令中操作码$OP(IR)$送到$CU$分析指令并发出各种微操作命令序列,地址码$Ad(IR)$送到$MAR$来取操作数。
- $PC$:程序计数器,存放下一条要指令地址,取指后自动加$1$(一条指令长度,不一定为$1$)以形成下一条指令地址的功能,与主存$MAR$有直接通路。
计算机工作过程
- 把程序和数据装入主存储器。
- 将源程序转成可执行问题。
- 从可执行文件的首地址开始逐条执行指令。
完成一条指令:
- 取指令$PC\rightarrow MAR\rightarrow M\rightarrow MDR\rightarrow IR$,并调用$PC$加$1$。
- 将指令放入$IR$,并分析指令$OP(IR)\rightarrow CU$。
- 调用$CU$协同执行指令$Ad(IR)\rightarrow MAR\rightarrow M\rightarrow MDR\rightarrow ACC$。
其中翻译包括:预处理;编译;汇编;链接四个阶段。
如何区分指令和数据:
- 通过不同的时间段来区分指令和数据,即在取指令阶段(或取指微程序)取出的为指令,在执行指令阶段(或相应微程序)取出的即为数据。
- 如果通过地址来源区分,由$PC$提供存储单元地址的取出的是指令,由指令地址码部分提供存储单元地址的取出的是操作数。
已知:
int a=2,b=3,c=1,y=0;
int main(){
y=a*b+c;
}
根据计算过程,$a$乘$b$再加上$c$,简单的高级语言背后的转换为机器语言就变成:
指令地址:
| 主存地址 | 操作码 | 地址码 | 注释 |
|---|---|---|---|
| 0 | 000001 | 0000000101 | 取数a至ACC |
| 1 | 000100 | 0000000110 | 乘b得ab,存于ACC中 |
| 2 | 000011 | 0000000111 | 加c得ab+c,存于ACC中 |
| 3 | 000010 | 0000001000 | 将ab+c,存于主存单元 |
| 4 | 000110 | 0000000000 | 停机 |
数据地址,基本的赋值操作:
| 主存地址 | 数据值 | 注释 |
|---|---|---|
| 5 | 0000000000000010 | a=2 |
| 6 | 0000000000000011 | b=3 |
| 7 | 0000000000000001 | c=1 |
| 8 | 0000000000000000 | y=0 |
分析指令地址对应的五条指令执行流程,基本流程是取指令->获取指令操作码->获取指令地址->根据指令地址取数->放入寄存器->计算:
- 执行地址为$0$的指令,取指令$1$到$4$,分析指令$5$,执行指令$6$到$8$:
- $(PC)=0$:程序计数器指向主存地址为$0$的指令。
- $(PC)\rightarrow MAR$,$(MAR)=0$:程序计数器将当前指向的指令地址通过系统总线传输给存储地址寄存器,从而存储地址寄存器的值现在置为$0$,表示它要处理的指令的主存地址为$0$。
- $M(MAR)\rightarrow MDR$,$(MDR)=000001,0000000101$:根据存储地址寄存器存储的地址$0$,在存储体中找到对应的指令$000001,0000000101$,并把它放入存储数据寄存器中。
- $(MDR)\rightarrow IR$,$(IR)=000001,0000000101$:将存储数据寄存器存储的指令放入指令寄存器中。
- $OP(IR)\rightarrow CU$:将指令寄存器的$000001$操作码传输给控制单元中,控制单元根据操作码知道这是“取数”的指令。
- $AD(IR)\rightarrow MAR$:控制单元根据取数指令,明白要从存储器中取出一个数,而取出的数的地址就是指令寄存器的$0000000101$,即取出存储地址为$5$的数据$a=2$,将这个地址交给存储地址寄存器负责取出。
- $M(MAR)\rightarrow MDR$,$(MDR)=000000,0000000010=2$:存储体根据存储地址寄存器的$0000000101$地址找到地址为$5$的数据$2$,并把$2$放到存储数据寄存器中。
- $(MDR)\rightarrow ACC$,$(ACC)=000000,0000000010=2$:将存储数据寄存器的数据放入累加器中,完成$0$这条指令。
- 执行地址为$1$的指令,取指令$1$到$4$,分析指令$5$,执行指令$6$到$10$:
- $(PC)+=1,(PC)=1$:程序计数器完成一条指令自动加一,从而现在指向地址为$1$的指令。
- $(PC)\rightarrow MAR,(MAR)=1$:存储地址寄存器的值现在置为$1$,表示它要处理的指令的主存地址为$1$。
- $M(MAR)\rightarrow MDR,(MDR)=000100,0000000110$:在存储体中找到地址为$1$的指令$000100,0000000110$,并把它放入存储数据寄存器中。
- $(MDR)\rightarrow IR,(IR)=000100,0000000110$:将存储数据寄存器存储的指令放入指令寄存器中。
- $OP(IR)\rightarrow CU$:将指令寄存器的$000100$操作码传输给控制单元中,控制单元根据操作码知道这是“乘法”的指令。
- $AD(IR)\rightarrow MAR$:控制单元根据乘法指令,在第一条指令中就得到了其中一个乘数a,现在要另一个乘数$b$,$b$的地址就是指令寄存器的$0000000110$,即取出存储地址为$6$的数据$b=3$,将这个地址交给存储地址寄存器负责取出。
- $M(MAR)\rightarrow MDR$,$(MDR)=000000,0000000011=3$:存储体根据存储地址寄存器的$0000000110$地址找到地址为$6$的数据$3$,并把$3$放到存储数据寄存器中。
- $(MDR)\rightarrow MQ$,$(MQ)=000000,0000000011=3$:由于需要乘法操作,所以将存储数据寄存器的$3$放入乘商寄存器中。
- $(ACC)\rightarrow X$,$(X)=2$:然后把$a$的值从累加器中放入通用寄存器中。(乘法操作中被乘数放入通用寄存器中,而乘数放入乘商寄存器中)。
- $(MQ)*(X)\rightarrow ACC$,$(ACC)=6$:控制单元通知算术逻辑单元,通过算术逻辑单元对ab进行相乘后放入累加器中。如果乘积太大则需要乘商寄存器辅助存储。
- 执行地址为$2$的指令,取指令$1$到$4$,分析指令$5$,执行指令$6$到$9$:
- $(PC)+=1$,$(PC)=2$:程序计数器完成一条指令自动加一,从而现在指向地址为2的指令。
- $(PC)\rightarrow MAR$,$(MAR)=2$:存储地址寄存器的值现在置为$2$,表示它要处理的指令的主存地址为2。
- $M(MAR)\rightarrow MDR$,$(MDR)=000011,0000000111$:根据存储地址寄存器存储的地址$2$,在存储体中找到对应的指令$000011,0000000111$,并把它放入存储数据寄存器中。
- $(MDR)\rightarrow IR$,$(IR)=000011,0000000111$:将存储数据寄存器存储的指令放入指令寄存器中。
- $OP(IR)\rightarrow CU$:将指令寄存器的$000011$操作码传输给控制单元中,控制单元根据操作码知道这是“加法”的指令。
- $AD(IR)\rightarrow MAR$:控制单元根据取数指令,从存储器中取出指令寄存器的地址为$0000000111$的数,即取出存储地址为$7$的数据$c=1$,将这个地址交给存储地址寄存器负责取出。
- $M(MAR)\rightarrow MDR$,$(MDR)=000000,0000000001=1$:存储体根据存储地址寄存器的$0000000111$地址找到地址为$7$的数据$1$,并把$1$放到存储数据寄存器中。
- $(MDR)\rightarrow X,X=000000,0000000001=1$:将存储数据寄存器的$1$放入通用寄存器中。(即加法操作中累加器存放被加数,通用寄存器中存放加数)。
- $(ACC)+(X)\rightarrow ACC$,$(ACC)=7$:控制单元通知算术逻辑单元,将$ab$与$c$相加并存回累加器中。
- 执行地址为$3$的指令,取指令$1$到$4$,分析指令$5$,执行指令$6$到$8$:
- $(PC)+=1$,$(PC)=3$:程序计数器完成一条指令自动加一,从而现在指向地址为3的指令。
- $(PC)\rightarrow MAR$,$(MAR)=3$:存储地址寄存器的值现在置为3,表示它要处理的指令的主存地址为$3$。
- $M(MAR)\rightarrow MDR$,$(MDR)=000010,0000001000$:根据存储地址寄存器存储的地址$3$,在存储体中找到对应的指令$000010,0000001000$,并把它放入存储数据寄存器中。
- $(MDR)\rightarrow IR$,$(IR)=000010,0000001000$:将存储数据寄存器存储的指令放入指令寄存器中。
- $OP(IR)\rightarrow CU$:将指令寄存器的000010操作码传输给控制单元中,控制单元根据操作码知道这是“存数”的指令。
- $AD(IR)\rightarrow MAR$,$(MAR)=0000001000=8$:控制单元根据存数指令,根据存储的目的地址为$0000001000$。
- $(ACC)\rightarrow MDR$,$(MDR)=7$:将运算的结果从累加器中传输为存储数据寄存器中。
- $(MDR)\rightarrow$:地址为$8$的存储单元,$y=7$:控制单元根据控制总线,将存储数据寄存器中的数据存储到存储地址寄存器中所指向的地址,所以地址码为$8$的$y$的值就变成了$7$。
- 执行地址为$4$的指令,取指令$1$到$4$,分析指令$5$,执行指令$6$:
- $(PC)+=1$,$(PC)=4$:程序计数器完成一条指令自动加一,从而现在指向地址为$4$的指令。
- $(PC)\rightarrow MAR$,$(MAR)=4$:存储地址寄存器的值现在置为$4$,表示它要处理的指令的主存地址为4。
- $M(MAR)\rightarrow MDR$,$(MDR)=000110,0000000000$:根据存储地址寄存器存储的地址$4$,在存储体中找到对应的指令$000110,0000000000$,并把它放入存储数据寄存器中。
- $(MDR)\rightarrow IR$,$(IR)=000110,000000000$:将存储数据寄存器存储的指令放入指令寄存器中。
- $OP(IR)\rightarrow CU$:将指令寄存器的$000110$操作码传输给控制单元中,控制单元根据操作码知道这是“停机”的指令。
- 操作系统通过中断指令停止程序。
计算机层次
- 微程序机器(微指令系统):由硬件直接执行微指令。
- 传统机器(用机器语言的机器):执行二进制机器指令,微程序解释机器执行微指令。
- 虚拟机器(操作系统机器):由操作系统程序实现,向上提供“广义指令”即系统调用。也称为混合层。
- 虚拟机器(汇编语言机器):不能直接运行汇编语言,必须用汇编程序(汇编器)翻译成机器语言程序,所以是虚拟的。汇编语言与机器语言一一对应。
- 虚拟机器(高级语言机器):面向用户,必须用编译程序(编译器或解释器)翻译成汇编语言程序。
- 翻译程序:是指把高级语言源程序转换成机器语言程序(目标代码)的软件。包括编译程序和解释程序。
- 编译程序:将高级语言编写的源程序全部语句一次全部翻译成目标机器语言程序,而后再执行机器语言程序(只需翻译一次)。如$C$和$CPP$。
- 解释程序:将源程序的一条语句翻译成对应于机器语言的语句,并立即执行。紧接着再翻译下一句(每次执行都要翻译),所以不会形成目标程序文件。如$JS$和$Python$。
- 对于$Java$既可以编译也可以解释。
计算机性能指标
主存储器
- $MAR$位数反映内存(而不是外存)单元的个数(最多支持多少个,如果超过了则$MAR$也无法寻址到,等同于没用),反映计算机处理能力和并行能力。
- $MDR$位数=存储字长=每个存储单元的大小,反映计算机一次性处理的数据大小。
- 总容量=存储单元个数×存储字长。单位为$bit$。
- 如$MAR$为$32$位,$MDR$为$8$位,总容量=$2^{32}\times8\div8=3^{32}Byte$,所以为$4GB$。
中央处理器
- $CPU$主频:$CPU$内数字脉冲信号振荡的频率。单位为赫兹。
- $CPU$时钟周期:$CPU$主频(时钟主频)=$1\div CPU$时钟周期。一个时钟多少秒。单位为纳秒或微秒。
- $CPI$($Clock,cycle,Per,Instruction$):执行一条指令所需的时钟周期数。
- 执行一条指令的耗时= $CPI\times CPU$时钟周期。
- $CPU$执行时间(整个程序的耗时)=$CPU$时钟周期数÷主频=(指令条数×
CPI)÷主频。 - $IPS$($Instructions,Per,Second$):每秒执行多少条指令,$IPS$=主频÷平均$CPI$。也可以得到$MIPS$即每秒执行多少百万条指令。由于每个机器有不同指令集,所以使用此进行性能比较有缺陷。
- 指令周期:一条指令需要多少秒,为$1/IPS$。
- $FLOPS$($Floating-point,Operations,Per,Second$):每秒执行多少次浮点运算。因此有$MFLOPS$、$GFLOPS$、$TFLOPS$、$EFLOPS$、$ZFLOPS$分别代表每秒$10^6$、$10^9$、$10^{12}$、$10^{15}$、$10^{18}$、$10^{21}$次浮点运算
系统整体
- 数据通路带宽:数据总线一次所能并行传送信息的位数(各硬件部件通过数据总线传输数据)。
- 吞吐量:指系统在单位时间内处理请求的数量。它取决于信息能多快地输入内存,$CPU$能多快地取指令,数据能多快地从内存取出或存入,以及所得结果能多快地从内存送给一台外部设备。这些步骤中的每一步都关系到主存,因此,系统吞吐量主要取决于主存的存取周期。
- 响应时间:指从用户向计算机发送一个请求,到系统对该请求做出响应并获得它所需要的结果的等待时间。通常包括$CPU$时间(运行一个程序所花费的时间)与等待时间(用于磁盘访问、存储器访问、$I/O$操作、操作系统开销等时间)。
- 基准程序是用来测量机算机处理速度的一种实用程序,以便于被测量的计算机性能可以与运行相向程序的其它计算机性能进行比较。但是也存在缺陷,因为基准程序的性能可能与某一段代码密切相关从而对此进行特别优化。
专业术语
- 系列机。具有基本相同的体系结构,使用相同基本指令系统的多个不同型号的计算机组成的一个产品系列。其特征就是指令系统向后兼容。
- 兼容。指计算机软件或硬件的通用性,即使用或运行在某个型号的计算机系统中的硬件/软件也能应用于另一个型号的计算机系统时,称这两台计算机在硬件或软件上存在兼容性。
- 软件可移植性。指把使用在某个系列计算机中的软件直接或进行很少的修改就能运行在另一个系列计算机中的可能性。
- 固件。将程序固定在$ROM$中组成的部件称为固件。固件是一种具有软件特性的硬件,固件的性能指标介于硬件与软件之间,吸收了软/硬件各自的优点,其执行速度快于软件,灵活性优于硬件,是软/硬件结合的产物。例如,目前操作系统已实现了部分固化(把软件永恒地存储于只读存储器中)。
汇编语言
概念
定义
在计算机组成原理中会涉及汇编语言的基本指令,需要对基本命令有基本的掌握。
机器语言是机器指令的集合。这些机器指令本质上就是由一组0和1组成的命令,是CPU唯一能解释执行的命令。
机器语言和高级语言之间的就是汇编语言,汇编语言是人类可以理解的语言。
汇编语言的主体是汇编指令,汇编指令和机器指令的差别在于指令的表示方法上,汇编指令是机器指令的助记符,汇编指令是更便于记忆的一种书写格式。它较为有效地解决了机器指令编写程序难度大的问题,汇编语言与人类语言更接近,便于阅读和记忆。使用编译器,可以把汇编程序转译成机器指令程序。
1000100111011000
MOV AX,BX
汇编语言不区分大小写。
表示把寄存器BX的内容移动到寄存器$AX$中。
运行流程
这就需要对汇编程序进行编译,即将其翻译成由机器指令组成的机器码,如此计算机就能执行汇编程序上。本质上,计算执行的仍然是机器指令,而非汇编指令。对更多高级的编程语言(比如$C++$等),使用高级语言编写的程序同样需要先转译成汇编程序,再编译成机器码,从而进一步地在机器上运行。这个过程,即编译过程。
把机器码程序(机器指令程序)转译成汇编指令,即反编译过程。
汇编语言组成
- 汇编指令:与机器指令一一对应,它是机器码的助记符,汇编后变成机器语言可以被计算机$CPU$执行。
- 伪指令:由编译器识别执行,用于指示汇编程序如何汇编源程序,也称为命令语句,编译结束后伪指令失去作用,计算机$CPU$不执行。
- 其它符号:如+、-等,由编译器识别执行,没有对应机器码。
汇编语言的核心是汇编指令,汇编指令决定了汇编程序的特性。
8086CPU
是一个由$Intel$于$1978$年所设计的$16$位微处理器芯片,是$x86$架构的鼻祖。
寄存器
$8086CPU$有$14$个$16$位寄存器,既能处理$16$位数据,也能处理$8$位数据,分别为:
通用寄存器,用来存放一般性数据:
- 数据寄存器:一般用于存放参与运算的操作数或运算结果。分为两个字节,高位和低位,其中高位为$\cdots H$,低位为$\cdots L$,如$AX$分为两个八位的$AH$和$AL$:
- $AX$($Accumulator$):累加器。用该寄存器存放运算结果,可提高指令的执行速度。此外,所有的$I/O$指令都使用该寄存器与外设端口交换信息。
- $BX$($Base$):基址寄存器。$8086/8088CPU$中有两个基址寄存器$BX$和$BP$。$BX$用来存放操作数在内存中数据段内的偏移地址,$BP$用来存放操作数在堆栈段内的偏移地址。
- $CX$($Counter$):计数器。在设计循环程序时,使用该寄存器存放循环次数,可使程序指令简化,有利于提高程序的运行速度。
- $DX$($Data$):数据寄存器。在寄存器间接寻址的$I/O$指令中存放$I/O$端口地址;在做双字长乘除法运算时,$DX$与$AX$一起存放一个双字长操作数,其中$DX$存放高$16$位数。
- 地址指针寄存器:
- $SP$($Stack,Pointer$):堆栈指针寄存器。在使用堆栈操作指令($PUSH$或$POP$)对堆栈进行操作时,每执行一次进栈或出栈操作,系统会自动将$SP$的内容减$2$或加$2$,以使其始终指向栈顶。其中$ESP$($Extended Stack Pointer$)为扩展栈指针寄存器,是$32$位寄存器,用于存放函数栈顶指针。
- $BP$($Base,Pointer$):基址寄存器。作为通用寄存器,它可以用来存放数据,但更经常更重要的用途是存放操作数在堆栈段内的偏移地址。$EBP$($Extended Base Pointer$),扩展基址指针寄存器,也被称为帧指针寄存器,是$32$位寄存器,用于存放函数栈底指针。
- 变址寄存器:
- $SI$($Source,Index$):源变址寄存器。存放源串在数据段内的偏移地址。
- $DI$($Destination,Index$):目的变址寄存器。存放目的串在附加数据段内的偏移地址。
段寄存器,为了对$1M$个存储单元进行管理,$8086/8088CPU$对存储器进行分段管理,即将程序代码或数据分别放在代码段、数据段、堆栈段或附加数据段中,每个段最多可达$2^{16}=64K$个存储单元。段地址分别放在对应的段寄存器中,代码或数据在段内的偏移地址由有关寄存器或立即数给出:
- $CS$($Code,Segment$):代码段寄存器。用来存储程序当前使用的代码段的段地址。$CS$的内容左移四位再加上指令指针寄存器$IP$的内容就是下一条要读取的指令在存储器中的物理地址。
- $SS$($Stack,Segment$):堆栈段寄存器。用来存储程序当前使用的堆栈段的段地址。堆栈是存储器中开辟的按先进后出原则组织的一个特殊存储区,主要用于调用子程序或执行中断服务程序时保护断点和现场。
- $DS$($Data,Segment$):数据段寄存器。用来存储程序当前使用的数据段的段地址。$DS$的内容左移四位再加上按指令中存储器寻址方式给出的偏移地址即得到对数据段指定单元进行读写的物理地址。
- $ES$($Extra,Segment$):附加数据段寄存器。用来存储程序当前使用的附加数据段段的段地址。附加数据段用来存放字符串操作时的目的字符串。
| 段寄存器 | 提供段内偏移地址的寄存器 |
|---|---|
| CS | IP |
| DS | BX、SI、DI或一个16位数 |
| SS | SP或BP |
| ES | DI(用于字符串操作指令) |
控制寄存器:用于存放控制数据:
- $IP$($Instruction,Pointer$):指令指针寄存器。用来存放下一条要读取的指令在代码段内的偏移地址。用户程序不能直接访问$IP$。
- $FLAGS$:标志寄存器,也称为程序状态寄存器($PSW$)。它是一个$16$位的寄存器,但只用了其中的$9$位,包括$6$个状态标志位和$3$个控制标志位。
$FLAGS$标志位的索引从0到15,则:
状态标志位,用于查看运算数据状态:
| 简写 | 英文名称 | 中文名称 | 索引 | 描述 | 用途 |
|---|---|---|---|---|---|
| CF | Carry Flag | 进位标志位 | 0 | 当进行加减运算时 ,若最高位发生进位或借位,则CF为1,否则为0 | 判断十六位无符号数运算结果是否超出了计算机所能表示的无符号数的范围 |
| PF | Parity Flag | 奇偶标志位 | 2 | 当指令执行结果的低8位中含有偶数个1时,PF为1,否则为0 | 奇偶校验 |
| AF | Auxiliary Flag | 辅助进位标志位 | 4 | 当执行一条加法或减法运算指令时,若结果的低字节的低4位向低字节的高4位(0-3向4-7)有进位或借位,则AF为1,否则为0 | 判断四位无符号数运算结果是否超出了计算机所能表示的无符号数的范围,如BCD码 |
| ZF | Zero Flag | 零标志位 | 6 | 若当前的运算结果为0,则ZF为1,否则为0 | 快速判断0 |
| SF | Sign Flag | 符号标志位 | 7 | 若运算结果的最高位为1,SF=1,否则为0 | 快速获取符号 |
| OF | Overflow Flag | 溢出标志位 | 11 | 当运算结果超出了带符号数所能表示的数值范围,即溢出时,OF=1,否则为0 | 该判断带符号数运算结果是否溢出 |
控制标志位有三个,用于控制CPU的操作,由程序设置或清除:
| 简写 | 英文名称 | 中文名称 | 索引 | 描述 | 用途 |
|---|---|---|---|---|---|
| TF | Trap Flag | 跟踪(陷阱)标志位 | 8 | 若将TF置1,CPU处于单步工作方式,每执行一条指令后产生一次单步中断 | 简化测试程序 |
| IF | Interrupt Flag | 中断允许标志位 | 9 | 若将IF置1,表示允许CPU接受外部从INTR引脚上发来的可屏蔽中断请求;若用CLI指令将IF清0,则禁止CPU接受可屏蔽中断请求信号 | 控制CPU是否可以响应CPU外部的可屏蔽状态中断请求 |
| DF | Direction Flag | 方向标志位 | 10 | 若将DF置为1,串操作按减地址方式进行,也就是说,从高地址开始,每操作一次地址自动递减;否则按增地址方式进行 | 串操作时控制数据计算的方向 |
寻址
有$16$根数据线和$20$根地址线。
由于$20$根地址线,所以可寻址的内存空间为$2^{20}=1MB$。但是由于数据线只有$16$位,所以$CPU$内部只能传输$16$位的地址。所以为了解决这个问题,$8086CPU$使用了两个十六位地址通过地址加法器合成一个二十位物理地址的方式。
其中定义第一个地址为段地址,第二个地址为偏移地址,即物理地址=段地址$\times16+$偏移地址。
如传入两个十六位地址,$1230H$和$00C8H$,然后$1230H\times16=12300H$(乘其对应的进制数等价于左移一位),然后$12300H+00C8=123C8$得到最终地址。
所以给定一个段地址,仅通过变化$n$位的偏移地址来寻址最多可以定位$2^n$个内存单元。
工作流程
在$8086CPU$加电启动或复位后(即$CPU$刚开始工作时)$CS$被设置为$FFFFH$,而$IP$被设置为$0000H$,即在$8086PC$机刚启动时,$CPU$使用地址加法器从内存$FFFF\times10H+0000H=FFFF0H$单元中读取指令执行,$FFFF0H$单元中的指令是$8086PC$机开机后执行的第一条指令。
- 在$CPU$中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对$CPU$的控制。
- $CPU$从何处执行指令是由$CS$、$IP$中的内容决定的,程序员可以通过改变$CS$、$IP$中的内容来控制$CPU$执行目标指令。
- 但是不能直接改变$CS$、$IP$寄存器的值,所以$8086CPU$使用转移指令来进行设置
JMP 段地址:偏移地址。 - 其中段地址用来修改$CS$,偏移地址用来修改$IP$。如果仅修改$IP$内容,可以使用
JMP 寄存器地址,如JMP AX类似于MOV IP,AX。
环境
下载
首先下载DOSBox,下载后点击安装。DOSBox即通过软件让在不同的系统如Windows、Linux、MacOS等运行DOS程序,直接操作硬件。
然后下载MASM5.0,将压缩包解压并把对应文件夹放到指定位置。宏汇编程序$MASM$是具有宏加工功能的汇编程序。可以用它定义含参数的程序段,在使用的位置上调用它们,汇编时将进行宏指令展开,把宏定义所预先定义的指令目标代码插在该位置上。
挂载
完成后打开$DOSBox$的$DOSBox.exe$程序。
由于这个软件的起始目录是$Z$,并没有我们电脑上的任何本地目录,所以我们需要首先将我们自己的目录给挂载到$DOSBox$系统中。
需要将$MASM$目录挂载在$DOSBox$中,输入MOUNT 挂载虚拟盘符 MASM5.0安装地址(命令不区分大小写)。如我的$MASM5.0$安装在$D$盘的$MASM$目录下,要挂载到$D$盘中运行,就要在$DOSBox$控制台中输入MOUNT D: D://MASM。如果挂载成功会显示Drive\,D\,is\,mounted\,as\,local\,directory\,D://MASM\\
然后查看部署是否成功,D:、dir,如果显示列表中出现了$DEBUG$就代表MASM挂载成功,可以使用里面的$DEBUG$进行操作。
然后挂载成功后输入D:进入挂载地址,输入DEBUG就可以开始使用$DEBUG$了(汇编程序执行器)。
此后每次使用$DOSBox$就必须挂载$MASM$,非常麻烦,可以在配置文件中配置挂载命令。
找到$DOSBox,0.74-3,Options.bat$,双击打开配置文件(注意不要直接修改bat文件),在末尾添加:
MOUNT D: D://MASM
D:
就相当于默认挂载,并转到虚拟盘符地址,点击$DOSBox.exe$可以直接使用相关工具了。
- $EDIT$编辑器。
- $MASM$编译器。
- $LINK$连接器。
- $DEBUG$调试器。
DEBUG
- $R$命令查看、改变$CPU$寄存器的内容。
R查看所有寄存器内容。R 寄存器名查看指定寄存器内容。然后下一行弹出一个:,后面可以添加值,从而能修改指定寄存器内容。
- $D$命令查看内存中的内容。其中左边为十六进制值,右边为对应ASCII值。
D 段地址:偏移地址:查看指定段地址与偏移地址所指向的内存后128个内存单元中内容。D 段地址:偏移地址 数量:查看指定段地址与偏移地址所指向的内存后指定数量的内存单元中的内容。
- $E$命令改写内存中的内容。
E 段地址:偏移地址 数据列表:从指定段地址与偏移地址所指向的内存开始修改内容为数据列表。数据列表中间以空格分隔。E 段地址:偏移地址:不表明数据列表,则控制台会主动给你提供从该地址开始的原数据,可以在后面输入新数据,输入空格后会继续弹出下个地址的原数据,回车结束输入执行修改。E 段地址:偏移地址 "字符串":从指定段地址与偏移地址所指向的内存开始保存字符串值,对应的ASCII码自动转换并保存到内容中。
- $U$命令将内存中的机器指令翻译成汇编指令。
U 段地址:偏移地址。结果分别为地址、机器指令、汇编指令、说明。 - $A$命令以汇编指令的格式在内存中写入一条机器指令。
A 段地址:偏移地址。从该地址开始输入汇编指令保存到对应内存。 - $T$命令执行一条机器指令。
T 段地址:偏移地址。从该地址执行保存的汇编指令。
汇编指令
数据传输指令
通用数据传送指令:
- $MOV$:
- 格式:
MOV 目的,源。 - 功能:把源操作数赋值传送给目的操作数,源操作数不变。
- 要求:目的和源可以为数据也可以为地址。
- 禁止操作:向段寄存器写入立即数(必须通过通用寄存器中转);$CS$作为目标寄存器;目的地址与源地址同时为存储单元或段寄存器。
- 格式:
- $MOVSX$:
- 格式:
MOVSX 目的寄存器,源操作数。 - 功能:把源操作数先进行符号拓展(为正数则高位补0,负数则高位补1),再赋值传送给目的操作数,源操作数不变。
- 要求:源操作数字长要小于或等于目标寄存器字长。
- 格式:
- $MOVZX$:
- 格式:
MOVZX 目的寄存器,源操作数。 - 功能:把源操作数先进行零拓展(高位补0),再赋值传送给目的操作数,源操作数不变。
- 要求:源操作数字长要小于或等于目标寄存器字长。
- 格式:
- $LEA$:
- 格式:
LEA 目的寄存器,源操作数。 - 功能:计算内存单元的有效地址(不是其中的操作数)并送到目的寄存器中。
- 说明:有效地址就是偏移地址,LEA指令等效于OFFSET运算符,计算DS段地址加上偏移地址的地址。
- 格式:
- $XCHG$:
- 格式:
XCHG 操作数1,操作数2。 - 功能:对两个操作数进行位置互换。
- 要求:操作数类型需要一致;至少一个为寄存器。
- 禁止操作:操作数为段寄存器或立即数或内存操作数。
- 格式:
- $CMPXCHG$:
- 格式:
CMPXCHG 操作数1,操作数2。 - 功能:比较并对两个操作数进行位置互换。
- 要求:第二个操作数必须为累加器$AL/AX/EAX$。
- 禁止操作:操作数为段寄存器或立即数或内存操作数。
- 格式:
堆栈传输指令:
堆栈操作指令中的操作数类型必须是字操作数,即16位操作数。
- $PUSH$:
- 格式:
PUSH 源地址。 - 功能:将字压入堆栈。
- 说明:一个字进栈,系统自动完成两步操作:$SP\leftarrow SP-2$,$(SP)\leftarrow$操作数。
- 说明:一个双字进栈,系统自动完成两步操作:$ESP\leftarrow ESP-4$,$(ESP)\leftarrow$操作数。
- 格式:
- $POP$:
- 格式:
POP 目的地址。 - 功能:将字弹出堆栈。
- 说明:弹出一个字,系统自动完成两步操作:操作数$\leftarrow(SP)$,$SP\leftarrow SP+2$。
- 说明:弹出一个双字,系统自动完成两步操作:操作数$\leftarrow(ESP)$,$ESP\leftarrow SP+4$。
- 格式:
- $PUSHA$:
- 格式:
PUSHA。 - 功能:把通用寄存器$AX,CX,DX,BX,SP,BP,SI,DI$依次全部压入堆栈。
- 要求:为$16$位字。
- 格式:
- $POPA$:
- 格式:
POPA。 - 功能:把通用寄存器$DI,SI,BP,SP,BX,DX,CX,AX$依次全部弹出堆栈。
- 要求:为$16$位字。
- 格式:
- $PUSHAD$:
- 格式:
PUSHAD。 - 功能:把拓展通用寄存器$AX,CX,DX,BX,SP,BP,SI,DI$依次全部压入堆栈。
- 要求:为$32$位字。
- 格式:
- $POPAD$:
- 格式:
POPAD。 - 功能:把拓展通用寄存器$DI,SI,BP,SP,BX,DX,CX,AX$依次全部弹出堆栈。
- 要求:为$32$位字。
- 格式:
操作
数据操作
如要读取$10000H$单元的内容可以使用下面的汇编程序:
MOV BX,1000H
MOV DS,BX
MOV AL,[0]
首先我们要读取$10000H$单元的内容,则必须把这个内容移动到段寄存器中然后读取。但是段寄存器不能直接被赋值,必须通过通用寄存器。所以第一步就是将$1000H$移动到基址通用寄存器$BX$中,因为是基址所以将$10000H$右移四位变为$1000H$为基址($10000H$表示欸$1000:0$)。然后将$1000H$基址从基址寄存器中移动到数据段寄存器$DS$中,此时可以通过$DS$读取数据了。其中$[X]$表示内存单元,$X$表示相对于段寄存器地址的偏移量,所以$[0]$的计算后的基址就是$1000H\times16+0=10000H$,然后通过$MOV$命令移动到$AL$累加器寄存器中。移动到$AL$而不是$AX$是因为一个内存单位为一个字节,为八比特,而$8086CPU$一个字为两个字节,所以只用累加器的低位就可以了,即$AL$即可。
所以针对数据传输必然是:数据→通用寄存器→段寄存器。
字操作
由于$8086CPU$数据线为$16$根,所以其字大小为十六位即两个字节。之前数据传输时只是传输一个字节,所以通用寄存器使用$AL$而不是$AX$,而这里要传输字,所以使用一整个通用寄存器。
假如内存情况为:
| 地址 | 数据 |
|---|---|
| 10000H | 23 |
| 10001H | 11 |
| 10002H | 22 |
| 10003H | 66 |
执行下面的汇编程序,请问执行后寄存器会变成什么样:
MOV AX,1000H
MOV DS,AX
MOV AX,[0]
MOV BX,[2]
MOV CX,[1]
ADD BX,[1]
ADD CX,[2]
计算机默认为小端模式,所以高位在高地址,低位在地地址,所以比如要读取10000H指向的字,不能只读一个$23$,因为只有八位而$8086CPU$字长为十六位,所以还要向高位读一个字节,即为$1123$,高地址的在高位,所以$11$在$23$前面。
| 指令 | 寄存器内容变化 | 说明 |
|---|---|---|
| MOV AX,1000H | AX=1000H | 将基址1000H送到累加器中 |
| MOV DS,AX | DS=1000H | 将基址1000H赋值给数据段寄存器 |
| MOV AX,[0] | AX=1123H | 将偏移量为0的数据读取到AX中,多读一个[1]位置 |
| MOV BX,[2] | BX=6622H | 将偏移量为2的数据读取到BX中,多读一个[3]位置 |
| MOV CX,[1] | CX=2211H | 将偏移量为1的数据读取到CX中,多读取一个[2]位置 |
| ADD BX,[1] | BX=8833H | 即将[1]位置的数据与BX数据相加,[1]位置的数据不能是半字,所以必须为十六位,即[2][1]=2211H,相加得到6622H+2211H=8833H |
| ADD CX,[2] | CX=8833H | 同理将[2]位置的数据扩展为[3][2]=6622H,相加2211H+6622H=8833H |
段操作
对于$8086CPU$可以将数据根据需要让一组内存单元定义为一个段,以后操作根据段来进行。
- 组的最大长度为$64K$,因为偏移量为$2^{16}=64K$。
- 地址连续。
- 起始地址为$16$倍数。(即地址的最后一个数字为$0$,如$123B0H$)
将$123B0H\sim123BAH$的内存单元定义为数据段,累加这个数据段中的前三个单元中的数据:
MOV AX,123BH
MOV DS,AX
MOV AL,0
ADD AL,[0]
ADD AL,[1]
ADD AL,[2]
栈操作
$8086CPU$的栈操作都是以字,即十六比特为单位进行。高地址单元放高八位,低地址单元放低八位。
其中$SS$指向栈顶的段地址,$SP$指向栈顶的偏移地址,所以$SS:SP$指向栈顶元素。
如将$10000H\sim1000FH$作为栈,执行下面指令:
MOV AX,0123H
PUSH AX
MOV BX,2266H
PUSH BX
MOV CX,1122H
PUSH CX
POP AX
POP BX
POP CX
$10000H\sim1000FH$作为栈,则栈底为高位$1000FH$,栈底为低位$10000H$,由于需要压入栈一共三个数据六个字节,所以只有$1000AH\sim1000FH$被使用到。
| 指令 | MOV AX,0123H | PUSH AX | MOV BX,2266H | PUSH BX | MOV CX,1122H | PUSH CX | POP AX | POP BX | POP CX |
|---|---|---|---|---|---|---|---|---|---|
| 1000AH | 22 | ||||||||
| 1000BH | 11 | ||||||||
| 1000CH | 66 | 66 | 66 | 66 | |||||
| 1000DH | 22 | 22 | 22 | 22 | |||||
| 1000EH | 23 | 23 | 23 | 23 | 23 | 23 | 23 | ||
| 1000FH | 01 | 01 | 01 | 01 | 01 | 01 | 01 |
$SP$在入栈和出站是会加减二。
又将$10000H\sim1000FH$这段空间当作栈,初始状态是空的。设置$AX=001AH$,$BX=001BH$。将$AX$、$BX$中的数据入栈。然后将$AX$、$BX$清零。最后从栈中恢复$AX$、$BX$原来的内容。
由于$10000H\sim1000FH$为栈,栈底在高位,所以栈为空时栈顶应该初始化赋值为最大值$1000FH$的下一位$SP=0010H$。
MOV AX,1000H
MOV SS,AX
MOV SP,0010H
MOV AX,001AH
MOV BX,001BH
PUSH AX
PUSH BX
SUB AX,AX
SUB BX,BX
POP BX
POP AX
由于$8086CPU$栈空间没有保护,所以可能存在栈顶越界。
栈段操作
可以将字段内存作为栈段,这是用户配置的,CPU并不会把它当作栈,所以我们要让$SS:SP$指向自定义栈段,从而能让栈操作指令能访问我们自己定义的栈段。
由于栈段操作时只需要移动$SP$,所以$SP$的大小决定栈段的最大长度,所以最大长度为$2^{16}=64K$。
如果将$10000H\sim1FFFFH$这段空间作为栈段,初始空间为空,则$SS=1000H$,求$SP$。
由于空间高位为$1FFFFH$作为栈底,所以如果为空,则栈顶应该在$1FFFFH$的更高一位$1FFFFH+1=20000H$。但是给定了栈段空间$10000H\sim1FFFFH$,所以不能取到$SS=2000H$,否则越界。
所以向栈中填充一个元素,所以任意时刻,$SS:SP$指向栈顶,当栈中只有一个元素的时候,$SS=1000H$,$SP=FFFEH$。
如果栈为空,就相当于栈中唯一的元素出栈,出栈后,$SP=SP+2$。$SP$原来为$FFFEH$,SP=FFFEH+2=0$,所以,当栈为空的时候,$SS=1000H$,$SP=0$。
所以$CS:IP$指向哪里,$CPU$就将该段内存当作代码,$SS:IP$指向哪里,$CPU$就将该段内存当作栈。
汇编程序
可以将汇编文件使用编译器编译为可执行文件。编写->编译->连接->执行。
如下面就是一个简单的汇编源程序:
ASSUME CS:codeseg
codeseg SEGMENT
START: MOV AX,0123H
MOV BX,0456H
ADD AX,BX
ADD AX,AX
MOV AX,4C00H
INT 21H
codeseg ENDS
END START
定义段
- $SEGMENT$和$ENDS$是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。
- $SEGMENT$和$ENDS$的功能是定义一个段,$SEGMENT$说明一个段开始,$ENDS$说明一个段结束。($END$可以视为$END,SEGMENT$的简写)
- 一个段必须有一个名称来标识,使用格式为:
段名 SEGMENT、段名 ENDS。 - 一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。
- 一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。
- 段名称最终会被编译、连接程序处理为一个段的段地址。
程序框架
$END$是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令$END$,就结束对源程序的编译。
如果程序写完了,要在结尾处加上伪指令$END$。否则,编译器在编译程序时,无法知道程序在何处结束。
$END$除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。由$START$后面加一个冒号,此后就是汇编指令,即基本的代码段。$END START$即指明入口在$START$位置。
假设
$ASSUME$表示为假设的伪代码,假设某一段寄存器与程序中的某个用$SEGMENT$和$ENDS$定义的段相关联,这样就不用再使用$MOV$指令将段通过通用寄存器移动到段寄存器,从而简化了代码。
如ASSUME CS:codeseg将$codeseg$代码段放到$CS$代码段寄存器中。
程序返回
我们的程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中。
而$DOS$是一个单任务操作系统,所以只能有一个程序运行,所以一个程序结束后,将$CPU$的控制枚交还给使它得以运行的程序,这个过程为程序返回。
返回应该在程序的末尾添加返回的程序段。
如代码的MOV AX,4C00H和INT 21H。这两句话的意思是$DOS$系统功能调$INT 21H$功能中的一种,表示带返回码结束用户程序。$INT 21H$指令中,寄存器$AX$分为$AH$和$AL$两个部分,$AH$中存入指令码$4C$表示带返回码结束,$AL$为程序返回码。用这个指令返回是不需任何条件,还可顺便关闭打开后忘记关闭的文件,并返回寄存器$AL$的值。
| AH | 功能 | 调用参数 |
|---|---|---|
| 00 | 程序终止(同INT 20H) | CS=程序段前缀。 |
| 01 | 键盘输入并回显 | AL=输入字符。 |
| 02 | 显示输出 | DL=输出字符。 |
| 03 | 异步通迅输入 | AL=输入数据。 |
| 04 | 异步通迅输出 | DL=输出数据 |
错误
- 语法错误:程序在编译时被编译器发现的错误,容易被发现。
- 逻辑错误:程序在编译时不能表现出来,在运行时发生的错误,不容易被发现。
编辑
打开DOSBox,挂载后直接运行EDIT命令,打开编辑器,编写:
ASSUME CS:codeseg
codeseg SEGMENT
MOV AX,0123H
MOV BX,0456H
ADD AX,BX
ADD AX,AX
MOV AX,4C00H
INT 21H
codeseg ENDS
END
编写完成偶点击File,选择Save然后输入文件名进行保存。然后点击File的Exit退出编辑。
编译
然后运行MASM,然后需要输入源程序名。如果源程序文件不是以$asm$为扩展名的话,就要输入它的全名,比如$test.txt$。在输入源程序文件名的时候一定要指明它所在的路径,如D:\MASM\test.asm(但是注意此时你已经挂载了$MASM$,所以必须使用dir查看当前文件路径,文件路径直接为$test.asm$)。如果文件就在当前路径下,只输入文件名就可以。
然后出现$Object,filename$,默认是后面括号的内容,如果要修改就输入新名字。
然后是$Source,listing$,表示列表文件,是编译器将源程序编译为目标文件的过程中产生的中间结果。
最后是$Cross-reference$,表示交叉引用文件,这个文件同列表文件一样,是编译器将源程序编译为目标文件过程中产生的中间结果。
也可以直接加上文件地址输入,如masm test.asm。
连接
将生成的$TEST.OBJ$文件连接为可执行的$TEST.exe$文件。
控制台中输入$LINK$进行连接。如果目标文件不是以$obj$为扩展名的话,就要输入它的全名,同理要指明文件路径。也可以直接输入LINK TEST.OBJ。
$Run,file$即表示要生成的可执行文件的名字。$List,file$映像文件是连接程序将目标文件连接为可执行文件过程中产生的中间结果。$Libraries$即库文件,包含了一些可以调用的子程序,如果我们的程序中调用了某一个库文件中的子程序,就需要在连接的时候,将这个库文件和我们的自标文件连接到一起,生成可执行文件。
此时会提示一个警告:$waning L4021: no stack segment$没有栈段,可以不用理会。得到一个TEST.EXE。
运行
由于是可执行文件,所以可以直接在控制台中输入文件名进行执行。
操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统﹐都要提供一个称为$shell$外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统工作。
$DOS$中有一个程序$command.com$,这个程序在$DOS$中称为命令解释器,也就是$DOS$系统的$shell$。
所以就是这个运行的$command$将汇编文件的程序加载进入内存,并设置$CPU$的$CS:IP$指向程序的第一条指令,即程序入口,从而使得程序得以运行。程序运行结束后返回$command$中,$CPU$继续运行$command$。
可以使用DEBUG 程序名的方式对程序进行监控。使用R指令对内存进行查看。可以看到$CX=000F$,即汇编程序一共十五个字节。
高级使用
[BX]
类似于$[0]$表示内存单元的偏移地址,所以$[BX]$也表示一个内存单元,但是偏移地址在$BX$中。
(X)
$(X)$用于表示一个地址或寄存器中的内容。如$(AX)=0010H$,即寄存器$AX$中的内容为$0010H$。如$MOV AX,[2]$等价于$(AX)=((DS)*16+2)$,$ADD AX,2$等价于$(AX)=(AX)+2$。
其中定义$idata$表示常量值的占位,如$MOV AX,[idata]$代表$MOV AX,[$常量$]$。
LOOP
为循环指令,格式:LOOP 标号。$CPU$执行该指令时会进行两步操作:
- $(CX)=(CX)-1$。
- 判断$CX$中的值,里面保存循环次数,不为零则转至标号处执行程序,如果为零打破循环向下执行。
计算$2^{12}$:
ASSUME CS:CODE
CODE SEGMENT
MOV AX,2
MOV CX,11
S: ADD AX,AX
LOOP S
MOV AX,4C00H
INT 21H
CODE ENDS
END
这里的标号$S$就指向的是ADD AX,AX指令。循环执行的语句要在标号和$LOOP$指令之间。
在实际编程中,经常会遇到,用同一种方法处理地址连续的内存单元中的数据的问题。
我们需要用循环来解决这类问题,同时我们必须能够在每次循环的时候按照同一种方法来改变要访问的内存单元的地址。这时,我们就不能用常量来给出内存单元的地址(比如$[0]$、$[1]$是常量),而应用变量。MOV AL,[BX]中的$BX$就可以看作一个代表内存单元地址的变量,我们可以不写新的指令,仅通过改变bx中的数值,改变指令访问的内存单元。
默认地址的段前缀都是放在$DS$中,可以设置不同的段寄存器从而设置不同的段前缀来简化。
多段程序
一个程序不可以只有一个代码段,所以必然存在多个段进行不同的数据操作。
由于$END$和$START$是一组关键字,$END$可以指明程序。
ASSUME 段寄存器名:段名
段名 SEGMENT
……
定义数据
……
START:
……
数据代码
……
段名 ENDS
END START