之前我们知道,各种存储器都和 CPU 的地址线、数据线和控制线相连。CPU 在操控它们的时候,把它们都当做内存来对待,把它们当作一个由若干存储单元组成的逻辑存储器,这个逻辑存储器被称为内存地址空间。
在 PC 机系统中,和 CPU 通过总线相连的芯片除了各种存储器外,还有以下 3 种芯片
- 各种接口卡(如网卡、显卡)上的接口芯片,它们控制接口卡进行工作
- 主板上的接口芯片,CPU 通过它们对部分外设进行访问
- 其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理
在这些芯片中,都有一组可以由 CPU 读写的寄存器。这些寄存器,在物理上可能处于不同的芯片中,但是它们在以下两点上是相同的
- 都和 CPU 的总线相连,这种连接是通过它们所在的芯片进行的
- CPU对它们进行读或写的时候都通过控制线向他们所在的芯片发出端口读写命令
从 CPU 的角度来看,这些寄存器都当作端口,对他们进行统一编址,从而建立一个统一的端口地址空间。每一个端口在地址空间中都有一个地址。
CPU 可以直接读写以下 3 个地方的数据
- CPU 内部的寄存器
- 内存单元
- 端口
现在学习一下端口的读写工作
14.1 端口的读写
在访问端口的时候,CPU 通过端口地址来定位端口。因为端口所在的芯片和 CPU 通过总线相连,所以,端口地址和内存地址一样,通过地址总线来传送。在 PC 系统中,CPU 最多可以定位 64KB 个不同的端口,则端口地址的范围为 0~65535.
对端口的读写不能用**mov**
、**push**
、**pop**
等内存读写指令。端口的读写指令只有两条:**in**
和**out**
,分别用于从端口读写数据和往端口写入数据。
我们看一下 CPU 执行内存访问指令和端口访问指令的时候,总线上的信息:
访问内存
mov ax,ds:[8]
执行时与总线相关的操作如下所示:
- CPU 通过地址线将地址信息 8 发出;
- CPU 通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据;
- 存储器将 8 号单元中的数据通过数据线送入 CPU
关于 CPU 对内存的读写,可以参考:第1章 基础知识.md
里面有对计算机内存读写的详细描述
访问端口
in al,60h ; 从 60h 号端口读入一个字节
执行时与总线相关的操作如下
- CPU 通过地址线将地址信息 60h 发出;
- CPU 通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
- 端口所在的芯片将 60h 端口中的数据通过数据送入 CPU
注意:在 **in**
和 **out**
指令中,只能使用 ax 或 al 来存放从端口中读入的数据或要发送到端口中的数据。访问 8 位端口时用 al,访问 16 位端口时用 ax。
对 0~255 以内的端口进行读写时:
in al,20h ; 从 20h 端口读出一个字节
out 20h,al ; 往 20h 端口写入一个字节
对 256~65535 的端口进行读写时,端口号放在 dx 中
mov dx,3f8h ; 将端口号 3f8h 送入dx
in al,dx ; 从 3f8h 端口读出一个字节
out dx,al ; 往 3f8h 端口写入一个字节
14.2 CMOS RAM芯片
下面通过对 CMOS RAM 的读写来体会一下对端口的访问。
PC 机中,有一个 CMOS RAM 芯片,一般简称为 CMOS。这个芯片的为
- 包含一个实时钟和一个有 128 个存储单元的 RAM 存储器(早期的计算机为 64 个字节,即 64 个存储单元)
- 该芯片靠电池🔋供电。所以,关机后其内部的实时钟仍可正常工作,RAM 中的信息不丢失。
- 128 个字节的 RAM 中,内部实时钟占用 0~0d h 单元来保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时 BIOS 程序读取。BIOS 也提供了相关的程序,使得我们在开机的时候配置 CMOS RAM 中的系统信息
- 该芯片内部有两个端口,端口地址为 70h 和 71h。CPU 通过这两个端口来读写 CMOS RAM。
- 70h 为地址端口,存放要访问的 CMOS RAM 端口的地址;71h 为数据端口,存放从选定的 CMOS RAM 单元中读取的数据,或要写入到其中的数据。可见,CPU 对 CMOS RAM 的读写分两步进行,比如,读 CMOS RAM 的 2 号单元;
- 将 2 送入 70h;
- 从端口 71h 读出 2 号单元的内容
检测点 14.1
编程,读取 CMOS RAM 的 2 号单元的内容
mov al,2
out 70h,al ; 向 70h 端口写入数据,选定 2 号单元
in al,71h ; 从 71h 端口读出数据,这个数据来自 2 号单元
编程,向 CMOS RAM 的2 号单元写入0
```assembly mov al,2 out 70h,al ; 向 70h 端口写入数据,选定 2 号单元
mov al,0 out 71h,al ; 向 71h 端口写入数据 0,从而写入 2 号单元
<a name="b3d83741"></a>
# 14.3 shl和shr指令
`shl` 和 `shr` 是逻辑移位指令,这里进行一下讲解。
<a name="b850d4e0"></a>
## shl 逻辑左指令
`shl` 是逻辑左移指令,它的功能为
![](http://gujigujicat.com//Typoraimage-20211121141101841.png#crop=0&crop=0&crop=1&crop=1&id=jGuEx&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
![](https://gw.alipayobjects.com/os/lib/twemoji/11.2.0/2/svg/31-20e3.svg#crop=0&crop=0&crop=1&crop=1&id=R2KVd&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=18) 移动 1 位的结果
指令:
```assembly
mov al,01001000b
shl al,1 ; 将 al 中的数据左移 1 位
移位后的结果为:(al)=10010000b,CF=0
移动多位的结果
如果移动位数大于 1 时,必须将移动位数放在 cl 中。比如,指令:
mov al,01010001b
mov cl,3
shl al,cl
执行后的结果为:(al)=10001000b,因为最后移出的一位是0,所以 CF=0.
可以看出,将 X 逻辑左移移位,相当于执行:X=X*2。比如:
mov al,00000001b
shl al,1 ; 执行后 (al) = 00000010b = 2
shl al,1 ; 执行后 (al) = 00000100b = 4
shl al,1 ; 执行后 (al) = 00001000b = 8
mov cl,3
shl al,cl ; 执行后 (al) = 01000000b = 64
shr 逻辑右移指令
shr
是逻辑右移指令,它和 shl
所进行的操作刚和相反
shr
移动 1 位
指令:
mov al,10000001b
shr al,1 ; 将 al 中的数据右移 1 位
执行后的结果:(al)=01000000b,CF=1
shr
移动多位
mov al,01010001b
mov cl,3
shr al,cl
执行后的结果为:(al)=00001010b,CF=0
可以看出将 X 逻辑右移一位,相当于执行 X=X*2
检测点 14.2
编程,用加法和移位指令计算 (ax)=(ax)*10
提示,(ax)10=(ax)2+(ax)*8
; 左移 1 位
shl ax,1
; 左移 3 位
mov bx,ax
mov cl,3
shl bx,cl
; 相加
add ax,bx
14.4 CMOS RAM中存储的时间信息
之前,我们讲解到 CMOS RAM 中的 0~0dh 存储单元存放着时间信息。
现在,详细地看一下其中 CMOS RAM 存放的信息。在 CMOS RAM中,存放着当前的时间:年、月、日、时、分、秒。这 6 个信息的长度都为 1 个字节,存放单元为:
秒:0号单元
;分:2号单元
;时:4号单元
;日:7号单元
;月:8号单元
;年:9号单元
这些数据以 BCD 码的方式存放。
BCD 码是以 4 位二进制表示十进制数码的编码方法,如下所示
十进制数码 | BCD码 |
---|---|
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0010 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
例如:现在使用 BCD 码表示数值 26
可见,一个字节可表示两个 BCD 码。则 CMOS RAM 存储时间信息的单元中,存储了用两个 BCD 码表示的两位十进制数,高 4 位的 BCD 码表示十位,低 4 位的 BCD 码表示个数。比如,00010100b 表示 14.
编程,在屏幕中间显示当前的月份。
分析,这个程序主要做以下两部分工作。
从 CMOS RAM 的 8 号单元读出当前月份的 BCD 码。
要读取 CMOS RAM 的信息,首先要向地址端口 70h 写入要访问的单元地址
然后从数据端口 71h 中取得指定单元中的数据
将用 BCD 码表示的月份以十进制的形式显示到屏幕上
我们可以看出,BCD 码值=十进制数码值,则BCD 码值 + 30h = 十进制数对应的ASCII码。
从 CMOS RAM 的 8 号单元读出的一个字节中,包含了用两个 BCD 码表示的两位十进制数,高 4 位的 BCD 码表示十位,低 4 位的 BCD 码表示个数。比如,00010100b 表示 14.
现在,我们需要进行以下两步工作。
将从 CMOS RAM 的 8 号单元中读出的一个字节,分为两个表示 BCD 码值的数据。
显示 (ah)+30h 和 (al)+30h 对应的 ASCII 码字符
完整的程序如下
mov al,8
out 70h,al
in al,71h
mov ah,al ; al中为从CMOS RAM的8号单元中读出的数据
mov cl,4
shr ah,cl ; ah中为月份的十位数码值
and al,00001111b ; al中为月份的个位数码值
assume cs:code
code segment
start: mov al,8
out 70h,al
in al,71h
mov ah,al
mov cl,4
shr ah,cl
and al,00001111b
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