III CPU、内存和端口

1. CPU

  1. 从存储器中取一条指令
  2. 分析指令的操作码
  3. 从存储器中读取操作数
  4. 执行指令
  5. 写入结果集
  6. 回到 1.

运算器进行信息处理,寄存器进行信息存储,控制器控制各种器件工作,总线连接各种器件。

16 位和 32 位操作系统的区别

  • 16 位操作系统中的中断调用相当于 32 位操作系统中的 API 调用。16 位操作系统中的段地址和偏移地址在 32 位中消失了,在 32 位操作系统中统一采用平坦的内存地址模式寻址。
  • 16 位操作系统中的程序运行在 RING0 级,也就是说普通程序和操作系统运行在同一个级别并拥有最高权限,而 32 位操作系统中的程序一般只拥有 RING3 级运行权限,程序的所有操作都受到操作系统控制,若程序要获得 RING0 操作特权,只能通过驱动程序实现。
  • 16 位操作系统的可执行文件格式和 32 位操作系统的可执行文件格式不同,在 32 位的 windows 操作系统中,可执行文件的格式叫 PE 格式,32 位的 windows 操作系统运行在 CPU 的保护模式之上,而 16 位的系统则运行在 CPU 的实模式上。

2. 内存

2.1. 地址

8086 汇编中可供使用的内存空间有 1MB。故物理地址的取值范围为 00000h 到 0FFFFFh。由于没有 20 位的寄存器来直接表示物理地址,我们一般采用逻辑地址来间接访问物理地址。逻辑地址由 16 位段地址:16 位偏移地址构成,计算方式为:

物理地址=段地址×10h+偏移地址\text{物理地址} = \text{段地址} \times 10h + \text{偏移地址}

故一个段可以表示的内存有 64KB,且其是首地址的物理地址的 16 进制表示的个位必然是 0。

选用不同的偏移地址,对应的段可能会有重合。一个端的结束地址即起始地址加上 10000h。

2.1.1. 偏移地址和段地址

data segment
a db "ABC"
s db "Hello$World", 0Dh, 0Ah, 0
data ends

这里数组 a 和 s 都在 data 段中,data 段的首字节为 a[0],数组 s 的偏移地址为 offset s = 3,因为 s[0] 和 a[0] 的距离是 3 字节。

  • 使用 offset 变量名或标号名 来引用变量或标号的偏移地址。

  • 使用 seg 变量名或标号名段名 来引用段地址。比如使用 mov ax, data 语句的效果和 mov ax, seg smov ax, seg a 的效果一样。

2.1.2. 直接寻址和间接寻址

直接寻址的两种方式:

  1. 段寄存器:[常数];如:cs:[1000h]ds:[2000h]
  2. 段寄存器:var[常数] 等价于 段寄存器:[var+常数],这里 var 是程序里定义的变量名或函数名;如 ds:s[-2] 等价于 ds:[s-2],编译器在编译期会自动把 var 的偏移地址给加进来,如 offset s=8,则编译后变为 ds:[7]

直接寻址时的缺省段址为 DS,即在不指定段寄存器时,默认为 DS。

间接寻址的两种方式:

  1. 段寄存器:[寄存器(+常数)],仅 BX、BP、SI、DI 四个寄存器可选。
  2. 段寄存器:[寄存器+寄存器(+常数)],其中一个必须从 BX、BP 中选,另一个必须从 SI、DI 中选。

间接寻址时如果寄存器中有 BP,则缺省段址为 SS,否则也为 DS。

笔记

  • 直接寻址和间接寻址的区别在于寻址时通不通过寄存器(只能是四个中的一个)
  • 缺省段址的规则可以简记为出现寄存器 BP 时为 SS,否则为 DS。

在 32 位系统中...

在 32 位系统中,新增了用 寄存器+寄存器*n+常数(这里 n 可以为 2、4、8)的寻址方法,可以方便数组的访问。与 16 位系统不同,32 位系统中对中括号中的寄存器没有要求,EBP、EBX、EDI、EAX、ECX、EDX、ESP 都可以放在中括号中。

2.1.3. 小端规则

当 CPU 把大于 8 位的数据写入内存时,会遵循小端规则:会先写入 8 位再写入 8 位。

2.1.4. 宽度修饰

汇编语言中可以通过这 3 个宽度修饰词限定变量的宽度:

  • byte ptr:8 位(1 字节)
  • word ptr:16 位(2 字节)
  • dword ptr:32 位(4 字节)

在符合这两种情况时,不需要加宽度修饰:

  • 指令中的变量有变量名:可以根据变量的声明确认宽度
  • 指令中的另一个操作数有明确宽度(比如寄存器):说明当前变量的宽度需与另一操作数的确定宽度保持一致

对于 mov ds:[bx], 1 这种指令,其中一个变量没有变量名修饰,也没法根据另一个操作数来确定宽度,这时候就需要用宽度修饰符来修饰。

2.1.5. 地址传送指令

详见 IV 8086指令系统

2.2. 内存空间划分

2.3. 显卡

2.3.1. 文本模式的显卡地址映射

文本模式下显卡共 80×25 个位置,以左上角为原点。显卡将被映射到 B800 这个段,每个屏幕上的字符占用两个内存单元,前一个(如 B800:0000)决定字符,后一个(如 B800:0001)决定前景色和背景色。屏幕上某个字符的偏移地址可以这样计算:

offset=(y×80+x)×2+(存储文本 ? 0:1)\text{offset} = (y \times 80+x)\times 2 + (\text{存储文本}\ ?\ 0:1)

2.3.2. 图形模式的显卡地址映射

图形模式下共支持 320×200 个像素点,以左上角为原点。显卡将被映射到 A000 这个段。一个内存地址表示一个像素点,故屏幕上某像素的偏移地址可以这样计算:

offset=y×320+x\text{offset} = y \times 320 +x
Pasted image 20240109190925

3. 寄存器

3.1. 通用寄存器

