0 linux设备模型出现的背景
随着计算机的周边外设越来越丰富,设备管理已经成为现代操作系统的一项重要任务,这对于
Linux来说也是同样的情况。每次Linux内核新版本的发布,都会伴随着一批设备驱动进入内核。在Linux内核里,驱动程序的代码量占有了相当大的比重。
为了降低设备多样性带来的
Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作Driver Model)的概念。设备模型将硬件设备归纳、分类,然后抽象出一套标准的数据结构和接口。驱动的开发,就简化为对内核所规定的数据结构的填充和实现。
本文将会从设备模型的基本概念开始,通过分析内核相应的代码,一步一步解析
Linux设备模型的实现及使用方法。
1 linux设备模型的基本概念
1.1 Bus, Class, Device和Device Driver的概念
下图是嵌入式系统常见的硬件拓扑的一个示例:
硬件拓扑描述
Linux设备模型中四个重要概念中三个:Bus,Class和Device(第四个为Device Driver,后面会说)。
Bus
(总线):Linux认为(可以参考include/linux/device.h中struct bus_type的注释),总线是CPU和一个或多个设备之间信息交互的通道。而为了方便设备模型的抽象,所有的设备都应连接到总线上(无论是
CPU内部总线、虚拟的总线还是“platform Bus”)。
Class
(分类):在Linux设备模型中,Class的概念非常类似面向对象程序设计中的Class(类),它主要是集合具有相似功能或属性的设备, 这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同Class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从 Class中继承即可。
Device
(设备):抽象系统中所有的硬件设备,描述它的名字、属性、从属的Bus、从属的Class等信息。
Device Driver
(驱动):Linux设备模型用Driver抽象硬件设备的驱动程序,它包含设备初始化、电源管理相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数)。
注:什么是Platform Bus?
在计算机中有这样一类设备,它们通过各自的设备控制器,直接和CPU连接,CPU可以通过常规的寻址操作访问它们(或者说访问它们的控制器)。这种连接方式,并不属于传统意义上的总线连接。但设备模型应该具备普适性,因此Linux就虚构了一条Platform Bus,供这些设备挂靠。
1.2 设备模型的核心思想
Linux
设备模型的核心思想是(通过xxx手段,实现xxx目的):
1.
用Device(struct device)和Device Driver(struct device_driver)两个数据结构,分别从“有什么用”和“怎么用”两个角度描述硬件设备。这样就统一了编写设备驱动的格式,使驱动开发从论述题 变为填空体,从而简化了设备驱动的开发。
2.
同样使用Device和Device Driver两个数据结构,实现硬件设备的即插即用(热拔插)。
在
Linux内核中,只要任何Device和Device Driver具有相同的名字,内核就会执行Device Driver结构中的初始化函数(probe),该函数会初始化设备,使其为可用状态。
而对大多数热拔插设备而言,它们的
Device Driver一直存在内核中。当设备没有插入时,其Device结构不存在,因而其Driver也就不执行初始化操作。当设备插入时,内核会创建一个 Device结构(名称和Driver相同),此时就会触发Driver的执行。这就是即插即用的概念。
3.
通过"Bus-->Device”类型的树状结构(见2.1章节的图例)解决设备之间的依赖,而这种依赖在开关机、电源管理等过程中尤为重要。
试想,一个设备挂载在一条总线上,要启动这个设备,必须先启动它所挂载的总线。很显然,如果系统中设备非常多、依赖关系非常复杂的时候,无论是内核还是驱动的开发人员,都无力维护这种关系。
设备模型中的这种树状结构,可以自动处理这种依赖关系。启动某一个设备前,内核会检查该设备是否依赖其它设备或者总线,如果依赖,则检查所依赖的对象是否已经启动,如果没有,则会先启动它们,直到启动该设备的条件具备为止。而驱动开发人员需要做的,就是在编写设备驱动时,告知内核该设备的依赖关系即可。
4.
使用Class结构,在设备模型中引入面向对象的概念,这样可以最大限度地抽象共性,减少驱动开发过程中的重复劳动,降低工作量。
2 用户和内核设备模型之间的桥梁
由《第一章
linux设备模型的基本概念》可知,
Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。
当然,
Linux设备模型给我们带来的便利远不止如此。既然已经建立了一个组织所有设备和驱动的树状结构,用户就可以通过这棵树去遍历所有的设备,建立设备和驱动程序之间的联系,根据类型不同也可以对设备进行归类,这样就可以更清晰的去“看”这颗枝繁叶茂的大树。另外,Linux驱动模型把很多设备共有的一些操作抽象出来,大大减少了重复造轮子的可能。同时Linux设备模型提供了一些辅助的机制,比如引用计数,让开发者可以安全高效的开发驱动程序。达成了以上这些好处之后,我们还得到了一个非常方便的副产品,这就是sysfs----一个虚拟的文件系统。sysfs给用户提供了一个从用户空间去访问内核设备的方法,它在Linux里的路径是/sys。这个目录并不是存储在硬盘上的真实的文件系统,只有在系统启动之后才会建起来。
下面这个命令可以用来显示
sysfs的大致结构:
$:tree /sys
这个命令的信息量非常大,我就不贴出来了,如果有兴趣的话可以看看这里,或者自己动手实验一下。
我们来看看第一层目录结构:
/sys
|-- block
|-- bus
|-- class
|-- dev
|-- devices
|-- firmware
|-- fs
|-- kernel
|-- module
`-- power
这里有
10个子目录,但并不是说这10个目录代表了10种完全不同的设备类型,实际上这些目录只是给我们提供了如何去看整个设备模型的不同的视角。其实从不同的目录出发都有可能找到同一个设备的。那真正的设备信息到底放在哪里呢?看看目录的名称就应该能猜到,对,就是devices子目录,Linux的所有设备都可以在这个目录里找到。这里是一个大杂烩,虽然五脏俱全但我们却无从下手。这里还是以U盘为例,插上U盘之后,在devices目录里如何找到这支U盘呢?真得很难办到。但是如果知道这个U盘在系统里的设备文件名(暂且假设为sdb),那就可以从block目录着手。
透过
block目录,我们很容易就可以找到这个U盘设备,符号链接device正是指向devices目录下的位置。
到这里,我们总结一下
/sys目录下各个子目录的作用。block目录是从块设备的角度来组织设备;bus目录是从系统总线这个角度来组织设备,比如PCI总线或者USB总线;class目录把看问题的视角提高到了类别的高度,比如PCI设备或者USB设备等;dev目录的视角是设备节点;devices目录在前面提到了,这里是所有设备的大本营;firmware目录包含了一些比较低阶的子系统,比如ACPI、EFI等;fs目录里看到的是系统支持的所有文件系统;kernel目录下包含的是一些内核的配置选项;modules目录下包含的是所有内核模块的信息,内核模块实际上和设备之间是有对应关系的,通过这个目录顺藤摸瓜找到devices或者反过来都是可以做到的;power目录存放的是系统电源管理的数据,用户可以通过它来查询目前的电源状态,甚至可以直接“命令”系统进入休眠等省电模式。
sysfs
正是用户和内核设备模型之间的一座桥梁,通过这个桥梁我们可以从内核中读取信息,也可以向内核里写入信息。
在
Linux里也可以找到一些图形化的工具来查询设备信息。比如GNOME下基于HAL的Device Manager:
或者
KDE下基于Solid的KInfoCenter:
这些图形化的工具提供了更加直观的方式来访问设备,但是它们的提供的信息还不够全面,而且没有向内核设备写数据的功能。
如果具体到某一类型的设备,
Linux下还有一些专用的工具可以使用。比如面向PCI设备的pciutils,面向USB设备的usbutils,以及面向SCSI设备的lsscsi等。对于Linux开发者来说,有时使用这些专用的工具更加方便。
我们如果要写程序来访问
sysfs,可以像读写普通文件一样来操作/sys目录下的文件,或者,也可以使用libsysfs。不过需要注意的是,Linux内核社区并不推荐用libsysfs,因为这个API的更新不够快,赶不上内核的变化。libsysfs已经逐渐背离最初创建它的目标,这个lib带来的问题似乎比它解决的还要多。当然,如果只是要访问设备,一般很少会直接操作sysfs,它太细节太底层了,大部分情况下可以使用更加方便的DeviceKit或者libudev。
总结一下,本文主要简单介绍了
Linux设备模型的基本概念和虚拟文件系统sysfs。接下来的篇章里将和大家继续探讨设备模型在内核空间的一些细节。