为了确保计算机能够正常工作,必须提供数据通路,让信息在连接到个人计算机的CPU、RAM和I/O设备之间流动。这些数据通路总称为总线,担当计算机内部主通信通道的作用。
所有计算机都拥有一条系统总线,它连接大部分内部硬件设备。一种典型的系统总线是PCI(Peripheral Component Interconnect)总线。目前使用其他类型的总线也很多,如ISA、EISA、MCA、SCSI和USB。
典型的情况是,一台计算机包括几种不同类型的总线,它们通过被称作“桥”的硬件设备连接在一起。两条高速总线用于在内存芯片上来回传送数据:前端总线将CPU连接到RAM控制器上,而后端总线将CPU直接连接到外部硬件的高速缓存上。主机上的桥将系统总线和前端总线连接在一起。
任何I/O设备有且仅能连接一条总线。总线的类型影响I/O设备的内部设计,也影响着内核如何处理设备。我们这篇博文将讨论所有PC体系结构共有的功能性特点,而不具体介绍特定总线类型的技术细节。
CPU和I/O设备之间的数据通路通常称为I/O总线。80x86微处理器使用16位的地址总线对I/O设备进行寻址,使用8位、16位或32位的数据总线传输数据。每个I/O设备依次连接到I/O总线上,这种连接使用了包含3个元素的硬件组织层次:I/O端口、接口和设备控制器。下图显示了I/O体系结构的这些成分:
1 I/O端口
每个连接到I/O总线上的设备都有自己的I/O地址集,通常称为I/O端口(I/O port)。在IBM PC体系结构中,I/O地址空间一共提供了65536个8位的I/O端口。可以把两个连续的8位端口看成一个16位端口,但是这必须从偶数地址开始。同理,也可以把两个连续的16位端口看成一个32位端口,但是这必须是从4的整数倍地址开始。有四条专用的汇编语言指令可以允许CPU对I/O端口进行读写,它们是in、ins、out和outs。在执行其中的一条指令时,CPU使用地址总线选择所请求的I/O端口,使用数据总线在CPU寄存器和端口之间传送数据。
I/O端口还可以被映射到物理地址空间。因此,处理器和I/O设备之间的通信就可以使用对内存直接进行操作的汇编语言指令(例如,mov、and、or等等)。现代的硬件设备更倾向于映射的I/O,因为这样处理的速度较快,并可以和DMA结合起来。
系统设计者的主要目的是对I/O编程提供统一的方法,但又不牺牲性能。为了达到这个目的,每个设备的I/O端口都被组织成如下图所示的一组专用寄存器。CPU把要发送给设备的命令写入设备控制寄存器(device control register),并从设备状态寄存器(device status register)中读出表示设备内部状态的值。CPU还可以通过读取设备输入寄存器(device input register)的内容从设备取得数据,也可以通过向设备输出寄存器(device output register)中写入字节而把数据输出到设备。
为了降低成本,通常把同一I/O端口用于不同目的。例如,某些位描述设备的状态,而其他位指定向设备发出的命令。同理,也可以把同一I/O端口用作输入寄存器或输出寄存器。
in、out、ins和outs汇编语言指令都可以访问I/O端口。内核中包含了以下辅助函数来简化这种访问:
inb(),inw(),inl()
分别从I/O端口读取1、2或4个连续字节。后缀“b”、“w”、“l”,分别代表一个字节(8位)、一个字(16位)以及一个长整型(32位)。
inb_p(),inw_p(),inl_p()
分别从I/O端口读取1、2或4个连续字节,然后执行一条“哑元(dummy,即空指令)”指令使CPU暂停。
outb(),outw(),outl()
分别向一个I/O端口写入1、2或4个连续字节。
outb_p(),outw_p(),outl_p()
分别向一个I/O端口写入1、2或4个连续字节,然后执行一条“哑元”指令使CPU暂停。
insb(),insw(),insl()
分别从I/O端口读取以1、2或4个字节为一组的连续字节序列。字节序列的长度由该函数的参数给出。
outsb(),outsw(),outsl()
分别向I/O端口写入以1、2或4个字节为一组的连续字节序列。
80x86没有使用以上这些辅助函数,因为对于直接使用in、out、ins和outs这些汇编语言是再好不过了,如果你要分析arm、mpis等其他体系结构,可以看看他们。我们主要分析x86体系,所以就不去管他们了。
虽然访问I/O端口非常简单,但是检测哪些I/O端口已经分配给I/O设备可能就不这么简单了。通常,I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据;但是,如果其他硬件设备已经使用了这个端口