通用寄存器包括:

  • AX:Accumulator 累加器(和 muldiv 有关)
  • BX:Base 基地址寄存器(和取地址有关,如 ds:[bx]
  • CX:Count 计数器(和 loop 指令有关)
  • DX:Data 数据寄存器(和 muldiv 有关,也与输入输出的暂存有关)

3.2. 段地址寄存器

段地址寄存器包括:

  • CS:代码段寄存器,CS:IP 指向当前将要执行的指令。
    • 只能通过 jmp far ptrjmp dword ptrcall far ptrretfintiret 等指令间接改变,不能通过 mov 指令直接修改。
  • DS:数据段寄存器。
    • 在开始运行时,DS 内的数据并不是数据段的段地址,而是 PSP 段址,因此需要手动先 mov ax, datamov dx, ax 来将 DS 设为数据段的段址。
  • ES:附加段寄存器。
  • SS:堆栈段寄存器,SS:SP 指向堆栈顶端。

DS、ES、SS 寄存器可以用 mov 指令赋值,但源操作数不能是常数,只能是寄存器或变量。可以对段寄存器赋值的寄存器仅限 AX、BX、CX、DX、SP、BP、SI、DI,变量必须是 word ptr 宽度的。

3.3. 偏移地址寄存器

偏移地址寄存器包括:IP、SP、BP、SI、DI,其中 IP 必须搭配 CS 使用,SP 必须搭配 SS 使用。

特别的,BX 也可以作为偏移地址寄存器使用,故实际能放在 [] 被用于间接寻址的寄存器有:BX、BP、SI、DI。这四个寄存器除了用于间接寻址外,也可以正常参与运算。

3.4. 标志寄存器

FL 是标志寄存器,共 16 个位,其中 6 个是状态标志:CF、ZF、SF、OF、PF、AF,3 个是控制标志:DF、IF、TF,还有 7 个是保留位。

标志寄存器的值不能被直接修改,但是可以通过 pushfpopf 被存入/取出到堆栈中,因此也可以搭配 pushpop 指令和别的寄存器对其进行简介修改。

CF、ZF、SF、OF、PF 寄存器都有与之相关的跳转指令,如:jc 表示 jump if carry,即 CF=1 时跳转;jnc 表示 jump if not carry,即 CF=0 时跳转。他们被称作 JCC 指令,在 IV 8086指令系统 中会进一步介绍。

CF、DF、IF 寄存器有与之相关的设置或清空指令,如:clc 表示 clear carry,即将 CF 置 0;stc 表示 set carry,即将 CF 置 1。

3.4.1. 进位标志 CF(Carry Flag)

CF 会被加减、乘法、移位运算影响,发生下列事件时 CF 会被置 1,否则置 0:

  • 两数相加(最高位)发生进位
  • 两数相减(最高位)发生借位
  • 两数相乘的宽度超过被乘数的宽度
  • 移位时最后被移除的一位为 1

3.4.2. 零标志 ZF(Zero Flag)

ZF 会被算术运算、逻辑运算、移位运算影响,运算结果不等于 0 时 ZF 置 1,否则 ZF 置 0。

3.4.3. 符号标志 SF(Sign Flag)

CF 会被算术运算、逻辑运算、移位运算影响,SF 将被置为运算结果的最高位

3.4.4. 溢出标志 OF(Overflow Flag)

OF 会被 addsubmulimul 指令影响,发生下列事件时 OF 会被置 1,否则置 0:

  • 两个正数相加变负数
  • 两个负数相加变正数
  • 两数相乘的乘积宽度超过被乘数宽度
  • 移位前后最高位发生改变

3.4.5. 奇偶校验标志 PF(Parity Flag)

ZF 会被算术运算、逻辑运算、移位运算影响,运算结果的低八位有偶数个 1 时将 PF 置 1,否则将 PF 置 0。

3.4.6. 辅助进位标志 AF(Auxiliary Flag)

AX 会被 addsub 指令影响,当第 3 位向第 4 位进位/借位时 AF 置 1 否则将 AF 置 0。

3.4.7. 方向标志 DF(Direction Flag)

DF 可以控制 rep movsb 的运行方向(类似于 memcpy 函数)。当 DF=0 时,字符串操作按正方向(低地址到高地址)执行;当 DF=1 时,字符串操作按反方向(高地址到低地址)执行。详见 IV 8086指令系统 中的字符串操作部分。

3.4.8. 中断标志 IF(Interrupt Flag)

IF 用于禁止或允许硬件中断。当 IF=0 时禁止硬件中断;当 IF=1 时允许硬件中断。

这样,被 clisti 包裹起来的代码在执行时就不会被硬件中断,可在需要修改中断向量等场景下。

3.4.9. 陷阱标志(Trap Flag)

TF 用于让 CPU 进入单步模式。当 TF=1 时,每执行一条指令后,CPU 会自动插入一条 int 01h 中断。

4. 端口

端口地址与内存地址独立,仅有 16 位偏移地址,其取值范围是 [0000h:0FFFFh][\text{0000h:0FFFFh}]。CPU 不能直接对 I/O 设备进行控制,需要通过向端口发送控制信号或读取来自端口的信号,指令如下:

  • in 寄存器 端口地址:从端口读入信号并存储到寄存器中。
  • out 端口地址 寄存器:将寄存器的值发送到对应端口。

评论

TABLE OF CONTENTS

1. CPU
2. 内存
2.1. 地址
2.2. 内存空间划分
2.3. 显卡
3. 寄存器
3.1. 通用寄存器
3.2. 段地址寄存器
3.3. 偏移地址寄存器
3.4. 标志寄存器
4. 端口