type
date
slug
category
icon
password
一、Linux 的设备模型sys文件目录讲解拓扑图分析“总线-设备-驱动”机制二、进一步了解2.1 总线2.2 设备2.3 驱动小结三、属性文件3.1 设备属性文件3.2 驱动属性文件3.3 总线属性文件
一、Linux 的设备模型
分离硬件和驱动 → 设备驱动模型分层概念
编写的驱动代码分成了两块:设备与驱动。设备负责提供硬件资源而驱动负责使用设备提供的硬件资源。 并由总线将它们联系起来。这样子就构成下图中的关系。

设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况:
- 设备(device) :挂载在某个总线的物理设备;
- 驱动(driver) :与特定设备相关的软件,负责初始化该设备以及提供该设备的操作方式;
- 总线(bus) :负责管理挂载对应总线的设备以及驱动;
- 类(class) :对于具有相同功能的设备,归结到一种类别,进行分类管理;
sys文件目录讲解
根文件系统中有个/sys文件目录,里面记录各个设备之间的关系。 下面介绍/sys下几个较为重要目录的作用。
其中
/sys/bus
总线类型集合,每个总线类型下有devices和drivers文件夹- devices 该总线下面所有设备,这些设备是符号链接,真正指向的是
/sys/devices
下面设备。 - drivers 所有注册在这个总线上的驱动,每个driver子目录下 是一些可以观察和修改的driver参数。
/sys/devices
注册在各种总线上,已被系统发现的物理设备。按照总线拓扑结构分类。
/sys/class
注册在kernel上设备类型,按照设备功能分类。
拓扑图分析
通过下面拓扑图,记录着设备与设备之间关系。

由上图,usb总线下需要编写drivers和devices,drivers提供usb-hid人机交互设备驱动,devices真实链接的是Devices下usb2。
Devices下面是根据总线类型分布,是系统已经发现的物理设备。
class 下面比如鼠标,根据输入设备分类, 在Devices找到对应总线下对应物理设备,和drivers下HID驱动连接起来。
“总线-设备-驱动”机制

在总线上管理着两个链表,分别管理着设备和驱动,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动, 同样当我们向系统注册一个设备时,便会向设备的管理链表插入我们的新设备。
在插入的同时总线会执行一个
bus_type
结构体中match
的方法对新插入的设备/驱动进行匹配。 (它们之间最简单的匹配方式则是对比名字,存在名字相同的设备/驱动便成功匹配)。 在匹配成功的时候会调用驱动device_driver结构体中probe方法(通常在probe中获取设备资源,具体的功能可由驱动编写人员自定义), 并且在移除设备或驱动时,会调用device_driver
结构体中remove
方法。以上只是设备驱动模型的机制 ,上面的
match
、probe
、remove
等方法需要我们来实现需要的功能。接下来进一步了解总线、驱动、设备,具体了解如何使用代码来实现创建总线,并在总线上创建设备及驱动。 同时也可以将我们驱动的某个控制变量,导出到用户空间。二、进一步了解
2.1 总线
总线是连接处理器和设备之间的桥梁,总线代表着同类设备需要共同遵守的工作时序,是连接处理器和设备之间的桥梁。
总线驱动则负责实现总线的各种行为,其管理着两个链表,分别是添加到该总线的设备链表以及注册到该总线的驱动链表。
在内核中使用结构体
bus_type
来表示总线,如下所示:- name: 指定总线的名称,新注册总线类型,会在
/sys/bus
目录创建一个新的目录,目录名就是该参数的值;
- drv_groups、dev_groups、bus_groups: 分别表示驱动、设备以及总线的属性。驱动属性存放/在
sys/bus/<bus-name>/driver/<driver-name>
下,设备属性存放在目录/sys/bus/<bus-name>/devices/<driver-name>
中;
- match: 当向总线注册一个新的设备或者是新的驱动时,会调用该回调函数。为新设备匹配驱动,或者为新驱动匹配设备;
- uevent: 总线上的设备发生添加、移除或者其它动作时,就会调用该函数,来通知驱动做出相应的对策;
- probe: 当总线将设备以及驱动相匹配之后,执行该回调函数,最终会调用驱动提供的probe函数(probe 通常是获取资源)。
- remove: 当设备从总线移除时,调用该回调函数;
- suspend、resume: 电源管理的相关函数,当总线进入睡眠模式时,会调用suspend回调函数;而resume回调函数则是在唤醒总线的状态下执行;
- pm: 电源管理的结构体,存放了一系列跟总线电源管理有关的函数,与
device_driver
结构体中的pm_ops有关;
- p: 该结构体用于存放特定的私有数据,其成员
klist_devices
和klist_drivers
记录了挂载在该总线的设备和驱动;
在实际编写 Linux 驱动模块时,Linux内核已经为我们写好了大部分总线驱动,正常情况下我们一般不会去注册一个新的总线。但我们还是了解一下内核提供过的注册总线和注销总线函数:
当我们成功注册总线时,会在
/sys/bus/
目录下创建一个新目录,目录名为我们新注册的总线名。bus目录中包含了当前系统中已经注册了的所有总线,例如 i2c,spi,platform 等。我们看到每个总线目录都拥有两个子目录devices和drivers, 分别记录着挂载在该总线的所有设备以及驱动。
2.2 设备
驱动开发的过程中,我们最关心的莫过于设备以及对应的驱动了。
- 目录
/sys/devices
记录了系统中所有设备,实际上在sys目录下所有设备文件最终都会指向该目录对应的设备文件;
- 目录
/sys/dev
记录所有的设备节点, 但实际上都是些链接文件,同样指向了devices目录下的文件。

在内核使用
device
结构体来描述我们的物理设备,如下所示:- init_name: 指定该设备的名称,总线匹配时,一般会根据比较名字,来进行配对;
- parent: 表示该设备的父对象,前面提到过,旧版本的设备之间没有任何关联,引入Linux设备模型之后,设备之间呈树状结构,便于管理各种设备;
- bus: 表示该设备依赖于哪个总线,当我们注册设备时,内核便会将该设备注册(添加链表)到对应的总线;
- of_node: 存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的
of_match_table
以及设备树的compatible
属性进行比较之后,将匹配的节点保存到该变量;
- platform_data: 特定设备的私有数据,通常定义在板级文件中;
- driver_data: 同上,驱动层可通过
dev_set/get_drvdata
函数来获取该成员;
- class: 指向了该设备对应类,开篇我们提到的触摸,鼠标以及键盘等设备,对于计算机而言,他们都具有相同的功能,都归属于输入设备。我们可以在
/sys/class
目录下对应的类找到该设备,如input、leds、pwm等目录;
- dev:
dev_t
类型变量,字符设备章节提及过,它是用于标识设备的设备号,该变量主要用于向/sys
目录中导出对应的设备;
- release: 回调函数,当设备被注销时,会调用该函数。如果我们没定义该函数时,移除设备时,会提示
“Device ‘xxxx’ does not have a release() function, it is broken and must be fixed”
的错误;
- group: 指向
struct attribute_group
类型的指针,指定该设备的属性。
内核也提供相关的API来注册和注销设备,如下所示:
在讲解总线的时候,我们说过,当成功注册总线时,会在/sys/bus目录下创建对应总线的目录,该目录下有两个子目录,分别是drivers和devices, 我们使用device_register注册的设备从属于某个总线时,该总线的devices目录下便会存在该设备文件。
2.3 驱动
设备能否正常工作,取决于驱动。驱动需要告诉内核, 自己可以驱动哪些设备,如何初始化设备。在内核中,使用device_driver结构体来描述我们的驱动,如下所示:
- name: 指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
- bus: 表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
- suppress_bind_attrs: 布尔量,用于指定是否通过 sysfs 导出 bind 与 unbind 文件,bind 与unbind 文件是驱动用于绑定/解绑关联的设备。
- owner: 表示该驱动的拥有者,一般设置为THIS_MODULE;
- of_match_table: 指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的
compatible
属性进行比较。
- probe: 当匹配驱动以及设备后,会执行该回调函数(总线中回调函数实际上会调用该处的),对设备进行初始化。通常的代码都是以main 函数开始执行的,但是在内核的驱动代码,都是从
probe
函数开始的。
- remove: 当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
- group: 指向
struct attribute_group
类型的指针,指定该驱动的属性;
内核提供了
driver_register
函数以及driver_unregister
函数来注册/注销驱动,成功注册的驱动会记录在/sys/bus/<bus>/drivers
目录, 函数原型如下所示:小结
到为止简单地介绍了总线、设备、驱动的数据结构以及注册/注销接口函数。下图是总线关联上设备与驱动之后的数据结构关系图

大致注册流程如下:

系统启动之后会调用
buses_init
函数创建/sys/bus
文件目录,这部分系统在开机时已经帮我们准备好了, 接下去就是通过总线注册函数bus_register
进行总线注册,注册完总线后在总线的目录下生成devices文件夹和drivers文件夹, 最后分别通过device_register
以及driver_register
函数注册相对应的设备和驱动。三、属性文件
bus_type、device、device_driver 结构体中都包含了一种数据类型
struct attribute_group
,如下所示,它是多个attribute文件集合,利用其进行初始化,可以提高注册attribute效率。注册的attribute用于描述在/sys
目录下生成文件(文件名和文件权限)。3.1 设备属性文件
- DEVICE_ATTR宏 定义用于定义一个
device_attribute
类型的变量(带dev_attr_
前缀)。四个参数_name,_mode,_show,_store,分别代表了文件名, 文件权限,show 回调函数,store回调函数。 - show回调函数以及store回调函数分别对应着用户层的cat和echo命令
- 参数_mode的值,可以使用S_IRUSR、S_IWUSR、S_IXUSR等宏定义
- device_create_file 函数用于创建文件
- device结构体,在总线的devices子目录创建一个属于该设备的目录
- device_attribute类型变量
- device_remove_file 函数用于删除文件
3.2 驱动属性文件
驱动属性文件,和设备属性文件的作用是一样,唯一的区别在于函数参数的不同,函数接口如下:
- DRIVER_ATTR_RW、DRIVER_ATTR_RO 以及 DRIVER_ATTR_WO 宏定义用于定义一个driver_attribute类型的变量,带有driver_attr_的前缀,区别在于文件权限不同, RW后缀表示文件可读写,RO后缀表示文件仅可读,WO后缀表示文件仅可写。而且你会发现,DRIVER_ATTR类型的宏定义没有参数来设置show和store回调函数, 那如何设置这两个参数呢?在写驱动代码时,只需要你提供xxx_store以及xxx_show这两个函数, 并确保两个函数的xxx和DRIVER_ATTR类型的宏定义中名字是一致的即可。
- driver_create_file 和 driver_remove_file 函数用于创建和移除文件,使用driver_create_file函数, 会在/sys/bus/<bus-name>/drivers/<driver-name>/目录下创建文件。
3.3 总线属性文件
同样的,Linux也为总线通过了相应的函数接口,如下所示:
- BUS_ATTR宏定义用于定义一个bus_attribute变量,
- 使用bus_create_file函数,会在/sys/bus/<bus-name>下创建对应的文件。
- bus_remove_file则用于移除该文件
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/linux_bus_dev_drv_model
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!