Linux设备驱动模型

简介

Linux 设备驱动模型是内核中用于统一管理硬件设备、驱动程序和总线关系的核心框架。它通过一系列抽象结构(如struct devicestruct driverstruct classstruct bus_type等)构建了一个层次化的设备拓扑,使内核能够动态管理设备的生命周期(如热插拔、电源管理),并为用户空间(通过 /sys)提供标准化的设备信息接口。

设备驱动框架和设备驱动模型不是一个东西,前者指的是GPIO、Input、Pinctl等子系统,而后者主要是Linux内核对于驱动开发中的一种设计模式

Linux设备驱动模型的核心思想是把我们编写的驱动代码进行分层解耦:

  • 设备:提供硬件资源
  • 驱动:使用设备提供的硬件资源进行初始化等操作
  • 总线:将设备和驱动匹配起来
image-20250310204259795

任何设备都挂在在某个总线上,即使物理上没有总线,也要虚拟出来个总线(platform)

核心目标

  1. 统一管理硬件:无论设备是 PCI、USB、I2C 还是platform设备,都通过一致的接口管理
  2. 支持动态事件:热插拔、电源状态切换(休眠/唤醒)等
  3. 用户空间交互:通过 sysfs 文件系统(/sys)暴露设备属性和配置
  4. 自动匹配驱动:设备与驱动通过总线或类机制自动绑定

设备驱动模型的核心组件

Linux的设备驱动模型框架中,对于设备、驱动、总线有很多派生类,这里只列出其最原始的基类的代码

kobject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd;
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
image-20250310203354812

作用:

  • 是Linux设备驱动模型的根基,所有涉及到的类都继承该类(C语言实际上没继承,它会作为成员变量)

    • 通过parent指针记录整个设备模型的拓扑关系
  • 管理对象的引用计数

  • sysfs虚拟文件系统配合,每个Kobject会生成一个目录,将内核中的设备驱动拓扑关系展示到用户空间

attribute

总线、设备和驱动中的各个attribute成员变量则直接落实为sysfs中的1个文件,attribute会伴随着show()store()这两个函数,分别用于读写该attribute对应的sysfs文件

设备

/sys/devices目录记录了系统中所有设备,此外还有另一个目录/sys/dev记录所有的设备节点, 但实际上都是些链接文件,同样指向了devices目录下的文件

  • 定义:挂载在某个总线上的具体设备,如I2C传感器、USB设备、platform设备…它包含了一个设备的一些关键属性,比如:设备名、设备号、设备属于的类、总线、父设备…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct device {
const char *init_name;
struct device *parent;
struct bus_type *bus;
struct device_driver *driver;
void *platform_data;
void *driver_data;
struct device_node *of_node;
dev_t devt;
struct class *class;
void (*release)(struct device *dev);
const struct attribute_group **groups; /* optional groups */
struct device_private *p;
........
};

// 内核注册/注销设备(内核源码/driver/base/core.c)
int device_register(struct device *dev);
void device_unregister(struct device *dev);

struct device是所有设备的通用抽象基类,Linux内核还提供了许多其他的设备类,比如platform_devicei2c_client等,用于针对特定总线或设备类型进行扩展

struct device及其子类与struct cdev不属于同一体系,前者负责对硬件资源的抽象表示,属于Linux设备驱动模型。而后者主要用于提供与用户空间交互的接口。所以驱动代码里,可能会同时出现这2种设备。

驱动

注册了的驱动记录在/sys/bus/<bus>/drivers

  • 定义:与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct device_driver {
const char *name;
struct bus_type *bus;

struct module *owner;
const char *mod_name; /* used for built-in modules */

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */

const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;

int (*probe) (struct device *dev);
int (*remove) (struct device *dev);

const struct attribute_group **groups;
struct driver_private *p;
};

int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

内核针对具体的总线,为该类派生了许多子类,比如platform_driver, i2c_driver

  • 关键函数:
    • probe:设备和驱动匹配后,自动调用该函数,用来对设备初始化
    • remove:设备被拔出或者重启时被调用

总线

总线代表着同类设备需要共同遵守的工作时序,是连接处理器和设备之间的桥梁

注册了的总线位于sys/bus目录下,每个目录中又包含devicesdrivers2个目录,分别记录挂载在该总线的设备和驱动

image-20250310212912162
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct bus_type {
const char *name;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;

struct subsys_private *p;

};

struct bus_type是总线的唯一类,具体的总线类型也属于该类,比如platform_busi2c_bus,只不过name和那些函数指针不同

  • 关键函数:
    • match():通过设备名称或设备树兼容性(of_match_table)匹配设备与驱动
    • probe():match匹配成功后,会先调用总线的probe完成一些资源的初始化,再调用模块注册的probe
    • remove():设备移除时的资源释放逻辑

同一类的设备在 /sys/class 下有相同接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct class {
const char *name;
struct module *owner;

struct class_attribute *class_attrs;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;

int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);

void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
...
};

// 在/sys/class下创建/销毁类
extern struct class * __class_create(struct module *owner,
const char *name,
struct lock_class_key *key);
extern void class_destroy(struct class *cls);
  • 类是对于设备的一种high level抽象,按照功能可以对设备进行分类(区别于字符设备/块设备这种分类法),比如输入设备、LED设备、MISC设备、DRM设备…
  • Linux内核引入类,是将其用作具有相同功能的一类设备的容器

总线-设备-驱动的协作

image-20250310213928323

image-20250310214256435

匹配的时机

在总线上管理着两个链表,分别管理着设备和驱动。Linux中设备和驱动的注册的各自独立的,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动, 同样当我们向系统注册一个设备时,便会向设备的管理链表插入我们的新设备。

在插入的同时总线会执行一次bus_type结构体中match的方法对新插入的设备/驱动进行匹配。在匹配成功的时候会调用驱动device_driver结构体中probe方法(通常在probe中获取设备资源,具体的功能可由驱动编写人员自定义), 并且在移除设备或驱动时,会调用device_driver结构体中remove方法

匹配的方式

匹配的方式主要看该总线的match的实现,不同总线的 match() 函数逻辑不同:

  • platform总线:比较设备名称(platform_device.name)或设备树兼容性(.of_match_table
  • I2C 总线:匹配设备地址(i2c_client.addr)和驱动支持的地址列表
  • PCI 总线:检查 PCI 设备的厂商ID和设备ID是否在驱动的 id_table

设备树触发匹配的流程

  1. 设备树解析:内核启动时解析设备树(.dts),为每个设备节点(Node)生成 struct platform_device
  2. 动态注册设备:调用 of_platform_populate() 将设备树节点注册为平台设备
  3. 匹配驱动:通过设备树节点的 compatible 属性与驱动的 .of_match_table 匹配