汇编语言
汇编语言
0. 8086CPU常用debug命令
在学习和调试 8086CPU 程序时,通常使用 DEBUG 工具(DOS 自带调试程序),可以通过命令直接与程序进行交互。以下是 DEBUG 中常用的一些命令及其解释:
常用命令
1. R(Register 显示/修改寄存器)
- 功能:显示或修改 CPU 寄存器的内容。
- 用法:
- 输入
R
:显示所有寄存器的当前内容及标志位。 - 输入
R 寄存器名
:修改指定寄存器的值。例如,R AX
修改 AX 寄存器。
- 输入
- 示例:
- R AX=0000 BX=0000 CX=0000 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000 DS=1234 ES=1234 SS=1234 CS=1234 IP=0100 NV UP EI PL NZ NA PO NC - R AX AX 0000 :1234 (输入新的值)
2. D(Dump 显示内存内容)
- 功能:以十六进制形式查看内存内容。
- 用法:
- 输入
D
:显示当前段的内存内容。 - 输入
D 地址
:显示指定地址开始的内容。 - 输入
D 地址范围
:显示特定内存范围内容(例如D 1000:0000 1000:0010
)。
- 输入
- 示例:
- D 1000:0000 1000:0000 4D 5A 90 00 03 00 00 00-04 00 00 00 FF FF 00 00 MZ.............. 1000:0010 B8 00 00 8E D8 8E C0 BF-00 00 B9 00 01 F3 A5 E9 ................
3. E(Enter 修改内存内容)
- 功能:修改指定内存地址的内容。
- 用法:
- 输入
E 地址
:从指定地址开始修改内容。
- 输入
- 示例:
- E 1000:0000 1000:0000 4D. 41 (输入新的值,按回车确认) 1000:0001 5A. 53
4. U(Unassemble 反汇编指令)
- 功能:将内存中的机器码转换为对应的汇编指令。
- 用法:
- 输入
U
:从当前 CS:IP 开始反汇编。 - 输入
U 地址
:从指定地址开始反汇编。 - 输入
U 地址范围
:反汇编特定范围的内容。
- 输入
- 示例:
- U 1000:0000 1000:0000 B80000 MOV AX,0000 1000:0003 8ED8 MOV DS,AX 1000:0005 8EC0 MOV ES,AX
5. T(Trace 单步执行指令)
- 功能:单步执行一条指令,并显示当前 CPU 状态。
- 用法:
- 输入
T
:执行当前指令(CS:IP 指向的指令),并显示寄存器和下一条指令。
- 输入
- 示例:
- T AX=0000 BX=0000 CX=0000 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000 DS=1000 ES=1000 SS=1000 CS=1000 IP=0003 NV UP EI PL NZ NA PO NC 1000:0003 8ED8 MOV DS,AX
6. G(Go 连续运行程序)
- 功能:让程序从某个地址连续运行,直到遇到断点或程序结束。
- 用法:
- 输入
G
:从当前 CS:IP 开始运行。 - 输入
G 地址
:从指定地址开始运行。 - 输入
G 地址范围
:运行到指定地址范围的断点。
- 输入
- 示例:
- G 1000:0100
7. A(Assemble 汇编指令)
- 功能:直接在内存中输入汇编指令。
- 用法:
- 输入
A 地址
:从指定地址开始写入汇编代码。
- 输入
- 示例:
- A 1000:0000 1000:0000 MOV AX,1234 1000:0003 MOV DS,AX 1000:0005 INT 20
8. F(Fill 填充内存)
- 功能:在指定的内存范围内填充某个字节值。
- 用法:
- 输入
F 地址范围 值
:将范围内的每个字节填充为指定值。
- 输入
- 示例:
- F 1000:0000 1000:00FF 90
9. P(Proceed 执行过程)
- 功能:类似
T
(单步执行),但跳过 CALL 指令,不进入子程序。 - 用法:
- 输入
P
:从当前 CS:IP 开始执行一步。
- 输入
10. Q(Quit 退出 DEBUG)
- 功能:退出 DEBUG 工具。
11. N(Name 加载文件名)
- 功能:指定加载的文件名。
- 用法:
- 输入
N 文件名
:将文件名载入 DEBUG。
- 输入
- 示例:
- N TEST.COM
12. L(Load 加载文件)
- 功能:将文件加载到内存。
- 用法:
- 输入
L
:加载当前文件到内存。 - 输入
L 地址
:将文件加载到指定地址。
- 输入
13. W(Write 写回文件)
- 功能:将修改后的内存数据写回文件。
- 用法:
- 输入
W
:将修改的内容保存到文件中。
- 输入
总结
8086 调试中最常用的 DEBUG 命令包括:
R
:查看/修改寄存器。D
:查看内存。E
:修改内存。U
:反汇编。T
:单步执行。G
:连续运行。A
:手动汇编代码。
通过这些命令,可以方便地分析、调试和运行 8086 汇编程序。
0. 8086CPU常用寄存器
8086 CPU 中的常用寄存器主要分为以下几类,每类寄存器都有其特定的用途:
1. 数据寄存器
用于存储和操作数据。它们可以作为 16 位寄存器使用,也可以拆分为两个 8 位寄存器(高位和低位)。
AX(累加器寄存器)
- 高位:AH
- 低位:AL
- 用于算术运算、逻辑运算以及 I/O 操作,很多指令默认使用 AX。
BX(基址寄存器)
- 高位:BH
- 低位:BL
- 常用于存储数据地址。
CX(计数器寄存器)
- 高位:CH
- 低位:CL
- 用于循环和移位操作的计数。
DX(数据寄存器)
- 高位:DH
- 低位:DL
- 用于存储数据,有时用于 I/O 操作中的端口地址。
2. 指针和索引寄存器
这些寄存器用于地址计算和数据操作。
SP(堆栈指针寄存器)
指向当前栈顶。—BP(基址指针寄存器)
通常用于访问堆栈中的参数或局部变量。SI(源变址寄存器)
常用于字符串操作,指向源地址。DI(目的变址寄存器)
常用于字符串操作,指向目标地址。
3. 段寄存器
8086 CPU 采用段式内存管理,每个段寄存器存储一个段的起始地址。
CS(代码段寄存器)
指向当前正在执行代码的段。DS(数据段寄存器)
指向当前使用的数据段。SS(堆栈段寄存器)
指向当前堆栈的段。ES(附加段寄存器)
在字符串操作中用作目标段。
4. 标志寄存器(FLAGS)
用于存储运算结果的状态信息,以及控制 CPU 的某些操作。
- 常用标志位:
- ZF(零标志):结果是否为零。
- SF(符号标志):结果是否为负。
- CF(进位标志):是否产生进位或借位。
- OF(溢出标志):是否产生算术溢出。
- PF(奇偶标志):结果中1的个数是否为偶数。
- AF(辅助进位标志):用于 BCD(十进制)运算。
总结
- 数据寄存器:AX、BX、CX、DX
- 指针和索引寄存器:SP、BP、SI、DI
- 段寄存器:CS、DS、SS、ES
- 标志寄存器:FLAGS
这些寄存器的灵活组合使 8086 CPU 能够高效地完成复杂的运算和内存操作。
1. 基础知识
1.1 机器语言
机器语言是机器指令的集合。
机器指令是一列二进制数字。
计算机将之转变为一列高低电平,以使计算机的电子器件受到驱动,进行运算。
CPU Central Procesing Unit
每种CPU硬件设计和内部结构不同,需要不同的电平脉冲来控制。
每一种CPU都有自己的机器指令集,也就是机器语言。
1.2 汇编语言的产生
汇编语言的主体是汇编指令。
汇编指令是机器指令便于记忆的书写格式。
寄存器,CPU中可以存储数据的器件。 AX/BX等是寄存器代号。
将汇编指令转换成机器指令的翻译程序,称为编译器。
1.3 汇编语言的组成
1.4 存储器
CPU和内存搭配使用。
想让一个CPU工作,必须向他提供指令和数据。指令和数据在存储器中存放,也就是内存。
1.5 指令和数据
指令和数据在内存中无区别,都是二进制数据,仅仅是应用上的概念。
1.6 存储单元
[0|1]=1b(bit) 8bit=1B(Byte) 1024B=1KB 1024KB=1MB 1024MB=1GB
Your conversions are mostly correct, except that the first one,
[0|1] = 1b(bit)
, might need clarification. Let’s rewrite it step by step:
- A bit (
b
) is the smallest unit of data in computing, and it can be either0
or1
.- 8 bits = 1 Byte (
B
)- 1024 Bytes = 1 Kilobyte (
KB
)- 1024 Kilobytes = 1 Megabyte (
MB
)- 1024 Megabytes = 1 Gigabyte (
GB
)This hierarchy is based on binary prefixes, which are commonly used in computer science for data storage and memory. Let me know if you want further > explanation or examples!
1.7 CPU对存储器的读写
CPU读写数据需要进行信息交互:
- 地址信息,存储单元的地址
- 控制信息,器件的选择,读或写的命令
- 数据信息,读或写的数据
计算机使用导线传输电信号。
CPU连接其他芯片的导线,称为总线,物理上,总线是一根根导线的集合。
总线从逻辑上分为三类:地址总线、控制总线、数据总线。
使用汇编指令来命令CPU进行数据读写,例如:move ax,[3]
1.8 地址总线
一根导线可传送的稳定状态只有两种,高电平和低电平,用二进制表示就是1或者0。10根导线可以表示2的10次方个数,0~1023。
寻址范围和导线数量有关系。
00 0000 0000
1.9 数据总线
数据总线的宽度界定了CPU和外界的数据传输速度。 8根总线一次可以传送一个8位二进制数据。
1.10 控制总线
CPU对外部器件的控制是通过控制总线进行的。总线的宽度决定了CPU对外部器件的控制能力。
1.1~1.10 小结
CPU芯片有很多管脚,管脚与总线相连。3种总线的宽度标志了CPU不同方面的性能。
- 地址总线的宽度决定了CPU的寻址能力。
- 数据总线的宽度决定了CPU与其他器件进行数据传输时的一次数据传送量。
- 控制总线的宽度决定了CPU对系统中其他器件的控制能力。
1.11 内存地址空间
1.12 主板
主板上有核心器件和一些主要器件,通过总线相连。
CPU、存储器、外围芯片组、扩展插槽。扩展插槽上一般有RAM内存条和各类接口卡。
1.13 接口卡
CPU通过控制接口卡,实现CPU对外设设备得间接控制。
1.14 各类存储芯片
读写属性上分为:随机存储器RAM和只读存储器ROM。
- 随机存储器RAM,存储程序和数据。主存储器由主板RAM和扩展插槽RAM组成。
- 装有BIOS(Basic Input/Output System)的ROM。
- BIOS是主板和各类接口卡(显卡、网卡)厂商提供的软件系统,通过他利用该设备进行输入输出。
- 主板上的ROM中存储着主板的BIOS(系统BIOS)
- 显卡上的ROM存储着显卡的BIOS。
- 接口卡上的RAM。
- 显卡RAM,显存。
RAM主存储器和扩展插槽的RAM主存储器,有什么区别?
RAM 主存储器 和 扩展插槽的 RAM 主存储器 都是计算机的随机存取存储器,但它们在物理位置、容量扩展能力以及应用场景上有所区别。以下是详细对比:
1. 主存储器的定义:
RAM 主存储器(内置主存储器):
- 是计算机主板上预装或焊接的 RAM。
- 通常是系统默认配置,容量和性能可能固定。
- 直接作为系统内存使用,存储操作系统和程序运行时所需的临时数据。
扩展插槽的 RAM 主存储器:
- 是通过主板上的 内存插槽(DIMM 或 SO-DIMM 插槽) 插入的模块化 RAM。
- 用户可以根据需要购买和安装更多的 RAM 模块来扩展容量。
- 常用于增加系统性能或满足运行更大程序的需求。
2. 主要区别:
特性 主存储器(内置) 扩展插槽的 RAM 主存储器 物理位置 焊接在主板上,无法更换或移除 插入主板内存插槽,可插拔更换 容量 通常较小,固定不变 可根据主板支持的插槽和容量上限扩展 性能 性能固定,取决于制造时的规格 性能由用户选择的内存条决定 可扩展性 无法扩展,取决于出厂配置 灵活扩展,可添加或升级更多内存 维护成本 出现问题需要更换主板 单独更换受损的 RAM 模块即可 3. 实际应用场景:
主存储器(内置):
- 典型于轻薄笔记本电脑(如 MacBook Air),内部内存直接焊接在主板上。
- 优点是节省空间,提高稳定性;缺点是用户无法自行升级或更换。
扩展插槽的 RAM 主存储器:
- 常见于台式机和高性能笔记本(如游戏本),支持用户根据需求升级内存。
- 适用于需要更高性能或更大容量的场景。
4. 总结:
- 主存储器(内置) 更加集成化,适合追求轻便设计的设备,但限制了用户的扩展能力。
- 扩展插槽的 RAM 主存储器 提供灵活性,适合台式机和高性能设备,能随需求增长而升级。
两者的选择通常取决于设备的设计目标以及用户的使用需求。
RAM和ROM?
ROM 是 Read-Only Memory 的缩写,中文称为“只读存储器”。在计算机中,ROM 是一种不可随意修改的数据存储设备,主要用于存储不会频繁更改的数据,通常是一些固件> (firmware)或程序代码。以下是详细解释:
ROM的特点:
- 只读:ROM 中存储的数据只能读取,通常不能被用户或程序写入或修改(某些类型的 ROM 如 EEPROM 支持有限的重写)。
- 非易失性:即使断电,存储在 ROM 中的数据也不会丢失。
- 高速存取:ROM 的读取速度通常比硬盘快,适合存放启动程序和系统级数据。
在计算机中,ROM 的作用:
- 固件存储:存储不可修改或极少更改的固件,例如 BIOS 或 UEFI 程序,用于引导计算机启动。
- 嵌入式设备:存储嵌入式系统中的程序代码(例如微控制器、路由器、电视等设备)。
- 永久数据:存放在设备出厂时就写入的、用户不能直接更改的数据,比如游戏机的内置游戏。
ROM 的类型:
- Mask ROM:在制造过程中永久写入数据,无法更改。
- PROM(Programmable ROM):一次性可编程存储器,用户可用专门设备写入数据,但只能写一次。
- EPROM(Erasable Programmable ROM):可擦除和重新编程,需要紫外线擦除。
- EEPROM(Electrically Erasable Programmable ROM):可用电信号擦除并重新编程,广泛应用于现代设备。
- Flash ROM:一种特殊的 EEPROM,可快速擦写,广泛用于 U 盘、固态硬盘等。
ROM 在现代计算机中仍然是不可或缺的一部分,尤其是在存储启动数据和不可更改的程序中起着重要作用。
1.15 内存地址空间
所有物理存储器(RAM和ROM)被看做一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。
内存地址空间的大小受CPU地址总线宽度的限制。
现在主流PC和Phone的地址总线宽度为多少,内存地址空间是多少?
现代主流 PC 和手机的地址总线宽度和内存地址空间如下:
1. 主流 PC 的地址总线宽度和内存地址空间
地址总线宽度:
- 大多数主流 PC 使用 64 位处理器,因此地址总线宽度为 64 位。
- 但由于实际硬件限制,目前消费级处理器通常支持 48 位物理地址总线,虚拟地址可以达到 64 位(理论值)。
内存地址空间:
- 理论上,64 位地址总线的内存地址空间为 ( 2^{64} ) 字节,即 16 EB(ExaBytes)。
- 但现代消费级处理器实际支持的物理地址范围通常是 ( 2^{48} ) 字节,即 256 TB(TeraBytes)。
- 实际支持的内存容量受主板和芯片组限制,通常为几百 GB(例如 128 GB 或 512 GB)。
2. 主流手机的地址总线宽度和内存地址空间
地址总线宽度:
- 手机处理器(例如 ARM 架构处理器)也采用 64 位架构,地址总线理论上支持 64 位。
- 实际的物理地址宽度通常为 32 位或 36 位,高端设备可能达到 40 位。
内存地址空间:
- 理论上,64 位地址总线支持的地址空间为 16 EB,但实际硬件实现的物理地址范围小得多。
- 32 位地址总线的地址空间为 ( 2^{32} ) 字节,即 4 GB。
- 36 位地址总线支持 64 GB,40 位支持 1 TB。
当前主流高端手机的内存容量一般在 8 GB 到 16 GB 范围内,部分旗舰机型支持更高。
总结:
设备 地址总线宽度 理论内存地址空间 实际支持范围 主流 PC 64 位(48 位物理) 16 EB(256 TB 物理) 通常为 128 GB 至 1 TB 主流手机 64 位(32-40 位物理) 16 EB(1 TB 物理) 通常为 8 GB 至 16 GB 实际内存地址空间受限于硬件实现和操作系统支持,例如操作系统的内存管理能力及芯片组的设计规格。
2. 寄存器
内部总线实现CPU内部各个器件之间的联系,外部总线实现CPU和主板上其他器件的联系。
- 运算器,信息处理
- 寄存器,信息存储
- 控制器,控制各个器件工作
- 内部总线,数据传送
CPU内部由哪些器件组成?
CPU(中央处理单元)的内部由多种器件组成,它们共同负责执行计算机的基本运算和控制功能。以下是 CPU 的主要组成部分及其功能:
1. 核心部分
算术逻辑单元(ALU, Arithmetic Logic Unit):
- 用于执行基本的算术运算(如加减乘除)和逻辑运算(如与、或、非、异或)。
- 是 CPU 的运算核心,处理大部分指令中的数学和逻辑任务。
控制单元(CU, Control Unit):
- 负责指挥 CPU 的各个部分如何协同工作。
- 解释并执行指令,从内存中获取指令,解码,并控制数据流的方向。
寄存器(Registers):
- 高速的存储单元,用于暂存数据和指令。
- 包括:
- 通用寄存器:存储临时数据或操作数。
- 专用寄存器:如程序计数器(PC)、指令寄存器(IR)、状态寄存器(SR)等。
2. 缓存(Cache)
- 位于 CPU 内部或极其接近,用于存储常用的数据和指令,减少对主存的访问。
- 分为多级:
- L1 缓存(一级缓存):速度最快,容量最小。
- L2 缓存(二级缓存):容量更大,速度略慢。
- L3 缓存(三级缓存):仅存在于高端 CPU,容量更大,用于共享多个核心的数据。
3. 时钟与控制逻辑
时钟生成器(Clock Generator):
- 提供统一的时钟信号,确保 CPU 各部分同步工作。
- CPU 的性能(主频)由时钟信号的频率决定。
指令解码器(Instruction Decoder):
- 将从内存中取出的机器指令翻译为 CPU 能够执行的微操作。
4. 总线接口(Bus Interface Unit, BIU)
- 负责 CPU 与外部设备(如内存和输入输出设备)之间的数据传输。
- 包括数据总线、地址总线和控制总线接口。
5. 流水线(Pipeline)
- 用于并行处理多个指令,提高执行效率。
- 将指令分解为多个阶段(如取指、解码、执行等),每个阶段由专门的硬件完成。
6. 分支预测单元(Branch Prediction Unit)
- 预测程序中分支指令的执行路径,减少流水线停顿,提高指令执行效率。
7. 内存管理单元(MMU, Memory Management Unit)
- 负责虚拟地址到物理地址的映射。
- 提供内存保护和分页机制,支持现代操作系统的虚拟内存功能。
8. 浮点运算单元(FPU, Floating Point Unit)
- 专门处理浮点数的计算。
- 对于科学计算和图形处理非常重要。
9. 多核心单元
- 现代 CPU 通常包含多个核心(Core),每个核心相当于一个独立的处理单元。
- 支持并行处理,显著提高计算能力。
10. 图形处理单元(GPU, Integrated GPU)
- 部分现代 CPU 集成了 GPU,用于处理图形任务,减轻独立显卡的负担。
11. 专用单元
- 向量处理单元(SIMD, Single Instruction Multiple Data):
- 用于加速多媒体和大数据计算。
- 加密单元(Crypto Unit):
- 加速数据加密和解密。
- 神经网络处理单元(NPU, Neural Processing Unit):
- 专门用于 AI 运算的硬件单元。
总结
一个现代 CPU 的内部通常由以下部分组成:
- 运算单元:ALU、FPU、SIMD 等。
- 控制单元:CU、寄存器、指令解码器。
- 存储单元:寄存器、缓存。
- 数据交互单元:总线接口、内存管理单元。
- 专用单元:分支预测、加密单元、NPU 等。
随着技术的发展,CPU 的集成度越来越高,同时也变得更加复杂和高效。
2.1 通用寄存器
2.2 字在寄存器中的存储
为什么计算机中常用十六进制表示二进制?
计算机中常用十六进制表示二进制,主要是因为十六进制能够高效地表示二进制数,并且容易转换和阅读。以下是详细原因:
1. 简化表示
- 二进制数通常由大量的
0
和1
组成,对于人类来说,直接阅读和操作大段二进制数非常困难。
- 例如,二进制数
101110100101
很难快速理解。- 十六进制将每 4 位二进制数映射为一个十六进制字符,显著缩短了长度。
- 示例:
- 二进制:
1011 1010 0101
- 十六进制:
BA5
2. 易于转换
十六进制与二进制之间的转换非常直观:
- 每个十六进制数字对应 4 位二进制数(因为 ( 2^4 = 16 ))。
- 不需要复杂的计算,只需将二进制分组即可。
- 示例:
- 二进制:
1110 1101
- 十六进制:
ED
与十进制相比,十六进制更适合表示二进制数:
- 十进制与二进制之间的转换需要反复除以 2 或乘以 2,较为复杂。
- 示例:
- 二进制:
1010 1100
- 十进制:
172
(需要计算)。3. 节省空间
- 十六进制表示法比二进制更紧凑,占用的空间更小,尤其在存储数据、编写代码或显示调试信息时。
- 示例:
- 二进制:
1101011110111101
(16 位)- 十六进制:
D7BD
(4 位)4. 方便识别和调试
- 现代计算机处理器和系统中的地址、数据、指令等多以二进制存储,而十六进制更容易表达这些内容。
- 在调试工具(如汇编语言和内存转储工具)中,使用十六进制可以更清晰地查看和分析数据。
- 示例:
- 内存地址
0x1A3F
是十六进制表示,便于识别,比对应的二进制1101000111111
直观得多。5. 与字节对齐
- 计算机中,数据通常以字节为单位存储(1 字节 = 8 位二进制)。
- 一个字节可以用 2 个十六进制字符表示(因为 ( 16^2 = 256 )),这与存储结构高度匹配。
- 示例:
- 字节内容:
1111 0001
(二进制)- 十六进制:
F1
总结
十六进制被常用来表示二进制数,主要是因为它具有以下优点:
- 简化二进制数的表示,缩短长度。
- 转换简单直观。
- 节省空间,方便阅读。
- 与计算机存储结构(字节和地址)高度匹配。
因此,十六进制是二进制数在人类交互中最常用的表示方式之一。
2.3 几条汇编指令
十六进制加法、减法如何计算?
十六进制加法和减法的计算遵循与十进制类似的规则,但因为基数是 16,需要特别注意进位和借位。以下是具体的计算方法和步骤:
1. 十六进制加法
规则:
- 每一位按 16 进制进行相加。
- 若和大于或等于 16,则需要进位到下一位。
- 十六进制字符对照表:
0, 1, 2, ..., 9, A(10), B(11), C(12), D(13), E(14), F(15)
步骤:
- 从最低位开始对齐两数,逐位相加。
- 如果和 ≥ 16,则取余数作为当前位,进位 1 到下一位。
- 重复直到最高位,若最高位有进位,则补上一位。
示例:
计算:
1A7
+B9
- 按位对齐:
1A7 + 0B9 ------
- 从最低位开始:
- 7 + 9 = 16(取余数 0,进位 1)
- A(10) + B(11) + 1 = 22(取余数 6,进位 1)
- 1 + 0 + 1 = 2(无进位)
结果:
206
2. 十六进制减法
规则:
- 每一位按 16 进制进行相减。
- 若被减数小于减数,则需要向更高位借 1,借位值为 16。
步骤:
- 从最低位开始对齐两数,逐位相减。
- 如果被减数小于减数,则向更高位借 1。
- 借位时,当前位的值加上 16,再进行减法。
- 重复直到最高位,若无借位则直接相减。
示例:
计算:
1A7
-B9
- 按位对齐:
1A7 - 0B9 ------
- 从最低位开始:
- 7 - 9(不够减,向上一位借 1,当前位值为 7 + 16 = 23,23 - 9 = 14,记作
E
)- A(10) - B(11) - 1(借位)= 10 - 11 - 1 = 14(记作
F
,向再上一位借 1)- 1 - 0 - 1(借位)= 0(无借位)
结果:
0FE
总结
十六进制加法:
- 和 ≥ 16 时取余数,向高位进 1。
十六进制减法:
- 被减数不足时向高位借 1,加 16 后再减。
熟练使用十六进制字符表(0-F)和基本加减法规则,可以快速完成十六进制计算!
2.4 物理地址
所有内存单元构成的存储空间是一个一维的线性空间。
CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。
2.6 16位结构的CPU
地址加法器采用
- 物理地址=段地址x16+偏移地址
- 一个数据的二进制形式左移N位,相当于该数据乘以2的N次方。
分段机制与分页结合
- 现代 CPU 大多使用分页机制,但早期 CPU(如 x86 16 位)采用分段机制。
- 分段机制:物理地址 = 段基址 + 偏移量。
- 现代 CPU 中分段机制通常简化(如 x86-64 将段基址固定为 0)。
CPU计算物理地址的方法? 以市面上最主流的CPU为例。? 概括的将,16位结构,描述了一个CPU具有以下几个方面的结构特征:
- 运算器一次最多可以处理16位的数据
- 寄存器的最大宽度是16位
- 寄存器和运算器之间的通路位16位
16位结构的CPU与32位和64之间的区别?
16位、32位和64位结构的CPU主要区别在于它们的数据处理能力、地址空间、寄存器宽度和计算能力。以下是详细比较:
1. 数据处理能力
16位 CPU:
- 每次处理的数据宽度为 16 位(2 字节)。
- 运算能力有限,适合处理较小的数据范围。
- 每条指令可操作的数据范围是 ( 2^{16} ) 即 65536(约 64 KB)。
32位 CPU:
- 每次处理的数据宽度为 32 位(4 字节)。
- 单条指令能处理的数据范围扩大到 ( 2^{32} ) 即 4,294,967,296(约 4 GB)。
- 比 16 位 CPU 更适合计算密集型任务。
64位 CPU:
- 每次处理的数据宽度为 64 位(8 字节)。
- 能处理的单条指令范围是 ( 2^{64} ) 即 18,446,744,073,709,551,616(约 18 EB)。
- 提供更强的计算能力,适合大数据、AI 和科学计算等高性能需求。
2. 内存地址空间
16位 CPU:
- 能访问的内存地址空间为 ( 2^{16} ) 字节(64 KB)。
- 通常需要通过分段机制扩展地址空间,但仍然非常有限。
32位 CPU:
- 内存地址空间为 ( 2^{32} ) 字节(4 GB)。
- 是现代操作系统的主流标准,适用于大多数应用场景。
64位 CPU:
- 理论内存地址空间为 ( 2^{64} ) 字节(16 EB)。
- 实际受限于硬件实现,通常消费级设备支持的内存上限为几 TB 到几十 TB。
- 更适合运行需要大量内存的应用,如虚拟化、大型数据库、视频处理等。
3. 寄存器宽度
16位 CPU:
- 寄存器宽度为 16 位,数据传输和指令执行一次最多操作 16 位数据。
- 典型代表是早期的 Intel 8086、80286 等。
32位 CPU:
- 寄存器宽度为 32 位,指令执行和数据传输更高效。
- 常见于 Intel Pentium 系列和早期 ARM 架构。
64位 CPU:
- 寄存器宽度为 64 位,能执行更复杂的指令,支持更大范围的数据操作。
- 常见于 AMD64、Intel x86-64 和现代 ARM64 架构。
4. 指令集和兼容性
16位 CPU:
- 使用 16 位指令集(如 x86-16)。
- 与现代应用不兼容,仅能运行非常老旧的软件。
32位 CPU:
- 使用 32 位指令集(如 x86)。
- 可以兼容 16 位程序,但无法运行 64 位程序。
64位 CPU:
- 使用 64 位指令集(如 x86-64 和 ARM64)。
- 向下兼容 32 位程序(需操作系统支持),但不能直接运行 16 位程序(一般需要虚拟化或模拟器)。
5. 性能和应用场景
16位 CPU:
- 性能较低,仅适用于嵌入式系统或历史遗留的简单任务。
- 典型应用:老式计算机、嵌入式设备(如微控制器)。
32位 CPU:
- 性能较好,支持大多数日常任务和中等规模的计算。
- 典型应用:旧式台式机、笔记本电脑、部分嵌入式设备(如路由器)。
64位 CPU:
- 性能最高,支持高性能计算、虚拟化、大型数据处理。
- 典型应用:现代 PC、服务器、智能手机、高性能计算设备。
6. 操作系统支持
16位 CPU:
- 支持早期操作系统(如 MS-DOS、早期 Windows)。
- 不兼容现代操作系统。
32位 CPU:
- 支持 32 位操作系统(如 Windows XP/7/10 32 位、Linux 32 位)。
- 不支持运行 64 位操作系统。
64位 CPU:
- 支持 64 位操作系统(如 Windows 10/11 64 位、macOS、Linux 64 位)。
- 一些支持运行 32 位应用的兼容模式。
总结:
特性 16 位 CPU 32 位 CPU 64 位 CPU 数据宽度 16 位 32 位 64 位 内存地址空间 64 KB 4 GB 16 EB(理论值) 寄存器宽度 16 位 32 位 64 位 指令集 x86-16 x86 x86-64 / ARM64 应用场景 嵌入式、历史系统 日常应用、中等性能 高性能计算、大数据 兼容性 仅支持 16 位程序 支持 16 和 32 位程序 支持 32 和 64 位程序 64 位 CPU 是现代计算的主流,支持更大的内存、更高的性能和更复杂的任务。
2.6 8086CPU 给出物理地址的方法
下面以当前市面上最主流的 x86-64(AMD64 / Intel 64) 架构为例,介绍 CPU(准确来说,是其 内存管理单元 MMU)将虚拟地址转换为物理地址的过程。
一、基础概念
虚拟地址(Virtual Address)
- 程序看到并使用的地址,由操作系统为每个进程分配独立的地址空间。
- 在 x86-64 下,当前常见实现通常使用 48 位的虚拟地址(高位扩展规则下,理论最大可达 57 位甚至更多,但实际并未完全实现)。
物理地址(Physical Address)
- 内存硬件(RAM)实际使用的地址。
- 由 CPU 的 物理地址总线 提供,实际可访问范围比理论值往往要小,受限于芯片组、主板设计等。
页(Page)
- 内存管理最常用的基本单位。
- 在 x86-64 下,最常见的页大小为 4 KB。同时也支持大页(2 MB 或 1 GB),但原理相似。
多级页表(Multi-level Page Table)
- 为了管理和映射庞大的虚拟地址空间到物理地址空间,x86-64 采用 四级页表(某些场景可扩展到五级,但主流还是四级)。
- 每一级页表都存储下一级页表的物理基地址或直接存储目标页所在的物理基地址。
内存管理单元(MMU, Memory Management Unit)
- 负责将虚拟地址翻译成物理地址。
- 将多级页表的索引和页面内偏移综合起来,得出最后的物理地址。
二、x86-64 四级分页机制
在常见的 4 KB 页模式下,x86-64 的虚拟地址格式可简化表示如下(48 位虚拟地址):
[ 9 bits | 9 bits | 9 bits | 9 bits | 12 bits ] PML4 PDPT PDT PT Offset
- PML4(Page Map Level 4):第一级页表索引,占 9 位。
- PDPT(Page Directory Pointer Table):第二级页表索引,占 9 位。
- PDT(Page Directory Table):第三级页表索引,占 9 位。
- PT(Page Table):第四级页表索引,占 9 位。
- Offset:页内偏移,占 12 位(因为 4 KB = 2^12)。
也就是说,每一层页表用 9 位索引来选出一个 页表项(Page Table Entry,PTE),而页表项中存储下一层页表的基地址或实际物> 理页框地址。最后的 12 位用来定位该页内部的具体字节。
三、虚拟地址到物理地址的转换过程
假设我们有一个 48 位的虚拟地址(实际 CPU 可能只实现 48 位或 52 位等,更高位通过符号扩展),MMU 的转换步骤如下:
从 CR3 寄存器(页表基址)获取顶级 PML4 表地址
- 操作系统会在
CR3
(Control Register 3)中保存 PML4 表 在物理内存中的基地址(通常是 52 位或更宽的物理地址)。- MMU 根据这个基地址去内存中找到 PML4 表。
PML4 索引:读取虚拟地址的最高 9 位(PML4 字段)
- 在 PML4 表中,用这 9 位作为索引(即第几个表项)。
- 从对应的表项中读取到 PDPT 表 的基地址(在物理内存中的地址),同时还可读取权限信息(可读/可写/可执行等)。
PDPT 索引:读取虚拟地址接下来的 9 位
- 在找到的 PDPT 表中,用这 9 位作为索引。
- 从对应的表项中读取到 PDT 表 的基地址(物理地址)及权限信息。
PDT 索引:读取虚拟地址再往下的 9 位
- 在 PDT 表中,用这 9 位作为索引。
- 表项指向 PT 表 的基地址(物理地址),并包含相应权限信息。
PT 索引:读取虚拟地址最后的 9 位索引部分
- 在 PT 表中,用这 9 位作为索引。
- 如果这个表项有效,通常就能得到 实际物理页框基地址(page frame address)。同时还会含有权限位、是否在缓存中、是否在磁盘等信息。
页内偏移(Offset:12 位)
- 最后再将上述物理页框基地址 +
Offset
(12 位)- 得到 完整的物理地址。
若任何一级索引对应的表项无效(比如权限不够或表项为 0),就会触发 缺页异常(Page Fault) 或访问错误,由操作系统来处理。
四、示例
虚拟地址示例
假设一个虚拟地址为(用 48 位示意):
虚拟地址(16 进制):0x00007FFF_FF12_3450
- 为了便于说明,先将其二进制划分为 5 个部分(PML4、PDPT、PDT、PT、Offset)。
- 计算得出(以下仅为举例,非真实索引值):
- PML4 索引(9 bits):
0x1FF
- PDPT 索引(9 bits):
0x1FD
- PDT 索引(9 bits):
0x1FE
- PT 索引(9 bits):
0x050
- Offset(12 bits):
0x450
多级查表
PML4 表
CR3
中记录的物理地址:0x00000010_0000_0000
(示例)- 在该物理地址上存放 PML4 表。
- 利用 PML4 索引
0x1FF
找到对应的表项,读到 PDPT 的基址:0x00000020_0000_0000
。PDPT 表
- 在物理地址
0x00000020_0000_0000
存放 PDPT。- 利用索引
0x1FD
找到 PDT 的基址:0x00000020_0010_0000
。PDT 表
- 在物理地址
0x00000020_0010_0000
存放 PDT。- 利用索引
0x1FE
找到 PT 的基址:0x00000020_0020_0000
。PT 表
- 在物理地址
0x00000020_0020_0000
存放 PT。- 利用索引
0x050
找到实际页框的基址:0x00000030_1000_0000
。计算最终物理地址
- 物理页框基址:
0x00000030_1000_0000
- 页内偏移:
0x450
- 最终物理地址:
[ 0x00000030_1000_0000 + 0x450 = 0x00000030_1000_0450 ]五、ARM64 等其他主流架构
与 x86-64 类似,ARM64 等现代 64 位架构也普遍采用多级页表(通常 4 或 5 级),每级索引位数根据具体实现而定,也会有 4 KB、16 KB、64 KB 等不同页面大小的模式。核心逻辑都类似:
- 从顶级页表基址(TTBR0/TTBR1 等寄存器)开始。
- 逐级索引到下一级页表。
- 最终获得物理页框地址。
- 加上页内偏移得到物理地址。
六、总结
- MMU 负责地址翻译:CPU 并不会“手动”去做每次多级查表,而是将虚拟地址送给 MMU,由 MMU 自动完成分页查表过程。
- 四级分页(x86-64):主流的 64 位桌面/服务器 CPU 通常采用 4 级页表结构(PML4 → PDPT → PDT → PT),也可能根据需求扩> 展到 5 级。
- 页大小及多级深度可变:4 KB、2 MB、1 GB 大页,以及 ARM64 的 16 KB、64 KB 页,都会相应调整多级页表索引位数。
- 软件与硬件配合:操作系统内核负责分配和维护页表,CR3(或 TTBR)寄存器指向顶级页表;硬件(MMU)自动进行翻译,并在缺页> 或权限错误时发起异常。
简而言之:
- 现代主流 CPU 使用分页机制和多级页表。
- 虚拟地址通过多级索引(PML4 / PDPT / PDT / PT 等)依次查表找到对应的物理页框基址。
- 最后将该基址与页内偏移相加,形成 物理地址。
为什么现在CPU不再使用分段机制而使用分页?
在现代 CPU(尤其是 x86-64 和 ARM64 等 64 位架构)中,“分段机制(Segmentation)” 已基本被“分页机制(Paging)”所> 取代或大幅弱化,主要原因可概括为以下几点:
1. 简化内存管理
分段机制的复杂性
早期 x86 架构在 16 位时代大量使用分段(Code Segment、Data Segment、Stack Segment 等),每个段都有不同的基址和界限。应用程序需要频繁地调整段寄存器,管理起来相对繁琐。分页机制更符合现代操作系统需求
现代操作系统(如 Linux、Windows、macOS)普遍采用 分页(Paging) 来提供更灵活的内存管理功能,例如:
- 虚拟内存:可以把不常用的页面换出到磁盘,节省物理内存。
- 内存保护:每个页面可以设置只读、可写、可执行等权限。
- 共享内存:多个进程可以映射同一个物理页面,节省资源。
使用分页后,内存管理单元(MMU)只需要维护多级页表,不再关心段基址和段限长等复杂信息,体系结构更加统一。
2. 大内存空间与统一地址模型
- 分段更适合小地址空间
在 16 位或 32 位时代,分段+分页并存是为了扩大可用地址空间,但同时也带来复杂性。- 64 位带来的“大一统”
现代 64 位架构一般将分段机制弱化或基本关闭,仅保留少量的“伪分段”或“平坦(Flat)模式”:
- 所有段的基址为 0,段限长设置到最大,等效于无分段。
- CPU 主要依靠分页做虚拟地址到物理地址的映射与保护。
这样就形成了一种 “平坦内存模型”,使操作系统可以在巨大、连续的虚拟地址空间中更自由地调度和管理内存,简化开发和运行时的复> 杂度。
3. 硬件与软件的协同演进
- 操作系统普遍依赖分页
从 UNIX 到 Linux/Windows 这些现代操作系统,都基于分页来实现虚拟内存管理与进程隔离。分段往往只是最初在 x86 上为了兼容 16 > 位模式保留下来的遗产。- 硬件设计逐步弱化分段
x86-64 架构虽然在硬件上还保留“段寄存器”,但大多数情况下段基址都固定为 0,分段检查被简化甚至禁用。硬件厂商将设计重点放在更> 高效的分页机制(如多级页表、TLB、大页支持等),以满足大内存和高性能需求。- 性能和可扩展性
分段需要处理基址加法、限长检查,每次访问内存都要额外进行段检查,硬件逻辑更复杂。分页只需页面粒度的访问控制,利用高速缓存> (TLB)来加速地址翻译,性能更好、扩展性更强。4. 安全性与隔离
- 分页粒度更细
分段往往以“段”为单位,虽然也可以分得很细,但编写复杂。分页则以固定大小的“页”为单位(通常 4 KB、2 MB、1 GB 等),对于权限> (R/W/X)的设置和进程间隔离更直观、可控。- 现代安全需求
地址空间布局随机化(ASLR)、不可执行堆栈/数据区(NX bit)等安全特性都基于分页实现。分段模式难以在现代复杂系统中灵活应用这> 些特性。5. 向下兼容与过渡
- 早期 x86 的“分段+分页”
32 位的 x86(保护模式)既支持分段,又支持分页。操作系统往往将段设置成“平坦模式”(base=0,limit=4GB),再用分页来真正管理> 内存。- x86-64 的“长模式”
在“长模式”下(即 64 位模式),仍保留对分段寄存器的一些支持,但大多数情况下都将它们设置为平坦模式,只对 FS/GS 等寄存器做个> 别特殊用途(如线程本地存储)处理,而不再做通用的内存隔离。- ARM64 则几乎没有历史包袱
ARM64 更彻底地依赖分页来管理地址空间,没有 x86 那样复杂的历史遗留分段机制。总结
分页 在现代操作系统和 CPU 架构中成为主要的内存管理手段,主要原因是:
- 更大更复杂的内存空间需求:64 位下地址空间远超 32 位或 16 位时代,分段模式的管理复杂度大幅上升,不利于可扩展性。
- 易实现虚拟内存和进程隔离:分页可细粒度控制内存访问、实现共享与保护,分段机制则相对繁琐。
- 硬件与软件协同简化:硬件上弱化分段、强化分页;操作系统则充分利用分页实现安全与性能。
因此,现在的 CPU 都 默认使用分页(结合简化或“关闭”的分段)来实现内存地址转换与访问控制,让操作系统和应用开发者专注于更高层次的需求。
2.7 段地址x16+偏移地址=物理地址的本质含义
基础地址+偏移地址=物理地址
2.8 段的概念
2.9 段寄存器
2.10 CS和IP
8086CPU的工作过程:
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器。
- IP=IP+所读取指令的长度,从而指向下一条指令。
- 执行指令。转到步骤1,重复这个过程。
CPU根据什么将内存中的信息看做指令?
- CPU将CS:IP指向的内存单元中的内容看做指令。
CPU根据什么将内存中的信息看做指令?
在现代计算机体系结构中,CPU 之所以会把内存中的某些信息当作指令来执行,根本原因在于以下几点:
1. 程序计数器(PC / IP)指向“即将执行的指令”所在地址
程序计数器(Program Counter, PC)
- 也称 指令指针(Instruction Pointer, IP)(在 x86 架构中常用 IP/EIP/RIP 等名称)。
- CPU 内部的一个特殊寄存器,用来存储下一条要执行指令在内存中的地址。
取指(Fetch)阶段
- CPU 会根据 PC 的值,从内存中读取相应地址处的数据。
- 读出的这段二进制数据将被当作“指令”字节序列,送往 指令解码器(Instruction Decoder)。
解码(Decode)阶段
- 指令解码器把取出的二进制序列,按本机 指令集 的格式来解释:哪几位表示操作码、哪几位表示操作数,等等。
- 如果解码成功,就表明这段二进制确实是合法的指令;若解码失败或不合法,就会导致异常(非法指令异常)。
执行(Execute)阶段
- 根据解码结果,CPU 执行相应的操作(如加法、内存访问、跳转等)。
- 执行完后,将 PC 更新到下一条指令地址,进入下一轮取指、解码、执行的循环。
由此可见:CPU 是靠“程序计数器 + 指令解码器”的协同工作,将取到的内存数据解释为指令的。
2. 内存数据是指令还是普通数据,取决于程序逻辑和PC的指向
- 内存中存放的不仅仅是“程序指令”,也可能是“纯数据”。
- CPU 并不会自动区分“这是代码还是数据”,它只是从 PC 指向的位置取数据,然后试图将这些数据“解码”为指令。
- 如果有错误地跳转到一段本该是数据区域的地址,那么 CPU 取到的数据就会被当作指令解码,多数情况下会出现 非法指令 错误或执> 行一些意料之外的操作(这在安全漏洞利用中也很常见,称为“代码注入”或者“shellcode”)。
3. 编译器、操作系统与链接装载器的配合
- 编译器
- 将高级语言编译为目标文件(机器码),里面包含指令的二进制序列和一些数据区。
- 链接器(Linker)
- 把多个目标文件与库文件链接起来,生成可执行文件。
- 在可执行文件中,会将“代码段(.text)”和“数据段(.data / .bss)”等分门别类地放入不同区域。
- 装载器(Loader)
- 操作系统把可执行文件装载到内存对应的地址空间中,建立“代码段区域”、“数据段区域”等映射关系。
- 程序入口地址
- 当操作系统把程序运行起来后,CPU 的 PC 指向程序入口地址(如
_start
),开始从那里取指令执行。这样一来,放在“代码段”里的那部分内存内容,就成为 CPU 要执行的“指令”;而放在“数据段”里的则通常不会被 PC 指向,也就不会被> 当作指令执行。
4. 为什么要有“指令解码器”?
- 每种 CPU 都有特定的 指令集架构(ISA, Instruction Set Architecture)。
- 指令解码器的任务就是根据 ISA 的编码格式,解析取到的二进制指令。
- 例如,在 x86-64 下,一条指令可能包含可变长度的操作码和操作数修饰;在 ARM64 下,一般是固定 32 位对齐等。
- 解码器负责把这些“比特组合”翻译成 CPU 内部能执行的“微指令(Micro-ops)”或“微码(Microcode)”,进而驱动算术逻辑单元> (ALU)、寄存器、总线操作等。
5. 小结
- CPU 将内存中的信息当作指令的前提:该内存区域的地址恰好是 程序计数器(PC)指向的地方。
- 实现机制:
- CPU 在“取指阶段”根据 PC 读取指定内存地址的内容。
- “解码阶段”把读取到的二进制按照指令集格式解释。
- 如果合规,进入“执行阶段”;否则产生异常。
- 谁来告诉 CPU 该取哪段内存?
- 主要依赖 PC 的值,而 PC 的更新又受指令本身(如跳转、调用、返回等)以及操作系统管理的可执行文件布局影响。
因此,从物理层面上来说,CPU 并不知道“哪段内存是指令、哪段是数据”,它只是单纯地按照 PC 指向的地址取数据,并通过指令解码器解释那些比特流。如果能正确解码并符合权限,CPU 就执行;否则就报错或引发异常。
2.11 修改CS、IP的指令
jmp 段地址:偏移地址 指令的功能位:用指令中的段地址修改CS,偏移地址修改IP。
jmp 某一合法寄存器 指令的功能为:用寄存器中的值修改IP。
2.12 代码段
什么是内存单元? 16位、32位、64位CPU内存单元,有什么区别?
内存单元*(Memory Cell)是计算机内存中的最基本存储单位,用于存储单个数据元素。每个内存单元都有一个唯一的地址,便于 CPU 读取和写入数据。内存单元通常以字节(Byte)为单位,但在不同的 CPU 架构(如 16 位、32 位、64 位)中,内存单元的组织和处理方式会有所不同。以下是对内存单元的详细介绍及其在不同位数 CPU 中的区别。
--
一、什么是内存单元?
1. 定义
内存单元是计算机内存中能够存储一个固定大小数据的基本单位。每个内存单元都有一个唯一的地址,便于 CPU 进行数据访问。 存储单位:通常以 字节(Byte) 为单位,一个字节由 8 位(bit) 组成。
2. 结构
地址:每个内存单元都有一个唯一的地址,用于标识其位置。例如,地址
0x0000
、0x0001
等。 存储内容:每个内存单元可以存储一个字节的数据,表示为 8 个二进制位(如10101010
)。3. 功能
数据存储:用于存储程序指令、变量、缓冲区等各种数据。 数据传输:CPU 通过内存总线(数据总线、地址总线、控制总线)与内存单元进行数据交换。
--
二、16位、32位、64位 CPU 内存单元的区别
PU 的位数(16位、32位、64位)主要指的是其 寄存器宽度、数据总线宽度 和 地址总线宽度,这些因素共同影响内存单元的访问和管理方式。以下从几个关键方面对比不同位数 CPU 的内存单元:
1. 数据总线宽度
定义:数据总线是 CPU 与内存之间传输数据的通道,其宽度决定了每次可以传输的数据位数。
区别:
- 16位 CPU:数据总线宽度为 16 位,每次可以传输 2 字节 数据。
- 32位 CPU:数据总线宽度为 32 位,每次可以传输 4 字节 数据。
- 64位 CPU:数据总线宽度为 64 位,每次可以传输 8 字节 数据。
影响:
- 传输效率:位数越高,每次数据传输的字节数越多,数据传输效率越高。
- 带宽需求:更宽的数据总线需要更高的带宽支持,以维持高效的数据传输速率。
2. 地址总线宽度和内存寻址能力
定义:地址总线用于传输内存地址,决定了 CPU 可以直接寻址的内存空间大小。
区别:
- 16位 CPU:
- 地址总线宽度通常为 20 位(如 Intel 8086),通过分段机制扩展至 1 MB 地址空间。
- 现代 16位系统可能采用不同的地址扩展技术,但总体寻址能力有限。
- 32位 CPU:
- 地址总线宽度为 32 位,可以直接寻址 4 GB(2³² 字节)内存。
- 64位 CPU:
- 地址总线宽度通常为 48 位 至 52 位(具体取决于架构和实现),理论上可寻址 256 TB(2⁴⁸ 字节)至 4 PB(2⁵² 字节)内存。
影响:
- 内存容量:更高位数的 CPU 支持更大容量的物理内存,有利于运行需要大量内存的应用(如数据库、虚拟化、科学计算)。
- 内存管理:更大的地址空间允许更复杂和高效的内存管理策略,如分页、分段和虚拟内存。
3. 寄存器宽度
定义:寄存器是 CPU 内部用于存储临时数据的高速存储单元,其宽度决定了单次指令可以处理的数据大小。
区别:
- 16位 CPU:拥有 16 位宽的寄存器,适合处理 16 位数据。
- 32位 CPU:拥有 32 位宽的寄存器,适合处理 32 位数据。
- 64位 CPU:拥有 64 位宽的寄存器,适合处理 64 位数据。
影响:
- 计算能力:寄存器宽度越大,单次指令能处理的数据越多,计算能力越强。
- 数据类型支持:更宽的寄存器更适合处理大型数据类型,如 64 位整数、双精度浮点数等。
4. 内存单元对齐和访问效率
定义:内存对齐是指数据在内存中的存储地址必须满足特定的对齐规则(如 2 字节、4 字节、8 字节对齐)。
区别:
- 16位 CPU:通常要求 2 字节对齐,减少内存访问次数。
- 32位 CPU:通常要求 4 字节对齐,进一步提高访问效率。
- 64位 CPU:通常要求 8 字节对齐,优化对大数据类型的访问。
影响:
- 性能:正确的对齐可以减少内存访问次数,提高缓存命中率和整体性能。
- 兼容性:某些架构对对齐要求严格,不符合对齐规则可能导致性能下降甚至异常。
5. 指令集和内存操作
定义:不同位数的 CPU 通常伴随不同的指令集,影响如何操作内存。
区别:
- 16位 CPU:指令集较为简单,内存操作通常以 16 位为单位。
- 32位 CPU:指令集扩展,支持 32 位内存操作,提供更多的内存操作指令。
- 64位 CPU:指令集进一步扩展,支持 64 位内存操作,提供更高效的内存管理指令,如大页支持、增强的虚拟化指令等。
影响:
- 编程效率:更丰富的指令集简化了编程,实现复杂内存操作更为高效。
- 优化能力:高级指令(如 SIMD 指令)允许在内存操作中实现并行处理,提高性能。
--
三、总结
内存单元的基本概念
内存单元是计算机内存中存储单个字节数据的基本单位,每个内存单元都有唯一的地址。 字节对齐和寄存器宽度是影响内存单元访问效率和数据处理能力的重要因素。
不同位数 CPU 内存单元的主要区别
特性 16位 CPU 32位 CPU 64位 CPU 数据总线宽度 16 位(每次传输 2 字节) 32 位(每次传输 4 字节) 64 位(每次传输 8 字节) 地址总线宽度 20 位(约 1 MB,使用分段机制) 32 位(4 GB) 48-52 位(256 TB - 4 PB) 寄存器宽度 16 位 32 位 64 位 内存对齐要求 2 字节对齐 4 字节对齐 8 字节对齐 指令集复杂度 较简单,操作以 16 位为主 中等,支持 32 位内存操作 复杂,支持 64 位内存操作及高级指令 应用场景 早期计算机、嵌入式系统、简单任务 个人电脑、早期服务器、中等性能应用 现代 PC、服务器、高性能计算、大数据处理 实际影响
性能:随着 CPU 位数的增加,内存单元的访问效率和数据处理能力显著提升,满足了现代应用对高性能和大内存的需求。 内存容量:64 位 CPU 支持的内存容量远超 16 位和 32 位 CPU,适应了大规模数据处理和多任务并行的需求。 编程与优化:更高位数的 CPU 提供了更丰富的指令集和优化手段,提升了编程效率和程序执行性能。
--
综上所述*,内存单元作为计算机内存的基本存储单位,在不同位数的 CPU 架构中,其组织和处理方式随着位数的增加而变得更高效和灵活。16 位、32 位和 64 位 CPU 在数据总线宽度、地址总线宽度、寄存器宽度、内存对齐和指令集等方面的差异,决定了它们在性能、内存容量和应用场景上的不同表现。
3. 寄存器(内存访问)
3.1 内存中字的存储
字单元的概念:字单元,即存放一个字形数据(16位,和CPU位数有关系)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。
3.2 DS和[address]
CPU读内存单元的时候,要给出内存单元的地址,在8086CPU中,内存地址由段地址和偏移地址组成。
DS寄存器,用来存放要访问数据的段地址。
3.3 字的传送
3.4 mov,add,sub指令
现在CPU中16位、32位、64位系统中,内存中地址单元和地址字单元的存储?
在谈及 16 位、32 位、64 位系统时,很多人会把“CPU 位数”与“内存地址单元”、“数据存储单元”这些概念混在一起。其实,从 8 位机开始,绝大部分现代通用计算机体系结构都采用 “按字节(Byte)寻址” 的方式(即 byte-addressable),而 CPU 的位数 则主要决定了“一次能处理多少位的数据”和“地址总线(可寻址空间)多宽”,并不改变最小可寻址单位依旧是字节这一事实。下面从几个角度来回答“16位、32位、64位系统中,内存中地址单元和地址字单元的存储”这个问题。
1. 最小可寻址单位:字节(Byte)
共同点:
无论是 16 位、32 位还是 64 位的 CPU,内存中 最小的“地址单元”都是 1 个字节。也就是说,内存地址0x0000
和0x0001
指向的是两个不同的字节。CPU 通过地址总线上的二进制数来选中某一个字节的存储单元。原因:
早期(更早于 8 位机时代)可能出现过“位寻址”或“字寻址”的特殊体系结构,但在 现代通用计算机 中,“字节寻址” 成为事实标准,因为字节(8 bits)在数据表示上最为通用(一个 ASCII 字符正好占 1 字节)且灵活。2. CPU “位数” 与 “字(Word)” 的概念
CPU 位数
- 16 位 CPU:一次能处理(或寄存器宽度)是 16 位,数据总线一般也是 16 位。
- 32 位 CPU:一次能处理 32 位数据,数据总线一般是 32 位。
- 64 位 CPU:一次能处理 64 位数据,数据总线通常是 64 位。
“字(Word)”的大小
- 在 CPU 内部,所谓的 “一个字(Word)” 通常指 CPU 最擅长或最自然处理的数据宽度。
- 对于 16 位 CPU,一个“字”就是 16 bits(2 字节);
- 对于 32 位 CPU,一个“字”就是 32 bits(4 字节);
- 对于 64 位 CPU,一个“字”就是 64 bits(8 字节)。
为什么不直接用“字”做最小寻址单位?
- 如果采用“字”寻址,那么在 16 位系统中,地址
0x0001
指向的就不是下一个字节,而是下一个 16 位单元。这样在处理字节级别的数据时就会相当麻烦。- 所以现代主流体系结构都坚持 “字节” 作为最小地址单位,而 “字” 只是 CPU 自身的自然处理宽度,跟地址空间组织是两件事。
3. 地址总线宽度与可寻址空间
16 位系统
- 经典例子:Intel 8086 虽然是 16 位处理器,但最初的地址总线是 20 位(通过分段机制组合出 20 位地址线),可寻址 1 MB((2^{20} = 1,048,576) 字节)的内存。
- 如果真的是纯粹 16 位地址总线(没有分段扩展),那就是 (2^{16} = 65536) 字节,也就是 64 KB 的地址空间。
32 位系统
- 通常拥有 32 位的地址总线,就能够直接寻址 (2^{32} = 4,294,967,296) 字节,也就是 4 GB 内存空间。
- 这也是 32 位操作系统在不借助特殊扩展(如 PAE)时,用户态进程能用的内存上限常常在 2~3 GB 左右(还得留一点给内核)。
64 位系统
- 理论上拥有 64 位地址线可寻址 (2^{64} = 16) EB(ExaBytes)的内存,但实际 CPU、主板、操作系统并不会实现全部 64 根物理地址线。
- 目前常见的 x86-64 实现物理地址线在 48~52 位左右,对应的物理内存上限为 256 TB ~ 4 PB。
- 即便如此,也远超普通桌面或服务器的内存需求,给未来扩展留了很大余量。
4. 内存中如何存储“地址”本身
- 存储地址所需的字节数
- 16 位系统:如果要在程序中保存一个地址,就需要 16 位(2 字节)来存这个地址值。
- 32 位系统:需要 32 位(4 字节)来存一个地址。
- 64 位系统:需要 64 位(8 字节)来存一个地址。
- 在内存中,这些地址依旧是以字节为单位顺次存储
- 例如,在 x86(小端序)平台上,假设某个指针(地址)值是
0x12345678
(32 位),它会在内存中以字节的顺序存放为78 56 34 12
(从低地址到高地址依次为 78、56、34、12)。5. 对齐与存取效率
- 对齐(Alignment)
- 通常 16 位系统更注重 2 字节对齐,32 位系统常见 4 字节对齐,64 位系统则常见 8 字节对齐。
- 即如果数据类型是一个 32 位整数,在 32 位系统上最好放在地址能被 4 整除的地方;在 64 位系统上,若是 64 位整数或指针,最好放在能被 8 整除的地址上。
- 好处:
- 对齐可以让 CPU 一次取数(对齐的数据)就能完成对该数据的读取,无需额外的拆分或拼接,大大提高访问效率。
6. 小结
内存“地址单元”仍然是字节
- 这在 16 位、32 位、64 位系统中都相同,都是 “byte-addressable”。
CPU 位数决定“自然字宽度”、能一次处理的数据大小,以及最大寻址能力
- 16 位 CPU 每次擅长处理 16 位(2 字节);
- 32 位 CPU 每次擅长处理 32 位(4 字节);
- 64 位 CPU 每次擅长处理 64 位(8 字节)。
- 同时,地址总线决定最大可寻址空间大小。
“地址字单元”指的是存储一个完整地址所需的宽度
- 16 位系统存放一个地址需 2 字节;
- 32 位系统需要 4 字节;
- 64 位系统需要 8 字节。
- 这些字节也依旧是以“字节”的形式顺序存放在内存里(可能涉及大小端)。
对齐与效率
- CPU 位数越高,通常需要更宽的对齐边界才能最高效地读取/写入数据。
- 但从本质看,最小可寻址单位依旧是“1 字节”。
核心总结
- “按字节寻址”是一条“金科玉律”——在当今主流 CPU(无论 16、32 还是 64 位)里,都保持最小地址单元=1 字节。
- CPU 位数 更多影响的是 一次处理数据的位宽 以及 地址总线的宽度,进而影响 可用内存容量、处理效率、对齐要求 等。
- 当我们说“16 位、32 位或 64 位”系统时,并不代表“内存中一单元就是 2 字节、4 字节或 8 字节”,而是指 CPU“自然处理字长”和“地址总线/寄存器宽度”的变化;内存仍然是一字节一字节地被寻址。
3.5 数据段
一个字型数据在16位CPU中,占两个单元,所以偏移地址是0,2,4。
3.5 小结
在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
3.6 栈
3.7 CPU提供的栈机制
将某一段内存当做栈使用。
8086CPU的入栈和出栈都是以字为单位进行的。
CS:IP中存放着当前指令的段地址和偏移地址。
SS:SP指向栈顶元素,栈顶的段地址存放在SS中,偏移地址存放在SP中。
pop SP=SP+2 push SP=SP-2
3.8 栈顶超界问题
8086CPU需要自己处理栈顶超界的问题。
3.9 push,pop 指令
3.10 栈段
3.10 段的综述
用一个段存放数据 - 数据段 用一个段存放代码 - 代码段 用一个段当做栈 - 栈段
对于数据段,将他的段地址放在DS中,用mov,add,sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当做数据来访问。 对于代码段,将他的段地址放在CS中,将段中的第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义代码段中的指令。 对于栈段,将他的段地址放在SS中,将栈顶单元你的偏移地址放在SP中,这样push、pop指令,就会将我们定义的栈段当做栈空间使用。
CPU将内存中的某段内容当做代码,是因为CS:IP指向了那里。 CPU将内存中某段内容当做栈,是因为SS:SP指向了那里。
PUSH和POP实质上是一种内存传送指令。
64KB = 65536 = 0xFFFF = 64*1024
3.2 检测点
https://blog.csdn.net/fencecat/article/details/113751759
4. 第一个程序
4.1 一个源程序从写出到执行的过程
- 编写汇编源程序
- 对源程序进行编译连接。用编译程序编译源文件,产生目标文件;再用连接程序对目标文件进行连接,生成可执行文件。
- 程序(从源程序中汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(程序有多大,占用多少内存空间)
- 执行可执行文件中的程序。
操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(CS:IP,指向第一条要执行的指令),然后CPU执行。
4.2 源程序
汇编语言:
- 汇编指令,有对应机器码的指令。
- 伪指令,没有对应机器码,不会被CPU执行。编译器来执行伪指令。
assume:
- 假设某一寄存器和程序中的某一个segment...ends定义的段相关联。
程序:
- 程序最先以汇编指令的形式存在源程序中,经过编译、连接后转变位机器码,存储在可执行文件中。
标号:
- 一个标号指代了一个地址。最终将被编译、连接程序处理位一个段的段地址。
4.3 编辑源程序
4.4 编译
经过编译之后,编译器masm.exe的目录出现一个新的1.obj文件,这是对源程序1.asm进行编译得到的结果。
在编译过程中,提供一个源程序文件,可以得到:目标文件.obj,列表文件.lst,交叉引用文件.crf。
4.5 连接
学习汇编的主要目的:
- 就是通过汇编语言进行编程而深入的理解计算机底层的基本工作机理,达到可以随心所欲的控制计算机的目的。
编辑器-edit 编译器-masm 连接器-link 调试工具-debug
连接的作用:
- 当源程序很大时,将他氛围多个源程序文件来编译,每个源程序编译成为目标文件后,再用连接程序将他们连接到一起,生成一个可执行文件。
- 程序中调用了某个库中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件。
- 一个源程序编译后,得到了存有机器码的目标程序,目标文件中的有些内容还不能够直接用来生成可执行文件。
4.6 以简化的方式进行编译和连接
masm demo1.asm;
4.7 exe的执行
4.8 谁将可执行文件中的程序装在进入内存并使它运行?
汇编程序从写出到执行的过程
- edit -> 1.asm -> masm -> 1.obj -> link -> 1.exe -> command -> 内存中的程序 -> CPU
4.9 程序执行过程的跟踪
汇编语言-王爽 第四版中的这段话,我不理解,帮我重新解释一下。?
下面我先把原文逐句拆开,然后用更通俗的语言来解释它到底在讲什么。原文内容是(略有改动,但核心意思不变):
1)“程序加载后,
ds
中存放着程序所在内存区的段地址,这个内存区的偏移地址为 0,则程序所在的内存区的地址为ds:0
。”2)“这个内存区的前 256 个字节中存放的是 PSP(Program Segment Prefix),DOS 用来和程序进行通信。从 256 字节处(也就是偏移量 100H)开始存> 放的才是程序。”
所以,从
ds
可以得到 PSP 的段地址为SA
,PSP 的偏移地址为 0,则 PSP 的物理地址为SA × 16 + 0
。
因为 PSP 占 256(100H)字节,所以程序的物理地址就是:
[ SA \times 16 + 256 ;=; SA \times 16 + 16 \times 16 ;=; (SA + 16) \times 16 + 0 ]
可用段地址和偏移地址表示为:(SA + 10H):0
。重新解释这段话的来龙去脉
程序加载位置
当 DOS 把一个可执行程序加载到内存里时,会设置好DS
段寄存器,使它指向“程序的根基”所在的那一段。与此同时,程序起始的偏移默认是0
。
- 也就是说,“程序所在的内存区”的入口是
ds:0
。- 但这里有个“细节”:在 DOS 下,真正程序指令并不是立刻从
ds:0
开始放的,而是留出了 256 字节给 PSP。什么是 PSP(Program Segment Prefix)?
- PSP 是 DOS 留给每个程序的一个“前置块”,大小固定 256 字节(也就是 100H)。
- DOS 会在这 256 字节里存放一些和当前程序相关的环境信息、跳转入口、中断处理地址之类的内容,用于实现“程序与 DOS 之间的通信”。
程序真正的代码放在哪里?
- 因为 PSP 占了从段基址(
ds:0
)开始的前 256 字节,所以程序的代码要从 偏移地址100H
才正式开始存放。- 换句话说,假设
ds=SA
,那么程序代码的物理地址其实从
[ SA \times 16 + 100H ]
这个位置才开始。为什么最后给出
(SA+10H):0
这样的段:偏移表示?
- 在 8086 中,物理地址 = 段地址 × 16 + 偏移地址。
- PSP 的起始物理地址是
SA × 16
;它占 256 字节(100H)。- 程序起始物理地址就是
SA × 16 + 100H
。- 但是你也可以把同一个物理地址表示成另外一个段:偏移组合:
[ (SA + 10H) \times 16 + 0 = SA \times 16 + 10H \times 16 + 0 = SA \times 16 + 100H ]- 所以,程序起始的物理地址“等价”地可以写成
(SA+10H):0
。举个“门牌号”类比
- 可以把“物理地址”想象成一个实际的门牌号,比如“XX 街 123 号”。
- “段地址 × 16 + 偏移地址”就像“街区编号 + 巷内门牌”的组合,最后能找到同一个实际地址。
- 一栋大楼(段地址)从“0 楼”开始往上算(偏移地址),但如果你想从第 16 层(10H 楼)开始,也可以说这是另一栋楼(段地址 + 10H),从 0 层开> 始。这两种描述实际上指向同一个物理位置。
结论与要点
DS:0
并不一定就是你程序的第一条指令所在位置;在 DOS 环境下,前 100H 个字节给了 PSP 用。- 程序的真正入口往往是
DS:100H
。- 同一个物理地址可以有多种“段:偏移”的写法。把程序起始地址写成
(DS+10H):0
在计算上等价于DS:100H
,都是指向相同的物理地址。以上就是这段话的核心含义:**DOS 会在程序前预留 100H 字节给 PSP,而程序真正的起始(物理)地址可以通过计算发现,它“等效于”把段地址往前加 > 10H,然后偏移清零。**这样做在汇编中很常见,也是一种灵活运用“段:偏移”机制的体现。
“大小固定 256 字节(也就是 100H)”,这段话我不理解, 256个字节不应该是0xFF么?为什么是0x100?
这是一个进制转换的问题:
- 256 在十进制是 256。
- 把 256 转换成十六进制时,结果是 0x100。
- 而 0xFF(十六进制)等于 255(十进制),比 256 小 1。
因此,256(十进制)对应的十六进制不是 0xFF,而是 0x100。这是因为:
- 0xFF = (15 × 16¹) + (15 × 16⁰) = 255
- 0x100 = (1 × 16²) + (0 × 16¹) + (0 × 16⁰) = 256
所以当我们说 “大小固定 256 字节(也就是 100H)” 时,指的就是:
- 256 (十进制) = 0x100 (十六进制)
而不是 0xFF。
实验 3 编程、编译、连接、跟踪
为什么上述汇编程序执行完毕后, AX BX交换了数据?
先大致看一下这段程序的核心指令(与栈操作相关的部分):
mov ax,2000H mov ss,ax mov sp,0 add sp,10 ; SP = 0010H pop ax pop bx push ax push bx pop ax pop bx mov ax,4C00H int 21H
从
mov ss,ax
到add sp,10
这几条指令只是设置了栈段(SS=2000H)和栈顶指针(SP=0010H),真正导致 AX 与 BX 互换的关键是下面这六条指令(可以把它们拆分成三组 pop/push/push/pop 操作来理解):
pop ax
pop bx
push ax
push bx
pop ax
pop bx
下面按顺序分析寄存器与栈顶(SP)的变化。为了更直观,我们用「栈」的图示来表示每一次操作。初始时 SP=0010H。
1)pop ax
作用:从栈顶取出 2 字节赋给 AX,然后 SP 向上增加 2(栈在 x86 中是“向下生长”,pop 会增加 SP)。
前后状态(假设内存中的内容我们称之为 val1、val2 等):
SS:SP=2000:0010 <-- 栈顶(未知内容 val1) SS:0012 <-- (未知内容 val2) SS:0014 ... ; 执行 pop ax AX <- [2000:0010] (取到 val1) SP = 0012
执行完毕后:AX = val1,SP=0012H。
2)pop bx
作用:从栈顶(此时的 SP=0012H)取 2 字节赋给 BX,SP 再向上增加 2。
前后状态:
SS:SP=2000:0012 <-- 栈顶(未知内容 val2) SS:0014 SS:0016 ... ; 执行 pop bx BX <- [2000:0012] (取到 val2) SP = 0014
执行完毕后:BX = val2,SP=0014H。
3)push ax
作用:将 AX 的内容压入栈中,即 [SS:SP - 2] <- AX,SP 减 2。
前后状态:
原先 SP=0014,执行 push ax 之后: SP = 0012 [2000:0012] <- AX (即 val1)
执行完毕后:栈顶(0012H)中存放了 val1,SP=0012H。
4)push bx
作用:将 BX 的内容压入栈中,即 [SS:SP - 2] <- BX,SP 减 2。
前后状态:
原先 SP=0012,执行 push bx 之后: SP = 0010 [2000:0010] <- BX (即 val2)
执行完毕后:栈顶(0010H)中存放了 val2,SP=0010H。
此时的栈布局大致如下:
栈顶→ [2000:0010] = val2 [2000:0012] = val1
5)pop ax
作用:将当前栈顶 [2000:0010] 中的内容(即 val2) 弹出给 AX,SP += 2。
前后状态:
AX <- val2 SP = 0012
6)pop bx
作用:将当前栈顶 [2000:0012] 中的内容(即 val1) 弹出给 BX,SP += 2。
前后状态:
BX <- val1 SP = 0014
到这里,原来最开始弹出来时
AX=val1
,BX=val2
,经过这番「pop/push/push/pop」后,AX 里变成了 val2,BX 里变成了 val1。也就是说,AX 与 BX 的内容被交换了。小结
- 这段代码用「栈」作为中介,先
pop ax/pop bx
得到初始的 AX/BX,再push ax/push bx
、pop ax/pop bx
来完成交换。- 之所以程序结束后看起来「AX、BX 的数据被互换了」,就是因为这几条
pop/push
的组合恰好实现了一个 swap 效果。- 这是一种比较“绕”的交换方法,但在有些场合可能会用到(尤其在不用专门的交换指令时)。
因此,当你调试跟踪完
t1.exe
后会发现,最初保存在「栈顶」的那两个字(最先弹到 AX、BX 里的值) 在最后执行完所有指令之后,其实已经「对调」到了 AX、BX 中。
5. [BX]和loop指令
- [bx]和内存单元的描述
- 内存单元:内存单元的地址;内存单元的长度(类型)
- loop
- ():表示一个寄存器或者一个内存单元中的内容
- 三种类型:
- 寄存器名
- 内存单元的物理地址
- (ax) (ds) (al) (cx) (20000h) ((ds)*16+(bx))
- 三种类型:
5.1 [BX]
bx中存放的数据作为一个偏移地址EA,段地址SA默认在DS中,将SA:EA处的数据送入ax中,即:(ax) = ((ds)*16+(bx)) mov ax,[bx]
bx中存放的数据作为一个偏移地址EA,段地址SA默认在DS中,将ax中的数据送入内存SA:EA处,即:((ds)*16+(bx))=(ax)
5.2 Loop指令
loop格式:
- loop 标号
loop执行:
- (cx)=(cx)-1
- 判断cx中的值,不为零则转至标号处执行程序,为零则向下执行。
通常使用loop指令来实现循环,cx中存放循环次数。
assume cs:code
code segment
mov ax,2
mov cx,11
s: add ax,ax
loop s
mov ax,4C00H
int 21h
ends
end
标号:
- 在汇编语言中,标号代表一个地址,他实际上标识了一个地址,这个地址处有一条指令:add ax,ax
loop s:
- CPU执行loop s的时候,要进行两步操作:
- (cx)=(cx)-1
- 判断cx的值,不为0则转至标号s处所标识的地址处执行,如果为0则执行下一条指令。
用cx和loop指令配合实现循环功能的程序框架如下:
mov cx,循环次数
s:
循环执行的程序段
loop s
5.3 在Debug中跟踪用loop指令实现的循环程序
5.4 Debug和汇编编译器masm对指令的不同处理
mov al,ds:[0] ; (al)=((ds)*16+0)
5.5 loop和[bx]的联合应用
5.6 段前缀
用于显示的指明内存单元的段地址的ds: cs: ss: es:,称为段前缀
5.7 一段安全的空间
0:200~0:2FF这段是安全空间
5.8 段前缀的使用
assume cs:code
code segment
mov bx,0
mov cx,12
s: mov ax, 0ffffh
mov ds,ax
mov dl,[bx]
mov ax,0020H
mov ds,ax
mov [bx],dl
inc bx
loop s
mov ax, 4C00H
int 21h
code ends
end
assume cs:code
code segment
mov ax, 0ffffh
mov ds,ax
mov ax,0020H
mov es,ax
mov bx,0
mov cx,12
s: mov dl,ds:[bx]
mov es:[bx],dl
inc bx
loop s
mov ax, 4C00H
int 21h
code ends
end
实验4 [bx]和loop的使用
assume cs:code
mov ax,0
mov ds,ax
mov bx,200H
mov cx,64
mov al,0
s: mov [bx], al
inc bx
inc al
loop s
mov ax,4C00H
int 24h
code ends
end
assume cs:code
mov ax,0020H
mov ds,ax
mov cx,64
mov al,0
s: mov [al], al
inc bx
inc al
loop s
mov ax,4C00H
int 24h
code ends
end
assume cs:code
mov ax,cs
mov ds,ax
mov ax,0020H
mov es,ax
mov bx,0
mov cx, 17
s: mov al,[bx]
mov es:[bx], al
inc al
loop s
mov ax,4C00H
int 24h
code ends
ends
6. 包含多个段的程序
6.1 在代码段中使用数据
assume cs:code
dw 0123h, 0456h, 0789h, 0abch,0defh,0fedh,0cbah,0987h
start: mov bx,0
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00H
int 21H
code ends
end start
如何知道那一条指令是程序的第一条要执行的指令? 由可执行文件中的描述信息指明的。
可执行文件由描述信息和程序组成,程序来自于源程序中的汇编指令和定义的数据;描述信息则主要是编译、连接程序对源程序中相关伪指令进行处理所得到的信息。
6.2 在代码段中使用栈
6.3 将数据、代码、栈放入不同的段
assume cs:code,ds:data,ss:stack
data segment
dw 0123h, 0456h, 0789h, 0abch,0defh,0fedh,0cbah,0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,20h
mov ax,data
mov ds,ax
mov bx,0
mov cx,8
s: push ds:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0: pop ds:[bx]
add bx,2
loop s0
mov ax,4C00H
int 21h
code ends
end start
实验5 编写、调试具有多个段的程序
7.0 更灵活的定位内存地址的方法
7.1 and 和 or 指令
and指令,逻辑与指令,按位进行与运算
mov al,01100011B and al,00111011B
-> al=01000011B
or指令,逻辑或指令,按位进行或运算
mov al,01100011B or al,00111011B
-> al=01111011B
7.2 关于ASCII码
7.3 以字符形式给出的数据
assume cs:code,ds:data
data segment
db 'biezhihua'
db 'huyuqiong'
data ends
code segment
start: mov al,'a'
mov bl,'b'
mov ax,4c00H
int 21h
code ends
end start
7.4 大小写转换问题
大写字母ASCII码的第五位为0 小写字母ASCII码的第五位为1
assume cs:codesg, ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
and al,11011111B
mov [bx], al
inc bx
loop s
mov bx,5
mov cx,11
s0: mov al,[bx]
or al,00100000B
mov [bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
7.5 [bx+idata]
[bx+idata]表示一个内存单元,他的偏移地址为(bx)+idata,bx中的数值加上idata
mov ax,[bx+200]的含义:
- (ax)=((ds)*16+(bx)+200)
mov ax,[bx+200] = mov ax, 200[bx] = mov ax,[bx].200
7.6 用[bx+idata]的方式进行数组的处理
assume cs:codesg, ds:datasg
datasg segment
db 'BaSiC'
db 'MinIX'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx+0]
and al,11011111B
mov [bx],al
mov al,[bx+5]
or al,00100000B
mov [bx+5],al
inc bx
loop s
codesg ends
end start
char a[5] = "BaSiC"
char b[5] = "MinIX"
main() {
int i;
i = 0;
do {
a[i] = a[i]&0xDF;
b[j] = b[j]|0x20;
} while(i<5)
}
C语言:a[i]、b[i] 汇编语言:0[bx]、5[bx]
idata[bx]=[bx+idata]的方式为高级语言实现数组提供了便利。
7.7 SI和DI
si和di是16位寄存器,一次复制2字节。
codesg segment
start: mov ax,datasg
mov ds,ax
mov si,0
mov cx,8
s: mov ax,[si+0]
mov [si+16], ax
add si,2
loop s
mov ax,4c00H
int 21H
codesg ends
可以利用idata[bx/si/di]的方式,是程序变得简介。
7.8 [bx+si]和[bx+di]
[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)。
(ax)=((ds)*16+(bx)+(si))
mov ax, [bx][si] = mov ax, [bx+si]
7.9 [bx+si+idata]和[bx+di+idata]
mov ax,[bx+si+idata] mov ax,200[bx][si] mov ax,[bx].200[si]
(ax)=((ds)*16+(bx)+(si)+idata)
7.10 不同的寻址方式的灵活应用
定位内存地址的方法=寻址方式
- [idata]用一个常亮来表示地址,可用于直接定位一个内存单元。
- [bx]用一个变量来表示内存地址,可用于简介定位一个内存单元。
- [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元。
- [bx+si]用两个变量表示地址。
- [bx+si+idata]用两个变量和一个常量表示地址。
assume cs:codesg, ds:datasg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
codesg segment
start:
mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:
push cx
mov si,0
mov cx,3
s1: mov al,[bx+si]
and al, 11011111B
mov [bx+si] = al
inc si
loop s0
add bx,16
pop cx
loop s1
mov ax,4c00H
int 21H
codesg ends
end start
一般来说,在需要暂存数据的时候,我们都应该使用栈。
assume cs:code,ds:data,ss:stack
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
data segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
data ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,data
mov ds,ax
mov bx,0
mov cx,4
s0:
push cx
mov si,0
mov cx,4
s1:
mov al,ds:[bx+si+3]
and al,11011111b
mov ds:[bx+si+3],al
inc si
loop s1
add bx,16
pop cx
loop s0
mov ax,4c00h
int 21h
code ends
end start
实验 6 实践课程中的程序
8. 数据处理的两个基本问题
处理的数据在什么地方?
要处理的数据有多长?
reg 来表示一个寄存器:
- ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di
sreg来表示一个段寄存器:
- ds,ss,cs,es
8.1 bx、si、di、bp
- 在8086CPU中只有这四个寄存器可以用在[...]中来进行单元的寻址。
- 在[...]中,这4个寄存器可以单个出现,后者只能以4中组合出现。
- 只要在[...]中使用寄存器bp,而指令中没有显性的给出段地址,段地址就默认在ss中。
8.2 机器指令处理的数据在什么地方?
读取、写入、运算。
在指令执行前的一刻,他将要处理的数据所在的位置。
在指令执行前,要处理的数据可以在3个地方:CPU内部、内存、端口。
- 内存、寄存器、指令缓冲器、端口
8.3 汇编语言中数据位置的表达
在汇编语言中如何表达数据的位置?
- 立即数(idata)
- 对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编语言中称为:立即数-idata
mov ax,1
add bx,2000H
or bx,00010000B
mov al,'a'
- 寄存器
- 指令要处理的数据在寄存器中,在汇编指令中个提出相应的寄存器名。
mov ax,bx
mov ds,ax
push bx
mov ds:[0],bx
push ds
mov ss,ax
mov sp,ax
- 段地址SA和偏移地址EA
- 指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中。
- di/bx,段地址默认在DS中
- bp 段地址默认在SS中
- 也可以显性给出
8.4 寻址方式
当数据放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存单元的方法一般被称为寻址方式。
8086CPU 有多种寻址方式,我们在前面的课程中已经用到了,这里进行一下总结
寻址方式 | 含义 | 名称 | 常用格式举例 |
---|---|---|---|
[idata] | EA = idata; SA = (ds) | 直接寻址 | [idata] |
[bx] | EA = (bx); SA = (ds) | 寄存器间接寻址 | [bx] |
[si] | EA = (si); SA = (ds) | [si] | |
[di] | EA = (di); SA = (ds) | [di] | |
[bp] | EA = (bp); SA = (ss) | [bp] | |
[bx + idata] | EA = (bx) + idata; SA = (ds) | 寄存器相对寻址 | 用于结构体: [bx].idata |
[si + idata] | EA = (si) + idata; SA = (ds) | 用于数组: idata[si] , idata[di] | |
[di + idata] | EA = (di) + idata; SA = (ds) | 用于二维数组: [bx][idata] | |
[bp + idata] | EA = (bp) + idata; SA = (ss) | [bp + idata] | |
[bx + si] | EA = (bx) + (si); SA = (ds) | 基址变址寻址 | 用于二维数组: [bx][si] |
[bx + di] | EA = (bx) + (di); SA = (ds) | [bx + di] | |
[bp + si] | EA = (bp) + (si); SA = (ss) | [bp + si] | |
[bp + di] | EA = (bp) + (di); SA = (ss) | [bp + di] | |
[bx + si + idata] | EA = (bx) + (si) + idata; SA = (ds) | 相对基址变址寻址 | 用于表格(结构)中的数组项: [bx].idata[si] |
[bx + di + idata] | EA = (bx) + (di) + idata; SA = (ds) | [bx].idata[si] | |
[bp + si + idata] | EA = (bp) + (si) + idata; SA = (ss) | 用于二维数组: idata[bx][si] | |
[bp + di + idata] | EA = (bp) + (di) + idata; SA = (ss) | idata[bx][si] |
8.5 指令要处理的数据由多长?
在8086CPU的指令,可以处理两种尺寸的数据,byte和word。
- 通过寄存器名制定要处理的数据的尺寸。
mov ax,1
mov al,1
- 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编语言中可以为word或byte。
mov word ptr ds:[0],1 int word ptr [bx]
mov byte ptr ds:[0],1 inc byte ptr [bx]
- 其他方法,push指令只能进行字操作
8.6 寻址方式的综合应用
mov ax,seg
mov ds,ax
mov bx,60h
mov word ptr [bx+0cH],38
add word ptr [bx+0eH],70
mov si,0
mov byte ptr [bx+10h+si],'V'
inc si
mov byte ptr [bx+10h+si],'A'
inc si
mov byte ptr [bx+10h+si],'X'
struct company {
char cn[3];
char hn[9];
int pm;
int sr;
char cp[3];
}
strcut company desc = {"DEC","Ken Olsen",138,40,"PDP"}
main () {
int i;
dec.pm = 38;
dec.sr = dec.sr + 70;
i = 0;
dec.cp[i] = 'V';
i++;
dec.cp[i] = 'A';
i++;
dec.cp[i] = 'X';
return 0;
}
mov ax,seg
mov ds,ax
mov bx,60h
mov word ptr [bx].0cH,38
add word ptr [bx].0eH,70
mov si,0
mov byte ptr [bx].10h[si],'V'
inc si
mov byte ptr [bx].10h[si],'A'
inc si
mov byte ptr [bx].10h[si],'X'
[bx+si+idata].
通过[bx+si+idata]的方式来访问结构体中的数据。
bx用于定位整个结构体,idata定位结构体中的某一个数据项,si用于定位数组项中的每个元素。
汇编语言提供了更为贴切的书写方式:[bx].idata, [bx].idata[si]。
在C语言程序中,我们看到,如dec.cp[i],dec是一个变量名,指明了结构体变量的地址,cp是一个名称,指明了数据项cp的地址, 而i是用来定位cp中的每一个字符。
8.7 div指令
div是除法指令。
- 被除数÷除数=商;商 x 除数=被除数
- 除数:有8位、16位两种,在一个reg或内存单元中
- 被除数:默认放在ax、dx、ax中。
- 如果除数为8位,被除数则为16位,默认在AX中存放
- 如果除数为16位,被输出则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
- 结果:
- 如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;
- 如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
8.8 伪指令dd
db 字节型数据 dw 字型数据 word dd 双字型数据 double word
8.9 dup
dup是一个操作符。
db/dw/dd 重复的次数 dup (重复的数据)
db 3 dup (0)
db 3 dup (0,1,2)
实验7
9. 转移指令的原理
可以修改IP,或同时修改CS和IP的指令统称为转移指令。
转移指令就是可以额控制CPU执行内存中某处代码的指令。
8086CPU:
- 只修改IP时,称为段内转移,jmp ax
- 同时修改CS和IP,称为段间转移 jmp 1000:0
由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移
- 短转移IP, -128-127
- 近转移IP,~32768-32767
转移指令分为以下几类:
- 无条件转移指令,jmp
- 条件转移指令
- 循环指令 loop
- 过程
- 中断
9.1 操作符offset
操作符offset在汇编语言中是由编译器处理的符号,他的功能是取得标号的偏移地址。
9.2 jmp指令
jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。
jmp:
- 转移的目的地址
- 转移的距离
9.3 根据位移进行转移的jmp指令
jmp short 标号
"jmp short 标号"指令所对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移。这个位移,是编译器根据汇编指令中的标号计算出来的。
"jmp short 标号"的功能为:(IP)=(IP)+8位位移
- 8位位移=标号处的地址 - jmp指令后的第一个字节的地址
- short指名此处的位移为8位位移
- 8位位移的范围为-128~127,用补码表示。
- 8位位移由编译程序在编译时算出。
"jmp near ptr"的功能为:(IP)=(IP)+16位位移
- 16位位移=标号处的地址 - jmp指令后的第一个字节的地址
- near ptr指名此处的位移为16位位移
- 16位位移的范围为-32768~32767,用补码表示。
- 16位位移由编译程序在编译时算出。
8位补码?
当然可以。8位补码(Two's Complement)是一种用于表示有符号整数的二进制编码方法,广泛应用于计算机系统中,尤其是在处理负数时。下面将详细解释8位补码的概念、表示范围、转换方法以及一些常见操作。
1. 补码简介
补码是一种将负数表示为正数的特殊编码方式。对于8位补码:
最高位(即第7位,最左边的位)是符号位:
- 0 表示正数
- 1 表示负数
剩余的7位用于表示数值。
2. 表示范围
使用8位补码时,可以表示的整数范围为:
- 最小值:-128(1000 0000)
- 最大值:+127(0111 1111)
这意味着:
- 0 到 127 用标准的二进制表示(最高位为0)。
- -1 到 -128 用补码表示(最高位为1)。
3. 正数的表示
正数的补码与其原码(即标准二进制表示)相同。举例如下:
十进制 二进制(8位) 补码 0 0000 0000 0000 0000 5 0000 0101 0000 0101 127 0111 1111 0111 1111 4. 负数的表示
负数的补码表示方法如下:
- 求原码:首先写出该负数的绝对值的二进制表示。
- 取反:将所有位取反(0变1,1变0)。
- 加1:在取反后的结果上加1,得到补码。
示例:-5 的补码表示
- 原码(5的二进制):
0000 0101
- 取反:
1111 1010
- 加1:
1111 1011
因此,-5 的8位补码表示为 1111 1011。
步骤 结果 原码(5) 0000 0101 取反 1111 1010 加1 1111 1011 补码(-5) 1111 1011 另一个示例:-128 的补码表示
- 原码(128的二进制,注意:128在8位中无法表示为正数,因为最大正数是127,但为了说明补码的范围,假设我们需要表示-128)
- 取反:
0111 1111
→1000 0000
- 加1:
1000 0000
+0000 0001
=1000 0001
然而,-128 的补码在8位中直接表示为 1000 0000,因为在8位补码中,-128 是唯一一个没有对应正数的数。
5. 补码与原码、反码的关系
- 原码:最高位为符号位,其他位表示数值。
- 反码:负数的反码是将原码的所有位取反(不包括符号位)。
- 补码:反码加1。
总结关系
类型 正数 负数 原码 符号位 + 数值位 符号位 + 数值位 反码 与原码相同 取反(不包括符号位) 补码 与原码相同 反码 + 1 6. 补码的优点
使用补码有几个重要的优点,使其在计算机系统中成为标准:
- 唯一表示:每个数只有一种补码表示,避免了原码和反码中正负零的情况。
- 简化运算:加减运算可以统一使用加法器完成,减少了硬件复杂度。
- 符号扩展:在进行位扩展时,补码能正确保持数值的符号。
7. 补码的转换方法
正数转换为补码
- 将数值转换为二进制,确保使用8位表示。
- 如果符号位为0,则补码即为该二进制数。
示例:+25
- 二进制:
0001 1001
- 补码:
0001 1001
负数转换为补码
- 取绝对值的二进制表示(8位)。
- 取反。
- 加1。
示例:-25
- 绝对值25的二进制:
0001 1001
- 取反:
1110 0110
- 加1:
1110 0111
因此,-25 的补码表示为 1110 0111。
补码转换为十进制
检查最高位(符号位):
- 如果为0,按正数处理,直接转换为十进制。
- 如果为1,按负数处理。
对于负数:s
- 取补码,减1,取反,得到原码的绝对值。
- 在结果前加负号。
示例:
1110 0111
- 符号位为1,表示负数。
- 补码
1110 0111
减1:1110 0110
- 取反:
0001 1001
- 十进制:25
- 最终结果:-25
8. 补码的加减运算
加法
补码加法遵循二进制加法规则,不考虑符号位。溢出时,高位的进位被忽略。
示例:+5 (
0000 0101
) + +3 (0000 0011
)0000 0101 + 0000 0011 ------------ 0000 1000 (8)
减法
使用补码进行减法,可以转化为加上被减数的补码。
示例:+5 (
0000 0101
) - +3 (0000 0011
)+5: 0000 0101 -3: 补码 1111 1101 (取3的补码) 0000 0101 + 1111 1101 ------------ 1111 10010 忽略进位,结果为 `0000 1000`(8)
9. 常见误区
0 的表示:
- 在补码中,0 只有一种表示:
0000 0000
。- 原码和反码中可能存在+0和-0,但补码中消除了这一问题。
溢出:
- 当结果超出表示范围时,会发生溢出,但补码运算不改变溢出标志位(需要通过溢出标志位来检测)。
10. 实际应用中的例子
例子1:-10 + 7
表示:
- -10 的补码:
- 10 的二进制:
0000 1010
- 取反:
1111 0101
- 加1:
1111 0110
- 补码:
1111 0110
- +7 的补码:
0000 0111
加法:
1111 0110 (-10) + 0000 0111 (+7) ------------ 1111 1101 (-3)
结果:-3 的补码
1111 1101
例子2:-50 - 30
表示:
- -50 的补码:
- 50 的二进制:
0011 0010
- 取反:
1100 1101
- 加1:
1100 1110
- 补码:
1100 1110
- -30 的补码:
- 30 的二进制:
0001 1110
- 取反:
1110 0001
- 加1:
1110 0010
- 补码:
1110 0010
加法:
1100 1110 (-50) + 1110 0010 (-30) ------------ 1 1011 0000 (忽略进位,结果为 `1011 0000`)
结果:
1011 0000
的补码表示:
- 取反:
0100 1111
- 减1:
0100 1110
(即78)- 加上负号:-78
总结
8位补码是一种高效、统一的有符号整数表示方法,广泛应用于计算机系统中。它通过最高位作为符号位,结合二进制运算简化了加减运算的实现,同时避免了正负零的表示问题。理解补码对于深入学习计算机体系结构、汇编语言编程以及数字电路设计等领域至关重要。
9.4 转移的目的地址在指令中的jmp指令
"jmp far ptr 标号" 实现的是段间转移 - 远转移。功能如下:
- (CS)=标号所在段的地址,(IP)=标号在段中的偏移地址。
- far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
机器码:EA 0B 01 BD 0B - 0BBD:010B
9.5 转移地址在寄存器中的jmp指令
指令格式:jmp 16位reg
9.6 转移地址在内存中的jmp指令
jmp word ptr 段内转移 jmp dword ptr 段间转移
9.7 jcxz指令
指令格式:jcxz 标号 (如果cx=0,转移至标号处执行) 操作:当(cx)=0时,(IP)=(IP)+8位位移
jcxz 标号 if ((cx)==0) jmp short 标号
code segment
start:
mov ax,2000H
mov ds,ax
mov bx,0
s: mov cl,ds:[bx] ;将内存单元的值依次赋值给寄存器CL
mov ch,0 ;将CH填充为0,和CL组合判断CX是否为零
jcxz ok ;如果(CX)等于零则跳转到标号ok处,即结束查找
add bx,1 ;每次向后偏移一个字节
jmp short s
ok: mov dx,bx ;偏移地址存储在寄存器DX中
mov ax,4c00h
int 21h
code ends
9.8 loop指令
9.9 根据位移进行转移的意义
9.10 编译器对转移位移超界的检测
10 CALL和RET指令
call和ret指令都是转移指令,他们都修改IP或者同时修改CS和IP。
他们经常被共同用来实现子程序的设计。
10.1 ret和retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移 retf指令用占中的数据,修改CS和IP的内容,从而实现远转移
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax,4c00H
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start
[start]
|
V
mov ax, stack ; AX = stack segment address
mov ss, ax ; SS = stack segment
mov sp, 16 ; SP = 0x10
mov ax, 0 ; AX = 0
push ax ; Push 0 onto stack, SP = 0x0E
mov bx, 0 ; BX = 0
ret ; Pop 0 from stack, set IP = 0x0000
|
V
CS:0000
mov ax,4c00h ; AX = 0x4C00 (DOS terminate program)
int 21h ; Terminate program, return code = 0
当然可以。让我们详细分析并解释您提供的 8086CPU 汇编代码的执行过程和原理。
代码概述
您提供的汇编代码如下:
assume cs:code stack segment db 16 dup (0) stack ends code segment mov ax,4c00H int 21h start: mov ax,stack mov ss,ax mov sp,16 mov ax,0 push ax mov bx,0 ret code ends end start
这段代码定义了两个段(Segment):
stack
和code
。其中,stack
段用于栈操作,code
段包含程序的主要指令。end start
> 指定程序的入口点为start
标签。详细解析
1. 指令解释
1.1.
assume cs:code
- 作用:告诉汇编器,代码段寄存器 CS 对应的是
code
段。这有助于汇编器正确解析段内的指令和数据。1.2.
stack segment
到stack ends
stack segment db 16 dup (0) stack ends
- 定义:定义一个名为
stack
的段,用于栈操作。db 16 dup (0)
:在stack
段中分配 16 个字节,并将每个字节初始化为 0。这将作为栈空间使用。1.3.
code segment
到code ends
code segment mov ax,4c00H int 21h start: mov ax,stack mov ss,ax mov sp,16 mov ax,0 push ax mov bx,0 ret code ends
- 定义:定义一个名为
code
的段,包含程序的主要指令。1.4.
end start
- 作用:指定程序的入口点为
start
标签。这意味着当程序开始执行时,从start
标签处开始。2. 程序执行流程
让我们逐步跟踪程序的执行:
2.1. 程序入口:
start
标签程序从
start:
标签开始执行。2.2.
mov ax, stack
- 作用:将
stack
段的段地址加载到寄存器 AX 中。- 解释:段地址是指内存中该段的起始位置。
stack
段的地址由汇编器在编译时分配。2.3.
mov ss, ax
- 作用:将 AX 中的值(即
stack
段的段地址)移动到栈段寄存器 SS。- 解释:这一步将栈段寄存器 SS 设置为指向
stack
段,从而指明栈的物理位置。2.4.
mov sp,16
- 作用:将值 16(即 0x10)加载到栈指针寄存器 SP 中。
- 解释:SP 指向栈顶的位置。这里设置 SP 为 16,意味着栈的顶部在
stack
段的偏移地址 0x10(即 >stack:0010h
)。2.5.
mov ax,0
- 作用:将 0 加载到寄存器 AX 中。
- 解释:这是为了后续将 AX 的值压入栈中。
2.6.
push ax
- 作用:将 AX 中的值(0)压入栈中。
- 过程:
- SP 减少 2(因为在 8086 中,栈是向下增长的,每次压栈操作 SP 减少 2)。
- 原 SP = 16 → 新 SP = 14(即 0x0E)。
- 在
stack
段的偏移地址 0x0E 处存储 AX 的值 0x0000。2.7.
mov bx,0
- 作用:将 0 加载到寄存器 BX 中。
- 解释:简单地将 BX 清零。
2.8.
ret
- 作用:从栈中弹出一个返回地址,并将其加载到指令指针 IP 中,跳转到该地址继续执行。
- 过程:
- 从栈顶(
stack:000E
)弹出 2 字节的数据,这里是 0x0000。- SP 增加 2(即从 14 回到 16)。
- 将弹出的值 0x0000 加载到 IP 中。
- 程序跳转到
CS:0000
处继续执行。2.9. 跳转到
CS:0000
在 8086 架构中,物理地址是通过段地址和偏移地址计算得到的:
[ \text{物理地址} = \text{段地址} \times 16 + \text{偏移地址} ]
- CS 指向
code
段。- IP 被设置为 0x0000,因此跳转到
code
段的偏移地址 0x0000。2.10.
mov ax,4c00H
和int 21h
mov ax,4c00H int 21h
mov ax,4c00H
:
- 将 0x4C00 加载到 AX 中。
- 在 DOS 中,INT 21h 的 AH=4Ch 功能是终止程序,AL 是返回代码。
- 因此,AX=4C00h 表示终止程序,返回代码 0x00。
int 21h
:
- 触发 DOS 中断 21h,执行 AH=4Ch 的功能,即终止程序。
3. 总体执行流程
程序开始:
- CS 指向
code
段。- DS, ES 等默认段寄存器指向相应的段,未在此代码中明确设置。
设置栈段和栈指针:
- 将
stack
段的地址加载到 SS。- 设置 SP 为 16(0x10),即栈顶在
stack:0010h
。压栈操作:
- 将 AX=0 压入栈中,栈内容在
stack:000E
处存储 0x0000,SP 变为 0x0E。清零 BX 寄存器:
- 将 BX 设置为 0。
返回操作:
- ret 指令从栈中弹出 0x0000,设置 IP=0x0000,SP 回到 16(0x10)。
- 跳转到
CS:0000
。终止程序:
- 在
CS:0000
处,执行mov ax,4c00h
和int 21h
,终止程序并返回 0 作为退出代码。4. 栈操作的具体效果
栈段设置:
- SS = stack,SP = 0x10。
- 初始栈顶:
stack:0010h
。
push ax
:
- AX = 0 被压入栈中。
- SP 减少 2 → SP = 0x0E。
- stack:000Eh 存储 0x0000。
ret
:
- 从栈中弹出 0x0000,加载到 IP。
- SP 增加 2 → SP = 0x10。
- 程序跳转到
code:0000h
,即执行mov ax,4c00h
和int 21h
。5. 代码的执行原理与目的
这段代码主要展示了如何在 8086 汇编中设置栈段、进行基本的栈操作(如压栈和返回),以及如何通过 DOS 中断终止程序。
设置栈段和栈指针:
- 使用
mov ss, ax
和mov sp, 16
设置栈段和栈指针,确保栈操作有正确的内存空间。栈操作:
push ax
将 AX 的值(0)压入栈中。ret
从栈中弹出一个地址,并跳转到该地址继续执行。程序终止:
- 通过
mov ax,4c00h
和int 21h
终止程序,返回代码 0。6. 总结与关键点
段的定义与使用:
stack
段:用于栈操作,分配了 16 个字节的空间。code
段:包含程序的指令。栈的设置:
- SS 指向
stack
段,SP 设置为 16(0x10),确保栈操作在正确的内存区域进行。栈操作的流程:
- 压栈:
push ax
将 AX 的值压入栈中,SP 减少。- 返回:
ret
从栈中弹出一个地址,设置 IP,实现跳转。程序终止:
- 使用 DOS 中断 21h 的功能 4Ch 终止程序,返回代码 0。
内存布局:
- 栈段
stack
从 0x0000 到 0x0010 分配了 16 个字节。- 程序指令位于
code
段,从 0x0000 开始。执行流程图示:
[start] | V mov ax, stack ; AX = stack segment address mov ss, ax ; SS = stack segment mov sp, 16 ; SP = 0x10 mov ax, 0 ; AX = 0 push ax ; Push 0 onto stack, SP = 0x0E mov bx, 0 ; BX = 0 ret ; Pop 0 from stack, set IP = 0x0000 | V CS:0000 mov ax,4c00h ; AX = 0x4C00 (DOS terminate program) int 21h ; Terminate program, return code = 0
扩展说明:为什么
ret
跳转到CS:0000
在 8086 架构中,
ret
指令的作用是从栈中弹出返回地址,并将其加载到 IP(指令指针)中,从而实现函数调用的返回。在这段代码中:
push ax
将 0 压入栈中。ret
从栈中弹出 0,将其加载到 IP。- IP = 0x0000,所以程序跳转到
code
段的偏移地址 0x0000,执行mov ax,4c00h
和int 21h
,即终止程序。这种方式实际上利用了
ret
指令来控制程序的流程,将执行流跳转到了程序的开头,执行终止命令。这在实际编程中不常见,但在教学或特> 定场景下可能用于演示栈操作和程序流程控制。常见应用场景
- 栈设置:在程序开始时设置栈段和栈指针,以确保栈操作在正确的内存区域进行。
- 函数调用与返回:使用
call
和ret
指令实现函数的调用和返回,利用栈来保存返回地址和局部变量。- 程序终止:通过 DOS 中断
int 21h
的功能终止程序,并返回特定的退出代码。注意事项
- 栈空间:确保栈段有足够的空间来存储需要的值,避免栈溢出。
- 正确设置段寄存器:在进行栈操作前,必须正确设置 SS 和 SP,否则可能导致数据写入错误的内存区域。
- 返回地址的有效性:
ret
指令依赖于栈中的返回地址,确保栈中存储的是有效的地址,避免程序崩溃或不可预测的行为。结论
这段 8086 汇编代码展示了如何设置栈段、进行基本的栈操作(如压栈和返回),以及如何通过 DOS 中断终止程序。通过逐步分析每条> 指令的作用,我们理解了程序的执行流程和内存操作的基本原理。这对于学习 8086 汇编语言和计算机体系结构非常有帮助。
如果您对某些部分还有疑问,或需要进一步的解释,请随时提问!
10.2 call指令
CPU执行call指令时,进行两步操作:
- 将当前的IP或者CS和IP亚茹栈
- 转移
10.3 一句位移进行转移的call指令
call 标号:将当前的IP压栈后,转到标号处执行指令。
push IP jmp near ptr 标号
10.4 转移的目的地址在指令中的call指令
call far ptr 标号:段间转移
PUSH CS PUSH IP jmp far ptr 标号
10.5 转移地址在寄存器中的call指令
call 16位reg
PUSH IP jmp 16 reg
assume cs:code
stack segment
dw 8 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov ds,ax
mov ax,0
call word ptr ds:[0EH]
inc ax
inc ax
inc ax
mov ax,4c00H
int 21H
code ends
end start
assume cs:code
stack segment
dw 8 dup (0) ; 定义栈段,包含 16 个字节(8 个字,每个字为 2 字节)
stack ends
code segment
start: mov ax,stack ; 将栈段地址加载到 AX
mov ss,ax ; 将栈段地址加载到 SS(设置栈段)
mov ds,ax ; 将栈段地址加载到 DS(数据段和栈段相同)
mov ax,0 ; 将 0 加载到 AX
call word ptr ds:[0EH] ; 调用位于 ds:0EH 的子程序地址
inc ax ; AX = AX + 1
inc ax ; AX = AX + 1
inc ax ; AX = AX + 1
mov ax,4c00H ; 将 4c00H 加载到 AX
int 21H ; DOS 终止程序并返回
code ends
end start
10.6 转移地址在内存中的call指令
call word ptr 内存单元地址: PUSH IP jmp word ptr 内存单元地址
call dword ptr 内存单元地址: PUSH CS PUSH IP jmp dword ptr 内存单元地址
10.7 call和ret的配合使用
10.8 mul 指令
mul reg
mul byte ptr ds:[0] (ax)=(al)*((ds)*16+0)
mul word ptr [bx+si+8] (ax)=(ax)*((ds)16+(bx)+(si)+8) 低16位 (dx)=(ax)((ds)*16+(bx)+(si)+8) 高16位
10.9 模块化程序设计
10.10 参数和结果传递的问题
如何存储子程序需要的参数和产生的返回值。
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax
mov [di].2, dx
add si,2
add di,4
loop s
mov ax,4c00H
int 21h
cube: mov ax,bx
mul bx
mul bx
ret
code ends
end start
10.11 批量数据的传递
assume cs:code
data segment
db 'conversation'
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0 ;ds:si 只想字符串(批量数据)所在空间的地址
mov cx,12
call capital
mov ax,4c00H
int 21h
cube: and byte ptr [si], 1101111B
inc si
loop captial
ret
code ends
end start
10.12 寄存器冲突的问题
子程序开始: 子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回
capital: push cx
push si
change: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111B
inc si
jmp short change
ok: pop si
pop cx
ret
11. 标志寄存器
flag寄存器
有一种特殊的寄存器,具有下面三种作用:
- 用来存储相关指令的某些执行结果
- 用来为CPU执行相关指令提供行为依据
- 用来控制CPU的相关工作方式
程序状态字 = PSW。
11.1 ZF标志 0标志位
flag的第六位是ZF,0标志位。
它记录相关指令执行后,其结果是否为0,如果结果为0,那么ZF=1,结果结果不为0,那么ZF=0;
add/sub/mul/div/inc/or/and,运算指令,影响标志位寄存器。
mov/push/pop 传送指令,对标志寄存器无影响。
11.2 PF标志 奇偶标志位
flag的第2位是PF,奇偶标志位。 他记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如果1的个数为偶数,pf=1,如果为奇数,pf=0
11.3 SF标志 正负标志位
flag寄存器的第七位。 记录相关指令执行后,其结果是否为负。如果为负,sf=1;如果非负,sf=0。
11.4 CF标志 进位标志位
CF 进位标志位
CF是对无符号运算有意义的标志位。
mov al, 98H add al,al ;执行后 (al)=30H, CF=1,
mov al.97H sub al,98H ;执行后:(al)=FFH,CF=1 sub al,al ;执行后 (al)=0,CF=0
11.5 OF标志 溢出标志位
溢出标志位。
OF记录了有符号数运算的结果是否发生了溢出。
如果发生溢出,OF=1,如果没有,OF=0
OF是对有符号数运算有意义的标志位。
mov al,F0H add al,88H
add 指令执行后:CF=1,OF=0. 对于无符号运算,F0H+78H有进位,CF=1 对于有符号运算,F0H+78H发生溢出,OF=1
mov al,F0H add al,78H
add 指令执行后:CF=1,OF=0. 对于无符号运算,F0H+78H有进位,CF=1; 对于有符号运算,F0H+78H不产生溢出,OF=0;
11.6 adc 指令
adb是带进位加法指令,它利用了CF位上记录的进位值。
adc ax,bx (ax)=(ax)+(bx)+CF
如果CF的值是被sub指令设置的,那么他的含义就是借位值; 如果CS是被add指令设置的,他们他的含义就是进位值。
加法可以分两步来进行:
- 1 低位相加
- 2 高位相加再加上低位相加产生的进位值。
CPU提供adc指令的目的,就是进行加法的第二步运算的。
adc指令和add指令相配合就可以对更大的数据进行加法运算。
11.7 sbb指令
sbb是带借位减法指令,他利用了CF位上记录的借位值。
sbb ax,bx (ax)=(ax)-(bx)-CF
sbb和adc是基于同样的思想设计的两条指令。
11.8 cmp指令
cmp指令执行后,将对标志寄存器产生影响。
其他相关指令通过识别这些被影响的标志寄存器未来得知比较结果。
cmp ax,bx
如果(ax)=(bx) 则(ax)-(bx)=0 所以:zf=1 如果(ax)!=(bx) 则(ax)-(bx)!=0 所以:zf=0 如果(ax)<(bx) 则(ax)-(bx)将产生错位 所以:cf=1 如果(ax)>=(bx) 则(ax)-(bx)不必错位 所以:cf=0 如果(ax)>(bx) 则(ax)-(bx)不必错位,结果又不为0,所以:cf=0 并且 zf=0 如果(ax)<=(bx) 则(ax)-(bx)即可能借位,结果可能为0,所以:cf=1 或 zf=1
zf=1, (ax)=(bx) zf=0, (ax)!=(bx) cf=1, (ax)<(bx) cf=0, (ax)>=(bx) cf=0 && zf=0, (ax)>(bx) cf=1 || zf=1, (ax)<=(bx)
通add,sub指令一样,CPU在执行cmp指令的时候,也包含两种含义:
- 进行无符号数运算
- 进行有符号数运算
上面是进行无符号数比较时,相关标志位对比较结果的记录。
在考察sf(得知实际的结果的政府)的同时考察of(得知有没有移除),就可以得知逻辑上真正结果的政府,同时就可以知道比较的结果。
com ah,bh为例:
- 如果sf=1,而of=0
- of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负。
- sf=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)< (bh)
- 如果sf=0,而of=0
- of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负。
- sf=1,实际结果为非负,所以逻辑上真正的结果为非负,所以(ah)>= (bh)
- 如果sf=1,而of=1
- of=1,说明有移除,逻辑上真正结果的正负!=实际结果的正负;
- (ah) > (bh)
- 如果sf=0,而of=1
- (ah) < (bh)
11.9 检测比较结果的条件转移指令
转移指的是他能够修改IP,而条件指的是他可以根据某种条件,决定是否修改IP。
cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种。
根据无符号数的比较结果进行转移的条件转移指令(他们检测,zf,cf的值)和根据有符号数的比较结果进行转移的条件指令(检测sf、of、zf的值)
下面是根据无符号数的比较结果进行转移的条件转移指令: je 等于则转移 zf=1 jne 不等于则转移 zf=0 jb 低于则转移 cf=1 jnb 不低于则转移 cf=0 ja 高于则转移 cf=0且zf=0 jna 不高于则转移 cf=1活zf=1