主页
文章
分类
系列
标签
简历
【x86汇编语言01】 8086体系结构与汇编基础
发布于: 2022-1-13   更新于: 2022-1-13   收录于: 汇编语言
文章字数: 347   阅读时间: 2 分钟   阅读量:

一、基础知识

1、指令

  • 机器指令:CPU能直接识别并执行的二进制编码
  • 汇编指令:汇编指令是机器指令的助记符,同机器指令一一对应。
  • 指令:指令通常由操作码和地址码(操作数)两部分组成
  • 指令集:每种CPU都有自己的汇编指令集。

2、汇编语言程序

2.1 汇编语言由3类指令组成

  • 汇编指令
  • 伪指令:没有对应的机器码,由编译器执行,计算机并不执行
  • 其他符号:如+、-、*、/等,由编译器识别,没有对应的机器码。

2.2 指令代码格式

指令的代码格式(Instruction Format)说明如何用二进制编码指令,也称机器代码(Machine Code)格式,它由操作码和地址码组成

  • 指令的操作码(Opcode)表明处理器执行的操作,例如数据传送、加法运算、跳转等操作
  • 操作数(Operand)是参与操作的数据,也就是各种操作的对象,主要以寄存器或存储器地址、I/O 地址形式指明数据的来源,所以也称为地址码

Pasted image 20230627210309|800

1
2
mov al , [bx+si+6] ;指令功能:AL⬅[BX+SI+6]
				   ;机器代码:8A 40 06 (十六进制表示法)

操作码:“8A” 操作数:“40” ,“06”

2.3 语句格式

  • 执行性语句——表达处理器指令的语句
1
标号:处理器指令助记符 操作数,操作数 ;注释
  • 说明性语句——表达汇编程序命令的语句
1
名字  伪指令助记符 参数,参数,...... ;注释

2.3.1 标号与名字

  • 执行性语句中,冒号前的标号表示处理器指令在主存中的逻辑地址,主要用于指示分支、循环等程序的目的地址,可有可无
  • 说明性语句中的名字可以是变量名、段名、子程序名等,反映变量、段和子程序等的逻辑地址。
  • 标号采用冒号分隔处理器指令,名字采用空格或制表符分隔伪指令,据此也分别了两种语句。
  • 标号和名字是符合汇编程序语法的用户自定义的标识符(Identifier)。标识符(也称为符号Symbol)一般最多由31个字母、数字及规定的特殊符号(如 _、$、?、@)组成,不能以数字开头

2.3.2 助记符与伪指令

在汇编语言中,助记符(Mnemonic)和伪指令(Pseudoinstruction)是两种不同的概念。 1)助记符(Mnemonic):助记符是用于表示机器指令的一种简短的助记名称。它是汇编语言中可识别的文本符号,代表着特定的机器指令操作码。例如,“MOV"代表将数据从一个位置移动到另一个位置的机器指令。总的来说,助记符直接对应机器指令,而伪指令提供了更高级和方便的功能,用于改善程序结构和编译过程。 助记符(Mnemonics)是帮助记忆指令的符号,反映指令的功能。 [[汇编语言#十五、 指令系统总结|助记符总结]]

2)伪指令(Pseudoinstruction):伪指令是在汇编语言中使用的一类特殊指令,其并不直接对应于机器指令,而是由编译器或汇编器根据伪指令的含义来生成一系列真正的机器指令。伪指令通常用于提供汇编程序的组织结构、定义常量、定义变量等。它们更多地是为了方便程序员或者提供编译时的一些辅助功能。

1
msg    db'Hello,asm!',13,10,'$'

名字 MSG 指示这个字符串在主存的逻辑地址,包含有段基地址和偏移地址,也就是这个字符串的变量名。可以用一个MASM操作符OFFSET获得其偏移地址,保存到DX寄存器,汇编语言执行性语句如下:

1
mov dx, offset msg ;DX获得MSG的偏移地址

区别:

  • 助记符表示实际的机器指令,每个助记符对应着特定的操作码。它们直接被处理器执行。
  • 伪指令虽然看起来像指令,但它们并没有直接对应的机器指令。它们是由编译器或汇编器解释并转换成一系列真正的机器指令。

2.3.3 操作数和参数

处理器指令的操作数表示参与操作的对象,可以是一个具体的常量、也可以是保存在寄存器的数据,还可以是一个保存在存储器中的变量等。 双操作数的指令中,目的操作数写在逗号前,还可用来存放指令操作的结果;对应地,逗号后的操作数就称为源操作数。 例如,指令“MOV AH,9”中数字9是常量形式的源操作数,AH是寄存器形式的目的操作数。同样,OFFSET MSG经汇编后转换为一个具体的偏移地址,也是常量。 伪指令的参数可以是常量、变量名、表达式等,可以有多个,参数之间用逗号分隔。例如在“‘Hello, asm !’,13,10, ‘$’”示例中,就用单引号表达了一个字符串“Hello, asm !”,一个字符“$”,还有常量13和10(这两个常量在ASCII码表中表示回车和换行控制字符,其作用相当于C语言的“\n”)。

2.3.4 注释和分隔符

标号后的冒号、注释前的分号以及操作数间和参数间的逗号都是规定采用的分隔符,其他部分通常采用空格或制表符作为分隔符。多个空格和制表符的作用与一个相同。另外,MASM也支持续行符“\”,表示本行内容与上一行内容属于同一个语句。

2.4 编译器

够将汇编指令转换成机器指令的翻译程序每一种CPU都有自己的汇编指令集。
|800
在内存或磁盘上,指令和数据没有任何区别,都是二进制信息

3、存储器

随机存储器(RAM)在程序的执行过程中可读可写,必须带电存储

只读存储器(ROM)在程序的执行过程中只读,关机数据不丢失
|800
|800
|800
(以上3张图片来自王道考研 - 计算机组成原理课件)

4、总线


4.1 总线

总线是连接各个部件的信息传输线,是各个部件共享的传输介质

主板上有核心器件和一些主要器件,这些器件通过总线(地址总线、数据总线、控制总线)相连。这些器件有CPU、存储器、外围芯片组、扩展插槽等。扩展插槽上一般插有RAM内存条和各类接口卡。
来自唐朔飞·计算机组成原理经典课件|800

总线根据位置分类:

  • 片内总线(芯片内部总线)

  • 系统总线(计算机各部件之间的信息传输线)

    根据传送信息的不同,系统总线从逻辑上又分为3类,地址总线、控制总线和数据总线。

CPU要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行以下3类信息的交互。

  1. 地址总线:CPU通过地址总线来指定存储单元
    |800
    1根导线可以传送的稳定状态只有两种,高电平或是低电平。用二进制表示就是1或0

图示有10根地址线即一次可以传输10位,访问存储单元地址为1011,寻址范围为0 ~ (210 - 1)

  1. 数据总线:CPU与内存或其他器件之间的数据传送是通过数据总线来进行的
    |800
    8根数据线一次可传送一个8位二进制数据(即一个字节),传送2个字节需要两次;16根数据线一次可传送2个字节(内存对齐核心原理)

  2. 控制总线:CPU对外部器件的控制是通过控制总线来进行的。

有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。
所以,控制总线的宽度决定了CPU对外部器件的控制能力。

4.2 CPU对存储器的读写

|800
1、 CPU通过地址线将地址信息3发出。
2、 CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据。
3、 存储器将3号单元中的数据8通过数据线送入CPU。写操作与读操作的步骤相似。
联想:在组成原理中用微操作表示:(PC) → MAR; 1 → R; M(MAR) → MDR; …

4.3 CPU对外设的控制

CPU对外设都不能直接控制,如显示器、音箱、打印机等。

直接控制这些设备进行工作的是插在扩展插槽上的接口卡。

扩展插槽通过总线和CPU相连,所以接口卡也通过总线同CPU相连。CPU可以直接控制这些接口卡,从而实现CPU对外设的间接控制。

如:CPU无法直接控制显示器,但CPU可以直接控制显卡,从而实现对显示器的间接控制

5、内存地址空间

CPU将系统中各类存储器看作一个逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间。
对于CPU,所有存储器中的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU寻址能力限制。(或许就是计组中学的统一编址吧)
|800
每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据(对ROM写无效)。
|800

二、寄存器

1、寄存器

CPU由运算器、控制器、寄存器等器件构成,这些器件靠片内总线相连。

运算器进行信息处理;控制器控制各种器件进行工作;寄存器进行信息存储;

8086CPU有14个寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW都是16位
Pasted image 20230627180833|800

|800

16位结构CPU具有下面几方面的结构特性。

  • 运算器一次最多可以处理16位的数据;
  • 寄存器的最大宽度为16位;
  • 寄存器和运算器之间的通路为16位。

8086CPU可以一次性处理以下两种尺寸的数据。

  • 字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
  • 字:记为word,一个字由两个字节组成,可以存在一个16位寄存器中(16位CPU)
    |800
    8086采用小端模式:高地址存放高位字节,低地址存放低位字节。

2、通用寄存器

通用寄存器:通常用来存放一般性的数据,有AX、BX、CX、DX它们可分为两个可独立使用的8位寄存器,还有SI,DI,BP,SP不能分为高低字节

Pasted image 20230627203245|800

在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的

一个8位寄存器所能存储的数据范围是0 ~ 28-1。

3、8086CPU给出物理地址的方法

8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。
8086CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。
从8086CPU的内部结构来看,如果将地址从内部简单地发出,那么它只能送出16位的地址,表现出的寻址能力只有64KB。
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
|800
当8086CPU要读写内存时:

  1. CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
  2. 地址加法器将两个16位地址合成为一个20位的物理地址;

地址加法器采用物理地址 = 段地址×16 + 偏移地址的方法用段地址和偏移地址合成物理地址。

例如,8086CPU要访问地址为123C8H的内存单元,1230H左移一位(空出4位)加上00C8H合成123C8H

4、段寄存器

我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元,可以用分段的方式来管理内存。

用一个段存放数据,将它定义为“数据段”;

用一个段存放代码,将它定义为“代码段”;

用一个段当作栈,将它定义为“栈段”。

Pasted image 20230627204427|800

注意:

  • 一个段的起始地址一定是16的倍数;
  • 偏移地址为16位,变化范围为0-FFFFH,所以一个段的长度最大为64KB。
  • CPU可以用不同的段地址和偏移地址形成同一个物理地址。

段寄存器:8086CPU有4个段寄存器:CS、DS、SS、ES,提供内存单元的段地址。

4.1 CS和IP

CS为代码段寄存器,IP为指令指针寄存器,

CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,

CPU将CS:IP指向的内容当作指令执行。(即PC)
|800
8086CPU的工作过程简要描述

  1. 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
  2. IP=IP+所读取指令的长度,从而指向下一条指令;
  3. 执行指令。转到步骤1,重复这个过程。

在8086CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS=FFFFH,IP=0000H,即在8086PC机刚启动时,FFFF0H单元中的指令是8086PC机开机后执行的第一条指令。

8086CPU提供转移指令修改CS、IP的内容。

  • jmp 段地址:偏移地址:用指令中给出的段地址修改CS,偏移地址修改IP。如:jmp 2AE3:3

  • jmp 某一合法寄存器:仅修改IP的内容。如:jmp ax。在含义上好似:mov IP,ax

8086CPU不支持将数据直接送入段寄存器的操作,这属于8086CPU硬件设计

4.2 DS 和 [address]

DS寄存器:通常用来存放要访问数据的段地址

[address]表示一个偏移地址为address的内存单元,段地址默认放在ds中

通过数据段段地址和偏移地址即可定位内存单元。

mov bx, 1000H ;8086CPU不支持将数据直接送入段寄存器的操作

mov ds, bx ;ds存放数据段地址

mov [0], al ;将al数据(1字节)存到1000H段的0偏移地址处,即10000H

mov ax, [2] ;将数据段偏移地址2处的一个字(8086为2字节)存放到ax寄存器

add cx, [4] ;将偏移地址4处的一个字数据加上cx寄存器数据放到cx寄存器

sub dx, [6] ;dx寄存器数据减去数据段偏移地址6处的字数据存到dx

4.3 SS 和 SP

在基于8086CPU编程的时候,可以将一段内存当作栈来使用。

栈段寄存器SS,存放段地址,SP寄存器存放偏移地址,任意时刻,SS:SP指向栈顶元素

8086CPU中,入栈时,栈顶从高地址向低地址方向增长。

push ax表示将寄存器ax中的数据送入栈中,由两步完成。

  1. SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
  2. 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。
    |800

pop ax表示从栈顶取出数据送入ax,由以下两步完成。

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

实验

  1. 将10000H~1000FH这段空间当作栈,初始状态栈是空的;
  2. 设置AX=001AH,BX=001BH;
  3. 将AX、BX中的数据入栈;
  4. 然后将AX、BX清零;
  5. 从栈中恢复AX、BX原来的内容。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
mov ax, 1000H 
mov ss, ax 
mov sp, 0010H    ;初始化栈顶
mov ax, 001AH
mov bx, 001BH 

push ax 
push bx    ;ax、bx入栈

sub ax, ax   ;将ax清零,也可以用mov ax,0,
             ;sub ax,ax的机器码为2个字节,
             ;mov ax,0的机器码为3个字节。
        
sub bx, bx 

pop bx  ;从栈中恢复ax、bx原来的数据
pop ax  ;

[1] 《汇编语言》王爽

[2] 《x86汇编语言:从实模式到保护模式(第二版)》李忠、王晓波、余洁

[3] 《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1》

[4] 《汇编语言简明教程》钱晓捷

[5] 8086CPU中14个寄存器的详解_基址寄存器