10.2 输入输出接口的编址方式
Posted doctorx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10.2 输入输出接口的编址方式相关的知识,希望对你有一定的参考价值。
计算机组成
10 输入输出设备
10.2 输入输出接口的编址方式
CPU的运算能力很强,但它与外界沟通交流的手段却非常地单一。它总是希望有这样“我给你一个地址,你就给我一个数据”非常直白的沟通方法。所以,它平时也只能和存储器这样胸怀宽广,但是同样头脑简单的家伙在一起玩了。然而现在它需要面对外界那么多的朋友,各个都非常复杂,有时还经常捣些乱,那它恐怕就应付不了了。所以,它就找了I/O接口这样的帮手帮它打理外部的世界,而它呢还是希望能够用一个简单的方式和I/O接口进行交互和沟通。所以,我们就来看一看CPU是怎么和I/O进行沟通的。
这是I/O接口在计算机系统当中的位置。和访问存储器中的单元一样,我们想要CPU访问I/O接口当中的这些寄存器,也是需要通过编写指令来实现。那问题就是,I/O接口里面的这些寄存器的地址究竟是什么?
我们先来看几个基本概念,在系统当中通常会有多个I/O接口,每个I/O接口内部都有若干个寄存器。这些寄存器一般被称为I/O端口,我们不要被这个词的字面意义所迷惑,这个端口指的并不是我们计算机上的USB接口、网线接口这样实实在在的接口,而是一个抽象的概念,它实际上指的就是这些在I/O接口芯片内部的寄存器,它们就像在存储器当中的一个个存储单元一样,CPU要访问它们,就得有特定的地址。因此,每个寄存器,也就是每个I/O端口都需要有自己的地址,这称为端口地址,也叫做端口号。
那在计算机系统中,如何去设定这些端口号,就称为I/O端口的编址方式。
常见的I/O端口编址方式有两种:第一种是I/O端口和存储器分开编址,又被称为I/O映像的方式。x86体系结构就采用了这种方式。另一种常见的方式是I/O端口和存储器统一编址,又称为存储器映像的I/O方式。ARM、MIPS、PowerPC等体系结构都采用了这样的方式。
那我们先来看分开编址的方式。我们假设这个体系结构地址的宽度为3,它一共可以访问的地址单元就是2的三次方,总共8个,如果每个单元是一个字节,那它的存储器最大就是8个字节。然后我们需要在这个计算机系统当中增加一些I/O端口,I/O端口的地址是重新编排的,和存储器地址无关。一般情况下,我们需要的I/O端口的数量都比存储器单元要少得多,比如在这个示例的系统当中,我们需要四个I/O端口,那我们就给它分配四个端口号,0、1、2、3。这样的编址方式就称为分开编址。
在这种编址方式下,要访问I/O端口需要用特殊的指令。
x86提供了IN和OUT这两条指令。IN指令用于把I/O端口的内容输入到CPU当中的寄存器,而OUT指令则是把CPU寄存器当中的内容输出到I/O端口中。
那我们来看一看IN和OUT指令应该如何书写。
如果要访问的端口地址在0到255之间,那可以采用两种方式。一是叫直接寻址,也就是写一个立即数指定端口地址。例如 in al,80H
这条指令,这个80H就是一个端口号;那 out 80H,al
这条指令说的是从80H这个端口读出一个字节的内容,并存放到AL寄存器当中去;而 in ax,80H
这条指令则是说,将AX当中的两个字节的内容,传送到80H所指定的I/O端口中,当然这个I/O端口对应的应该是一个两字节的寄存器。
而如果端口地址大于255,则需要将这个地址先保存到DX寄存器当中,然后再执行IN和OUT的操作。我们也来看一个例子,假如我们要访问第288号端口,这时候就需要先把这个端口号存到DX寄存器当中(mov dx,288
),然后在IN和OUT指令当中,使用DX寄存器来指定端口号。这样就是对288号端口地址进行操作。当然,对于端口地址在0到255之间的,也可以使用间接寻址的方式,那么x86为什么要设定这两种方式呢?主要还是为了指令的长度。我们看到在直接寻址的情况下,我们需要有一个字节的操作码,还需要有一个字节保存这个端口号,那么一个字节所能表达的范围就是0到255,如果端口地址大于255,那原本就需要再增加一个字节来记录端口号,但这样指令就太长了。为了缩短指令长度,宁可多增加一个指令,这也是CISC的特点。
所以,对于间接寻址,有另外一个操作码,这条指令只有一个字节,既没有立即数,也没有寄存器的编号。所以,它是默认地采用DX寄存器来保存端口地址。这样在访问更大的端口地址时,指令的长度反而可以更短一些。这里就有一个问题,既然用这个方式能访问的地址范围更大,指令长度还更短,那我们为什么不只用这一种方式呢?还需要去用直接寻址这样的方式?这个问题就留给你自己思考。
那我们再通过一个例子来看一看这样的指令的操作过程。out 21H,al
这条指令是把AL寄存器当中的一个字节,传送到21H这个端口号。当CPU从存储器当中取回了这条指令,通过译码发现是一条OUT指令,它就会将AL寄存器当中的内容取出来,放到数据总线上,并将21H放到地址总线上。这时候系统总线应该怎么办呢?我们不妨把系统总线看成城市中的一条街道,而把系统总线所连接的存储器I/O接口看成街道两旁的一些建筑,每个建筑里面还有很多个单位,每个单位都有一个门牌号。现在就好像有一个快递员接收了一个任务,要把一个包裹送到21H这个地址。于是他就在这个街道上走,查看着每个大楼的门牌号,然后他发现,这两个存储器地址范围,一个是从0到7FFF; 一个是从80000到5个F。那么在存储器1当中,实际上是包含了21H这个地址的,但他接着再看,这个I/O接口1当中的地址是00到1F;而I/O接口2当中的地址是20到3F;I/O接口3的地址是40到5F。所以,在I/O接口2当中,也包含了21H这个地址。这个包裹应该送到哪儿呢?所以单凭这个地址,系统总线是无法判定要访问哪个设备的。因此,CPU发出的信号中,除了地址,还应该有一个别的信号,这个信号指明了当天要访问的是存储器还是I/O接口。
在x86的CPU当中,这个信号叫做M/IO。当这个信号为0的时候,表明当前在访问I/O接口;而这个信号为1的时候,表明在访问存储器。这样系统总线就知道该怎么办了,它会在所有的I/O接口当中,寻找这个地址(21H)所对应的端口,这就发现是在I/O接口2中。所以,系统总线会把这个传输传到I/O接口2。I/O接口2可能是一个独立的芯片,当它在系统总线上采样到这个地址和一个数据之后,就会在内部找到对应的端口号。我们要注意的一点是,这些I/O接口内部一般只有少数几个端口。所以,它只会采样地址的低几位,然后用这个低位在内部进行索引。在这个例子当中,21H的这个2 是系统总线用来找到这个I/O接口2的,而这个I/O接口2只用接收到地址21H的低位这个1,然后在内部找到对应的端口就可以了。最后这个I/O接口将从数据总线上采样到数据,也就是AL寄存器当中的内容,保存到它内部的数据输出寄存器中,这就完成了这条OUT指令所需的操作。至于这个数据输出寄存器它外面是连接到了几个小灯泡,还是一个数码管,那就是这个I/O接口和外设的连接情况了。
然后我们再来看一看I/O端口和存储器统一编址的情况。
还是假设地址宽度为3,那我们在这个统一编址的体系结构当中,总共就只有8个单元。然后根据需要,其中有一部分用来作为I/O端口的地址,其他部分用来作为存储单元的地址。
那我们之前介绍的模型机就采用了统一编址的方式,它的地址总线宽度为4位,这样一共就有16个单元。而存储器当中用了的地址是0到7(0000~0111),一共8个单元。而输入、输出设备则用了15和16(1110和1111)这两个地址。另外还有一些地址在这个系统当中没有使用,那在之后扩展中,可以增加一些存储器或者增加一些输入、输出端口。但是总共只有这16个,是不可以重复的。
当然,因为它是统一编址的。所以,给出任意一个地址,只有唯一的一个单元与之对应。所以,也就不需要刚才x86当中使用的M/IO这样的信号来指定当前的地址到底是I/O地址还是存储器地址。
那我们来看一看统一编址方式的优缺点。
首先来看优点。因为在统一编址的情况下,是不区分存储器地址和I/O端口地址的。所以,我们就可以直接用访问存储器的指令来访问I/O端口。而访问存储器的指令功能通常比较丰富,比如数据可以有各种的宽度,地址也有多种的产生方式,可以是立即数,可以是寄存器,也可以是寄存器加立即数,甚至还可以放在存储器当中。所以,这样就比较方便对I/O端口进行处理。另外,如果要设计单独的I/O操作的指令,无论它做得怎么简单,也是需要额外的一套硬件逻辑,而采用统一编址的方式,CPU中只需要有一套对外部总线的控制逻辑就可以了,内部结构简单,对外的引脚数目也会少一些。
但是统一编址也有它的缺点,由于I/O端口占用了一部分地址空间,从而使用于存储器的地址空间变小了。这个问题对于早期的处理器影响还是非常大的,我们想一想,x86的早期只有16位宽的地址,按说只能访问64KB的存储器,它为了有更多的存储器空间,还设计了一个非常复杂的段加偏移的方式才能访问一兆的地址空间。如果在这个时候还要为了I/O端口占用了一部分地址空间,那就很难接受了,这也是x86选用了分开编址方式的一个重要原因。而当CPU的字长到了32位之后,在很长一段时间,地址空间都不是一个问题。所以,那个时候直接从32位起步的MIPS就采用了统一编址的方式。当然现在到了64位之后,物理的存储器远远用不了这么大的地址空间。所以,地址空间被挤占这个因素现在已经不成问题了。另外,如果要用访问存储器的指令来进行I/O操作,那这些指令往往比单独设计的I/O指令要长,而且因为这些指令比较复杂,执行的时间可能也会长一些。这也是RISC为什么普遍采用了统一编址方式的原因。因为RISC的指令都是固定长度的,所以即使设计单独的I/O指令,也不会比普通的访存指令更短一些,而x86这样的CISC采用了变长的指令。所以,就可以设计出更短的专门用于I/O的指令,从而提高指令的密度。
那么了解了统一编址的特点,分开编址的特点,我们也就清楚了,他们的优缺点刚好是相对的。在分开编址的情况下,I/O端口不会挤占存储器的地址空间;而且因为设计了单独的I/O指令,它的指令编码可以做得很短,执行速度也比较快;而I/O地址空间一般是远远小于存储器地址空间的,所以独立的I/O指令可以使用较短的地址编码,从而让地址译码变得更为方便。另外,从软件编程的角度来看,有了单独的IN和OUT指令,可以很清晰地看出哪些是I/O操作,哪些是存储器操作,让程序的结构变得清晰易懂。而分开编址的缺点,我们刚才也都已经说完了,就不再重复了。
现在CPU可以用它习惯的方式和I/O接口进行交互,这确实让事情变得简单了很多,但是这并不意味着CPU就可以做甩手掌柜了。其实I/O接口只能帮CPU解决一些沟通交互上的琐碎细节,真正沟通的核心内容还得CPU自己来做。
以上是关于10.2 输入输出接口的编址方式的主要内容,如果未能解决你的问题,请参考以下文章
Arduino的A0~A5模拟端口。可以作为数字端口输入输出使用吗?