I/O体系结构
让信息在CPU、RAM和I/O设备之间流动的数据通路称之为总线,即计算机内的主通信通道。所有计算机都有一条系统总线(一种典型的系统总线是PCI总线),连接内部大部分的硬件设备。计算机内不同的总线可以通过“桥”进行连接。
任何I/O设备有且仅能连接一条总线,CPU和I/O设备之间的总线称为I/O总线。I/O体系结构中包含了三个硬件组织层次:I/O端口、I/O接口、I/O设备控制器:
I/O端口
每个连接到I/O总线上的设备都有自己的I/O地址集,称为I/O端口。I/O端口包含一组寄存器:CPU要把发送给设备的命令写入设备控制寄存器,并从设备状态寄存器中读出设备的状态,通过读取设备的输入寄存器从而获取设备的数据,也可以向设备的输出寄存器中写入数据从而把数据传输给设备。
访问I/O端口
inb(), inw(), inl()
分别从I/O端口读取1、2、4个连续字节。
inb_p(), inw_p(), inl_p()
分别从I/O端口读取1、2、4个连续字节,然后执行一条指令使CPU暂停。
outb(), outw(), outl()
分别向I/O端写入取1、2、4个连续字节。
outb_p(), outw_p(), outl_p()
分别向I/O端写入取1、2、4个连续字节,然后执行一条指令使CPU暂停。
内核中资源(resource)表示某个实体的一部分,被互斥地分配给设备驱动程序。一个资源表示I/O端口地址的一个范围,存放在数据结构resource中。所有的同种资源都插入到一个数形结构中,例如,表示I/O端口地址范围的所有资源都包含在一个根节点为ioport_resource的树中。struct resource结构主要成员如下:
struct resource {
resource_size_t start; /* 资源范围的开始 */
resource_size_t end; /* 资源范围的结束 */
const char name; /* 资源拥有者的描述 */
unsigned long flags; /* 各种标志 */
struct resource * parent; /* 指向资源树中父亲的指针 */
struct resource * sibling; /* 指向资源树中兄弟的指针 */
struct resource * child; /* 指向资源树中第一个孩子的指针 */
}
任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:
request_resource()
把一个给定范围分配给一个I/O设备
allocate_resource()
在资源树中寻找一个给定大小和排列方式的可用范围,若存在,就分配一个I/O设备
release_resource()
释放以前分配的I/O设备的给定范围。
I/O接口
I/O接口是出于I/O端口和对应的设备控制器之间的一种硬件电路,作用是翻译器,即把I/O端口中的值转换为设备所需要的命令和数据。它也检测设备的状态变化,对状态寄存器进行更新。I/O接口分为专用I/O接口和通用I/O接口,专用I/O接口包括磁盘接口、键盘接口、鼠标接口等,通用I/O接口包括串口、并口、USB等。
I/O设备控制器
控制器主要有两个作用:
1. 对从I/O接口接受到的高级命令进行解释,并通过向设备发送电信号序列强制设备执行相应的操作。
2. 对从设备接受到的电信号进行转换和解释,并(通过I/O接口)修改状态寄存器的值。
典型的设备控制器是磁盘控制器,它从CPU接受诸如"写这个数据块"的高级命令,并将其转换为"把磁头定位在正确的磁道上"和"把数据写入这个磁道"之类的低级磁盘操作。比较简单的设备设备控制器。
设备驱动程序模型
现代Linux操作系统要求设备即使类型不同,也应具有相似的功能,对这种设备的驱动程序应当关注以下几点:
1. 电源管理(设备控制电源线上不同的电压级别)
2. 即插即用(配置设备时透明的资源分配)
3. 热插拔(系统运行时支持设备的插入和移走)
系统中所有硬件都由内核全权负责电源管理,例如,在计算机计入待机状态时,内核应立刻强制每个硬件设备处于低功率状态,因此,每个响应待机状态的设备驱动程序必须包含一个回调函数,能够使得设备处于低功率状态。而且,硬件必须按照正确的顺序进入待机状态,否则一些设备可能处于错误的电源状态。例如,必须先使硬盘处于待机状态,然后才是它们的磁盘控制器,否则,磁盘控制器就不能向磁盘发送命令。
为了实现这些操作,Linux提供了一些数据结构和辅助函数,从而构成了设备驱动模型。
sysfs文件系统
sysfs是一种特殊的文件系统,与/proc类似,通常被安装于/sys目录。sysfs文件系统的目标是展现设备驱动程序模型组件间的层次关系,其下面通常有如下目录:
block
块设备,独立于所连接的总线
devices
所有被内核识别的硬件设备,依照连接它们的总线进行组织
bus
用于连接设备的总线
drivers
在内核中注册的设备驱动程序
class
系统中设备的类型,同一类可能包含由不同总线连接的设备,由不同的驱动程序驱动
power
处理一些硬件设备的电源状态的文件
firmware
处理一些硬件设备的固件的文件
kobject
kobejct是设备驱动程序模型的核心数据结构,每个kobject对应sysfs文件系统里的一个目录。kobejct被嵌入到一个叫做"容器"的更大的对象里,容器描述设备驱动程序模型中的组件,容器的典型例子有总线、设备以及驱动程序等等。
将一个kobject嵌入到一个容器里,意味着内核:
1. 为容器保持一个引用计数器
2. 维持容器的层次列表
3. 为容器的属性提供一个用户态查看的视图。
kobject、kset和subsystem
kobject数据结构的主要成员如下:
struct kobject {
char * kname; /* 指向包含容器名称的字符串 */
struct kref kref; /* 容器的引用计数器 */
struct list_head entry; /* 用于kobject所插入的链表的链表项 */
struct kobject * parent; /* 父kobject */
struct kset * kset; /* 所属的kset */
struct kobj_type * ktype; /* kobject的类型描述符 */
}
kref字段指向一个kref类型的对象,kref类型仅包含一个refcount字段,就是kobject的引用计数器。kobject_get()和kobject_put()分别用于增加和减少引用计数器的值,如果该值为0,就会释放kobject所使用的资源,并且执行ktype对象的release方法。
kset可以将kobject组织成一个层次树,kset是同类型的kobject的集合,其主要字段包含:
struct kset {
struct subsystem * subsys /* 指向subsystem的描述符 */
struct kobj_type * ktype /* 指向kobj_type的描述符 */
struct list_head list /* 包含在kset中的kobject的链表首部 */
struct kobject kobj /* 嵌入的kobject */
}
包含在kset中的所有kobject的parent字段指向的就是该kset中的kobj。
subsystem数据结构可以包含不同类型的kset,主要有两个字段:
kset
内嵌的kset结构,用于存放subsystem中的kset
rwsem
读写信号量,保护递归地包含于该subsystem中的kset和kobject。
设备驱动程序模型层次的一个例子:bus子系统包含一个pci子系统,pci子系统又包含驱动程序的一个kset,这个kset包含一个串口kobject,这个kobject又包含一个new-id的属性。