I/0体系结构和设备驱动程序
I/O体系结构
为了确保计算机能够正常工作,必须提供数据通路,让信息在连接到个人计算机的CPU、RAM和I/O设备之间流动。
这些数据通路总称为总线,担当计算机内部主通信通道的作用。
所有计算机都拥有一条系统总线,它连接大部分内部硬件设备。
一种典型的系统总线是PCl(Peripheral Component Interconnect)总线。
目前使用其他类型的总线也很多,例如ISA、EISA、MCA、SCSI和USB。
典型的情况是,一台计算机包括几种不同类型的总线,它们通过被称作“桥”的硬件设备连接在一起。
两条高速总线用于在内存芯片上来回传送数据:
前端总线将CPU连接到RAM控制器上,
而后端总线将CPU直接连接到外部硬件的高速缓存上。
主机上的桥将系统总线和前端总线连接在一起。
任何I/O设备有且仅能连接一条总线。
总线的类型影响I/O设备的内部设计,也影响着内核如何处理设备。
本节我们将讨论所有PC体系结构共有的功能性特点,而不具体介绍特定总线类型的技术细节。
CPU和I/O设备之间的数据通路通常称为I/O总线。
80x86微处理器使用I6位的地址总线对I/O设备进行寻址,使用8位、16位或32位的数据总线传输数据。
每个I/O设备依次连接到I/O总线上,这种连接使用了包含3个元素的硬件组织层次:
I/O端口、接口和设备控制器。
图13-1显示了I/O体系结构的这些成分。
I/O端口
每个连接到I/O总线上的设备都有自己的I/O地址集,通常称为I/O端口(1/O port)。
在IBM PC体系结构中,I/O地址空间一共提供了65536个8位的I/O端口。
可以把两个连续的8位端口看成一个16位端口,但是这必须从偶数地址开始。
同理,也可以把两个连续的I6位端口看成一个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端口都被组织成如图13-2所示的一组专用寄存器。
CPU把要发送给设备的命令写入设备控制寄存器(device control register),
并从设备状态寄存器(device status register)中读出表示设备内部状态的值。
CPU还可以通过读取设备输入寄存器(device input register)的内容从设备取得数据,
也可以通过向设备输出寄存器(device output register)中写入字节而把数据输出到设备。
为了降低成本,通常把同一I/O端口用于不同目的。
例如,某些位描述设备的状态,而其他位指定向设备发出的命令。
同理,也可以把同一I/O端口用作输入寄存器或输出寄存器。
访问I/O端口
in、out、ins和outs汇编语言指令都可以访问I/O端口。内核中包含了以下辅助函数来简化这种访问:
inb(),inw(),inl()
分别从I/O端口读取1、2或4个连续字节。后缀“b”、“w”、“I”分别代表一个字节(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个字节为一组的连续字节序列。
虽然访问I/O端口非常简单,但是检测哪些I/O端口已经分配给I/O设备可能就不这么简单了,对基于ISA总线的系统来说更是如此。
通常,I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据;
但是,如果其他硬件设备已经使用了这个端口,那么系统就会崩溃。
为了防止这种情况的发生,内核必须使用“资源”来记录分配给每个硬件设备的I/O端口。
资源(resource)表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。
在我们的情况中,一个资源表示I/O端口地址的一个范围。每个资源对应的信息存放在resource数据结构中,其字段如表13-1所示。
所有的同种资源都插入到一个树型数据结构中;
例如,表示I/O端口地址范围的所有资源都包含在一个根节点为ioport_resource 的树中。
节点的孩子被收集在一个链表中,其第一个元素由child指向。sibling字段指向链表中的下一个节点。
为什么使用树?
例如,考虑一下IDE硬盘接口所使用的I/O端口地址——比如说从0xf000到0xf00f。
然后,start字段为0xf000且end字段为0xf00f的这样一个资源包含在树中,控制器的常规名字存放在name字段中。
但是,IDE设备驱动程序需要记住另外的信息,
也就是IDE链(IDE chain)的主盘(master disk)使用0xf000~0xf007 的子范围,
从盘(slave disk)使用0xf008~0xf00f的子范围。
为了做到这点,设备驱动程序把两个子范围对应的孩子插入到0xf000~0xf00f的整个范围对应的资源下。
一般来说,树中的每个节点肯定相当于父节点对应范围的一个子范围。
I/O端口资源树(ioport_resource)的根节点跨越了整个I/O地址空间(从端口0~65535)。
任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:
request_resource()
把一个给定范围分配给一个I/O设备。
allocate_resource()
在资源树中寻找一个给定大小和排列方式的可用范围;
若存在,就将这个范围分配给一个I/O设备
(主要由PCI设备驱动程序使用,这种驱动程序可以配置成使用任意的端口号和主板上的内存地址对其进行配置)。
release_resource()
释放以前分配给I/O设备的给定范围。
内核也为以上应用于I/O端口的函数定义了一些快捷函数:
request_region()分配I/O 端口的给定范围,
release_region()释放以前分配给I/O端口的范围。
当前分配给I/O 设备的所有I/O地址的树都可以从/proc/ioports文件中获得。
I/O接口
I/O接口(1/0 interface)是处于一组I/O端口和对应的设备控制器之间的一种硬件电路。
它起翻译器的作用,即把I/O端口中的值转换成设备所需要的命令和数据。
在相反的方向上,它检测设备状态的变化,并对起状态寄存器作用的I/O端口进行相应的更新。
还可以通过一条IRQ线把这种电路连接到可编程中断控制器上,以使它代表相应的设备发出中断请求。
有两种类型的接口:
专用1/0接口
专门用于一个特定的硬件设备。在一些情况下,设备控制器与这种I/O接口处于同一块卡中(注1)。
连接到专用I/O接口上的设备可以是内部设备(位于PC机箱内部的设备),也可以是外部设备(位于PC机箱外部的设备)。
通用I/O接口
用来连接多个不同的硬件设备。连接到通用I/O接口上的设备通常都是外部设备。
每块卡都要插入PC的一个可用空闲总线插槽中。
如果一块卡通过一条外部电缆连接到一个外部设备上,那么在PC后面的面板中就有一个对应的连接器。
专用IO接口
专用I/O接口的种类很多,因此目前已装在PC上设备的种类也很多,我们无法一一列出,在此只列出一些最通用的接口:
键盘接口
连接到一个键盘控制器上,这个控制器包含一个专用微处理器。
这个微处理器对按下的组合键进行译码,产生一个中断并把相应的键盘扫描码写入输入寄存器。
图形接口
和图形卡中对应的控制器封装在一起,图形卡有自己的帧缓冲区,还有一个专用处理器以及存放在只读存储器(ROM)芯片中的一些代码。
帧缓冲区是显卡上固化的存储器,其中存放的是当前屏幕内容的图形描述。
磁盘接口
由一条电缆连接到磁盘控制器,通常磁盘控制器与磁盘放在一起。
例如,IDE接口由一条40线的带形电缆连接到智能磁盘控制器上,在磁盘本身就可以找到这个控制器。
总线鼠标接口
由一条电缆把接口和控制器连接在一起,控制器就包含在鼠标中。
网络接口
与网卡中的相应控制器封装在一起,用以接收或发送网络报文。虽然广泛采用的网络标准很多,但还是以太网(IEEE 802.3)最为通用。
通用IO接口
现代PC都包含连接很多外部设备的几个通用I/O接口。最常用的接口有:
并口
传统上用于连接打印机,它还可以用来连接可移动磁盘、扫描仪、备份设备、其他计算机等等。数据的传送以每次1字节(8位)为单位进行。
串口
与并口类似,但数据的传送是逐位进行的。
串口包括一个通用异步收发器(UART)芯片,它可以把要发送的字节信息拆分成位序列,也可以把接收到的位流重新组装成字节信息。
由于串口本质上速度低于并口,因此主要用于连接那些不需要高速操作的外部设备,如调制解调器、鼠标以及打印机。
CMCIA接口
大多数便携式计算机都包含这种接口。
在不重新启动系统的情况下,这种形状类似于信用卡的外部设备可以被插入插槽或从插槽中拔走。
最常用的PCMCIA设备是硬盘、调制解调器、网卡和扩展RAM。
SCSI(小型计算机系统接口)接口
是把PC主总线连接到次总线(称为SCSI总线)的电路。
SCSI-2总线允许一共8 个PC和外部设备(硬盘、扫描仪、CR-ROM刻录机等等)连接在一起。
如果有附加接口,宽带SCSI-2和新的SCSI-3接口可以允许你连接多达16个以上的设备。
SCSI标准是通过SCSI总线连接设备的通信协议。
通用串行总线(USB)
高速运转的通用I/O接口,可用于连接外部设备,代替传统的并口、串口以及SCSI接口。
设备控制器
复杂的设备可能需要一个设备控制器(device controller)来驱动。从本质上说,控制器起两个重要作用:
1.对从I/O接口接收到的高级命令进行解释,并通过向设备发送适当的电信号序列强制设备执行特定的操作。
2.对从设备接收到的电信号进行转换和适当地解释,并修改(通过I/O接口)状态寄存器的值。
典型的设备控制器是磁盘控制器,它从微处理器(通过I/O接口)接收诸如“写这个数据块”之类的高级命令,
并将其转换成诸如“把磁头定位在正确的磁道上”和“把数据写入这个磁道”之类的低级磁盘操作。
现代的磁盘控制器相当复杂,因为它们可以把磁盘数据快速保存到内存的高速缓存中,
还可以根据实际磁盘的几何结构重新安排CPU的高级请求,使其最优化。
比较简单的设备没有设备控制器,
可编程中断控制器(参见第四章中的“中断和异常”一节)和可编程间隔定时器(参见第六章中的“可编程间隔定时器(PIT)一节)”就是这样的设备。
很多硬件设备都有自己的存储器,通常称之为I/O共享存储器。
例如,所有比较新的图形卡在帧缓冲区中都有几MB的RAM,用它来存放要在屏幕上显示的屏幕映像。
我们将在本章的“访问I/O共享存储器”一节中讨论I/O共享存储器。
设备驱动程序模型
Linux内核的早期版本为设备驱动程序的开发者提供微不足道的基本功能:
分配动态内存,
保留I/O地址范围或中断请求(IRQ),
激活一个中断服务例程来响应设备的中断。
事实上,在更老的硬件设备上编程棘手而困难重重,
还有即使两种不同的硬件设备连在同一条总线上,但二者也很少有共同点。
因此,试图为这种硬件设备的驱动程序开发者提供一种统一的模型是难以做到的。
现在的情形大不一样。
诸如PCI这样的总线类型对硬件设备的内部设计提出了强烈的要求;
因此,新的硬件设备即使类型不同但也有相似的功能。对这种设备的驱动程序应当特别关注:
1.电源管理(控制设备电源线上不同的电压级别)
2.即插即用(配置设备时透明的资源分配)
3.热插拔(系统运行时支持设备的插入和移走)
系统中所有硬件设备由内核全权负责电源管理。
例如,
在以电池供电的计算机进入“待机”状态时,内核应立刻强制每个硬件设备(硬盘、显卡、声卡、网卡、总线控制器等等)处于低功率状态。
因此,
每个能够响应“待机”状态的设备驱动程序必须包含一个回调函数,它能够使得硬件设备处于低功率状态。
而且,硬件设备必须按准确的顺序进人“待机”状态,
否则一-些设备可能会处于错误的电源状态。
例如,
内核必须首先将硬盘置于“待机”状态,
然后才是它们的磁盘控制器,
因为若按照相反的顺序执行,磁盘控制器就不能向硬盘发送命令。
为了实现这些操作,Linux 2.6提供了一些数据结构和辅助函数,它们为系统中所有的总线、设备以及设备驱动程序提供了一个统一的视图;
这个框架被称为设备驱动程序模型。
sysfs文件系统
sysfs文件系统是一种特殊的文件系统,被安装于/sys目录下的/proc文件系统相似。
/proc 文件系统是首次被设计成允许用户态应用程序访问内核内部数据结构的一种文件系统。
/sysfs文件系统本质上与/proc有相同的目的,但是它还提供关于内核数据结构的附加信息;
此外,/sysfs的组织结构比/proc更有条理。或许,在不远的将来,/proc和/sysfs将会继续共存。
sysfs文件系统的目标是要展现设备驱动程序模型组件间的层次关系。该文件系统的相应高层目录是:
block
块设备,它们独立于所连接的总线。
devices
所有被内核识别的硬件设备,依照连接它们的总线对其进行组织。
bus
系统中用于连接设备的总线。
drivers
在内核中注册的设备驱动程序。
class
系统中设备的类型(声卡、网卡、显卡等等);同一类可能包含由不同总线连接的设备,于是由不同的驱动程序驱动。
power
处理一些硬件设备电源状态的文件。
firmware
处理一些硬件设备的固件的文件。
sysfs文件系统中所表示的设备驱动程序模型组件之间的关系就像目录和文件之间符号链接的关系一样。
例如,
文件/sys/block/sda/device可以是一个符号链接,
指向在/sys/devices/pci0000:00(表示连接到PCI总线的SCSI控制器)中嵌入的一个子目录。
此外,
文件/sys/block/sda/device/block是到目录/sys/block/sda的一个符号链接,这表明这个PCI设备是SCSI磁盘的控制器。
sysfs文件系统中普通文件的主要作用是表示驱动程序和设备的属性。
例如,
位于目录/sys/block/hda下的dev文件含有第一个IDE链主磁盘的主设备号和次设备号。
kobject
设备驱动程序模型的核心数据结构是一个普通的数据结构,叫做kobject,
它与sysfs文件系统自然地绑定在一起:每个kobject对应于sysfs文件系统中的一个目录。