⚠ 转载请注明出处:作者:ZobinHuang,更新日期:July 17 2021
本作品由 ZobinHuang 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,在进行使用或分享前请查看权限要求。若发现侵权行为,会采取法律手段维护作者正当合法权益,谢谢配合。
目录
有特定需要的内容直接跳转到相关章节查看即可。
Section 1. 端口的概念:介绍了 8086 CPU 中端口的概念
Section 2. 端口的读写过程:分析了端口的读写过程,并且给出了用于端口读写的指令
Section 3. 实例:使用 8086 CPU 读取 CMOS RAM:通过读写主板上的 CMOS RAM 理解了使用 in 和 out 指令用于端口读写的过程
1. 端口的概念

前面的文章中,我们讨论了 CPU 操作寄存器和内存的过程。除了这二者,CPU 还会面临着其它的操作对象,如外置接口卡芯片、主板接口芯片等等。这些芯片与内存 RAM 控制器一样,通过三类总线与 CPU 进行互联。同样地,在这些芯片上也都存在这一些可供 CPU 读写的寄存器。在 8086 CPU 的世界观中,把这些寄存器称为 端口。
2. 端口的读写过程
回顾 8086 CPU 读写内存的过程:
- CPU 通过地址线将读/写地址信息发出
- CPU 通过控制器向内存 RAM 控制器发出读写命令:选中 RAM 控制器,并且通知它要从中读/写数据
- CPU 和 RAM 控制器通过数据线进行数据传输
对于端口的读写,有着类似的过程:
- CPU 通过地址线将读/写地址信息发出
- CPU 通过控制器向端口所在芯片发出读写命令:选中对应的端口芯片,并且通知它要从中读/写数据
- CPU 和端口芯片通过数据线进行数据传输
对于 8086 CPU 来说,它所能访问到的端口是统一编址的。也就是说和内存一样,只要给定一个端口地址,就能唯一确定全局的某一个寄存器。8086 CPU 最多可以寻址 64KB 个不同的端口,也就是 0~65535 个端口。
在汇编程序中,"in [register], [port_address]" 指令用于读取端口内容到指定寄存器,"out [port_address], [register]" 指令用于向指定端口写入指定寄存器中的数据。注意!在 8086 CPU 中,只能基于 AX 和 AL 寄存器来进行 in 和 out 操作,两者分别对应 16 位和 8 位操作。
3. 实例:使用 8086 CPU 读取 CMOS RAM
除了我们在 BIOS 和 DOS 提供的中断例程 中所提到的,主板上有一个 ROM 芯片门用于存储系统上点之后的主板硬件自检和中断注册外 (i.e. BIOS),主板上还有一个名为 CMOS RAM 的芯片。这个芯片中有一个 实时钟 (RTC) 和一个 128-bytes 的 RAM 寄存器。这个芯片在主板掉电以后靠电池供电,因此可以一直工作。在这 128B 的 RAM 中,0~0DH 这 14 个字节用于保存时间信息,剩下的字节用于保存主板的配置信息,以供 BIOS 读取配置。
为了访问这个芯片中存储的 128B RAM 数据,CPU 需要采用端口的方式来访问这个芯片。这个芯片内部有两个端口:70H 和 71H,其中 70H 为地址端口,CPU 向这个寄存器中写入要访问的 128B RAM 的地址;71H 为数据端口,CPU 可以从这个端口中读取选定的 128B RAM 单元的数据。
插曲:shl 和 shr 逻辑移位指令
shl 和 shr 指令可以用于对寄存器中存储的内容分别进行 左移位 和 右移位 的操作。以 shl 为例:"shl ax, 1" 指令将 AX 寄存器中保存的 16 位值进行逻辑左移,并且将被移出的最高位放入 CF (Carry Flag) 标志位中。如果要进行大于 1 位的逻辑移位,则需要将移动位数放入 CL 寄存器中,并使用 "shl al, cl" 的指令进行移位。
在 CMOS RAM 中,存储时间信息的单元为:
存放单元 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
内容 | 秒 | 分 | 时 | 日 | 月 | 年 |
这些时间信息都是采用 BCD 码 的方式存储的。我们通过下面的程序来基于端口从 CMOS RAM 中读取月份信息,并将月份信息显示在屏幕上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31code segment
start: # 设置读取的 RAM 单元地址
mov al, 8
out 70h, al
# 将月份信息读取进来
in al, 71h
mov ah, al
# 在 AH 中保留十位信息
mov cl, 4
shr ah, cl
# 在 AL 中保留个位信息
and al, 00001111b
# 将 BCD 码转化为 ASCII 码
add ah, 30h
add al, 30h
# 显示在屏幕上
mov bx, 0B800h
mov es, bx
mov byte ptr es:[160*12+40*2], ah # 显示月份的十位数字
mov byte ptr es:[160*12+40*2+2], al # 显示月份的个位数字
mov ax, 4C00h
int 21h
code ends
end start
运行的效果如下所示:
