CPU、内存和端口
约 3277 个字 145 行代码 预计阅读时间 18 分钟
数据的表示方式¶
汇编语言使用关键词 db (Define Byte) 来定义字节类型的变量,展开来讲即:
| 关键词 | 位数 | 符号 | 变量 |
|---|---|---|---|
| db | 8 | byte | char |
| dw | 16 | word | short int |
| dd | 32 | double word | long int 或 float |
| dq | 64 | quadruple word | __int64 , long long (十进制 %lld , 十六进制 %llx) 或 double |
| dt | 80 | ten bytes | long double (%Lf) |
1 word 的位数表示一个系统单步能处理的最大位数
那么变量定义的格式如下:
当我们需要多个相同的初始值时,可以使用 DUP 运算符:
内存¶
内存以字节为单位分配地址,DOS系统运行在 CPU 的实模式下,可访问的地址范围为 [00000h,0FFFFFh] ,即最多只能访问 1MB 内存空间
我们将形如 0FFFFFh 的单个数值表示的地址称作物理地址。由于 8086 每个寄存器都均为 16 位宽度,没有一个寄存器能容纳一个 20 位的物理地址,为此 Intel 公司设计 8086 时定义了 4 个段地址寄存器和 4 个偏移地址寄存器,以便用逻辑地址形式即 Seg:Offset 组合来间接访问物理地址。
段地址和偏移地址两个概念都和段(segment)有关:
- 偏移地址指段内某个变量或标号与段首之间的距离
- 段地址是指 20 位段首地址的高 16 位
段 Segment
段是符合以下两个条件的一块内存空间:
- 内存块的长度位 10000h 字节,即 64KB
- 内存块的 20 位首地址的低 4 位必须等于 0
例如,对于段 [12340h,2233Fh] ,其段地址为 1234h
逻辑地址¶
在 8086 中,逻辑地址由 16bit Seg:16bit Offset 构成,可以根据如下公式将其转换成物理地址:
在汇编语言程序中,如果要访问逻辑地址 1234h:5678h (物理地址 179B8h) 指向的字节并赋值给寄存器 AL,可以这样写:
- 用
offset 变量名或标号名引用变量或标号的偏移地址 - 用
seg 变量名或标号名或段名引用段地址
在引用变量或数组元素时,若用常数表示它们的偏移地址,称这种寻址方式为直接寻址,如 ds:[5678h] ;若它们的偏移地址含有寄存器,称这种寻址方式为间接寻址,如 ds:[bx+2]
在源程序中,设某个变量或数组名字为 var ,则直接寻址的一般形式为:段寄存器:var[常数] 或 段寄存器:[var+常数] 。经过编译后变量名或数组名会替换成它们的偏移地址。例如,假设 offset s=8 ,那么 ds:[s+1] , ds:s[-2] 分别编译成 ds:[9] 和 ds:[6]
8086 规定的间接寻址一般形式为: 段寄存器:[寄存器1+寄存器2+常数] ,其中两个寄存器可以同时存在也可以缺少一个。仅有 BX,BP,SI,DI 这四个寄存器允许出现在方括号中,且 BX 和 BP 不能同时出现;SI 和 DI 不能同时出现。
缺省段址
若逻辑地址中省略段地址,则使用默认缺省值:
- 直接寻址方式缺省段址为 DS
- 间接寻址依据偏移地址是否含有寄存器
BP区分- 若有,则缺省段址为 SS
- 若无,则缺省段址为 DS
小端规则¶
Intel 用的是小端存储法(little-endian):
内存空间划分和显卡地址映射¶
16 位 CPU 只能访问 0000:0000 至 F000:FFFF 之间的 1M 内存空间,其划分如下:
| 地址范围 | 用途 |
|---|---|
[0000:0000,9000:FFFF] | 操作系统和用户程序 |
[A000:0000,A000:FFFF] | 映射显卡内存 |
[B000:0000,B000:7FFF] | 映射显卡内存 |
[B800:0000,B800:7FFF] | 映射显卡内存 |
[C000:0000,F000:FFFF] | 映射 ROM |
所以操作系统和用户程序总共可用的内存空间为 [0,9FFFF] 共 640KB
寄存器¶
8086 一共有 14 个 16 位寄存器:
- 通用寄存器:包括
AX,BX,CX,DX,用于算术、逻辑、移位运算 - 段地址寄存器:包括
CS,DS,ES,SS,用来表示段地址 - 偏移地址寄存器:包括
IP,SP,BP,SI,DI,用来表示偏移地址 - 标志寄存器:包括
FL,用来存储标志位
80386中,除了段寄存器外所有寄存器均扩充到 32 位宽度,前面加上 E
通用寄存器¶
通用寄存器包括 AX,BX,CX,DX ,作用是做算术、逻辑、移位运算。其中 AX 的高八位和低八位成为 AH,AL ,其它寄存器同理。
在 80386 中,32 位寄存器 EAX 低 16 位就是 AX ,而高 16 位没有名字。
其实命名不是按照字母表前四个字母来的,不过我们写程序的时候随意用也没关系
AX: Accumulator 累加器BX: Base 基地址寄存器CX: Counter 计数器DX: Data 数据寄存器
段地址寄存器¶
段地址寄存器包括 CS,DS,ES,SS
CS:代码段寄存器DS:数据段寄存器ES:附加段寄存器SS:堆栈段寄存器
其中 CS 不能用 mov 指令赋值;DS,ES,SS 可以用 mov 赋值,但源操作数不能是常数,且如果是寄存器的话仅限 AX,BX,CX,DX,SP,BP,SI,DI (除 IP 外),位宽必须是 word ptr 宽度。
DOS 把某个 EXE 载入到内存后,在将控制权交给程序之前,会对以下寄存器赋初值:
CS= 代码段的段地址IP= 首条指令的偏移地址SS= 堆栈段的段地址SP= 堆栈段的长度DS= PSP 段址ES= PSP 段址
CS:IP 即指向当前将要执行的指令;SS:SP 指向堆栈顶端;PSP 是程序段前缀(program segment prefix),它是一个由 DOS 分配给当前 EXE 的,位于首段之前的,长度为 100h 字节的内存块,PSP 中存储了与当前 EXE 进程相关的一些信息。
由于 DS 的初始值并非数据段的段地址,因此在程序刚开始运行时不能用 DS 的当前值作为段地址去引用或访问数据段内任何一个变量,而是应该先把 DS 赋值为 seg data
偏移地址寄存器¶
偏移地址寄存器包括 IP,SP,BP,SI,DI
IP需要跟CS搭配构成CS:IP指向当前将要执行的指令,所以该寄存器的名字不能在任何指令中引用SP需要跟SS搭配构成SS:SP指向堆栈顶端,该寄存器不能置于 [] 内用于间接寻址- 剩下的三个寄存器可以用来间接寻址,但总共有四个段寄存器,所以 Intel 安排通用寄存器
BX也可以用来间接寻址BX,BP,SI,DI四个寄存器除了用来间接寻址外,都可以参与算术、逻辑、移位运算- 寄存器
BP默认跟栈搭配,因此指令mov si, [bp+6]等效于mov si, word ptr ss:[bp+6]
寄存器相对寻址时,方括号内必须是BX,BP,SI,DI,不然会报错
标志寄存器¶
FL 是标志寄存器,它里面有些位用来反应当前指令的执行状态,称为状态标志;有些位用于控制 CPU,称为控制标志;剩余 7 位是保留位,除了第一位恒为 1 外,其它保留位恒为 0。
- 状态标志共 6 个,包括:
CF.ZF,SF,OF,PF,AF - 控制标志共 3 个,包括:
DF,IF,TF
状态标志¶
进位标志 CF
add,sub,mul,imul以及移位指令都会影响 CF(Carry Flag)- 两数相加产生进位时 CF 置 1
- 两数相减产生借位时 CF 置 1
- 两数相乘乘积超过被乘数宽度时 CF 置 1
- 移位指令最后移出的那一位保存在 CF 中
如,可以利用带进位加法指令 adc 以及逻辑左移指令 shl 实现进制转换:
零标志 ZF
- 算术运算指令、逻辑运算指令、移位运算指令均会影响 ZF(Zero Flag)
- 当运算结果为 0 时 ZF 置 1
- 当运算结果非 0 时 ZF 置 0
- 注意:
jz(jump if zero) 和je(jump if equal) 跳转依据均为ZF==1,故二者等价;同理,jnz和jne也等价
符号标志 SF
- 算术运算指令、逻辑运算指令、移位运算指令均会影响 SF(Sign Flag)
- 当运算结果为正时 SF 置 0
- 当运算结果为负时 SF 置 1
- 注意: 相当于运算结果的最高位;符号标志相关的跳转指令可以用来判定正负
js(jump if sign) 依据为SF==1jns(jump if no sign) 依据为SF==0
溢出标志 OF
add,sub,mul,imul、移位运算指令均会影响 OF(Overflow Flag)- 当两正数相加变负数时 OF 置 1
- 当两负数相加变正数时 OF 置 0
- 当两数相乘乘积宽度超过被乘数宽度时 OF 置 1(此时 CF 也置 1)
- 当仅移动一位,且移位前最高位不等于移位后最高位时 OF 置 1
奇偶校验位 PF
- 当运算结果低 8 位中二进制 1 的个数为偶数时 PF 置 1;否则置 0
辅助进位标志 AF
- 若执行加法指令时第 3 位向第 4 位产生进位则 AF 置 1
- 若执行减法指令时第 3 位向第 4 位产生借位则 AF 置 1
- 注意: AF(Auxiliary Flag) 并没有相关跳转指令,它跟 BCD 码调整指令有关,如
AAA,AAS,DAA,DAS
遇到这种题好好算算二进制减法吧
若以下指令序列的操作数均为符号数,请写出每个指令序列执行后标志位CF、ZF、SF、OF的值,并指出CMP指令的前后操作数的大小关系。
(1) mov al, 99h
cmp al, 34h ; CF=0, ZF=0, SF=0, OF=1, 99h < 34h
(2) mov ah, 81h
cmp ah, 0FFh ; CF=1, ZF=0, SF=1, OF=0, 81h < 0FFh
(3) mov bx, 1234h
cmp bx, 8086h ; CF=1, ZF=0, SF=1, OF=1, 1234h > 8086h
(4) mov cx, 0FFFFh
cmp cx, 0FFFEh ; CF=0, ZF=0, SF=0, OF=0, 0FFFFh > 0FFFEh
(5) mov dx, 3F7Dh
cmp dx, 1000h ; CF=0, ZF=0, SF=0, OF=0, 3F7Dh > 1000h
控制标志¶
方向标志 DF
- DF(Direction Flag)用来控制字符串操作指令如
rep,movsb的运行方向- 当
DF=0时,字符串操作指令按正方向运行(先操作低地址再操作高地址) - 当
DF=1时,字符串操作指令按反方向运行(先操作高地址再操作低地址)
- 当
- 注意:
cld(clear direction) 指令设置DF=0;std(set direction) 指令设置DF=1 - 通常,若源首地址小于目标首地址,则置
DF为 1;相应的,若源首地址大于目标首地址,则置DF为 0
例如,我们希望将位于 1000 处的字符串 ABCDE 复制到地址 1002 处,如果我们设置 DF=0 的话:
中断标志 IF
- IF(Interrupt Flag)用于禁止、允许硬件中断
- 当
IF=0时,禁止硬件中断 - 当
IF=1时,允许硬件中断
- 当
- 注意:
cli(clear interrupt) 指令设置IF=0;sti(set interrupt) 指令设置IF=1 - 我们通常使用的
int 21h属于软件中断,是显式(Explicit)的 - 硬件中断是指由硬件某个事件触发,并由CPU自动插入并调用一个隐式(implicit)的
int n指令来调用中断服务子函数
int n 函数的指针保存在 0:n*4 处,例如 int 1h 函数的首地址为 1234h:5678h :
我们可以手动将其地址修改,将 int 中断的指针指向我们自己定义的函数,实现侵入。
陷阱标志 TF
- TF(Trap Flag)位于
FL寄存器第八位,用于设置 CPU 的运行模式- 当
TF=1时,CPU 进行单步模式 - 当
TF=0时,CPU 进行常规模式
- 当
- 注意: 当 CPU 进入单步模式后,每执行一条指令后都会跟随执行一条
int 01h中断指令
TF 并不能通过单独指令修改,但可以通过如下操作置 1:
端口¶
端口地址独立于内存地址,并且不像内存那样既有段地址又有偏移地址,端口地址仅有 16 位偏移地址,范围是 [0000h,0FFFFh] 。
读写端口地址的指令是 in,out ,例如 in al, 61h 表示从 61h 号端口读取一个字节的信号并保存到 AL 中。
以下程序利用端口实现输出当前时间信息:
对于从键盘读取输入:
<1> 高 dos中断调用
相当于 al=getchar() 但不能读取方向键
<2> 中 bios中断调用(basic input/output system)
AX = 键盘编码,可以读取方向键、功能键、PgUp等键,但不能读取单独的ctrl键
<3> 低 端口操作
60h 是键盘输入的端口,可以读取各个键的编码
文本模式的显卡地址映射¶
可以得到类似如下图所示的效果:
