LV06-04-linux设备模型-01-设备模型简介

linux的设备模型?这是啥?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、设备模型的概念

1. 几个基本概念

在前面写的驱动中,我们发现编写驱动有个固定的模式只有往里面套代码就可以了,它们之间的大致流程可以总结如下:

  • 实现入口函数xxx_init()和卸载函数xxx_exit()
  • 申请设备号 register_chrdev_region()
  • 初始化字符设备,cdev_init函数、cdev_add函数
  • 硬件初始化,如时钟寄存器配置使能,GPIO设置为输入输出模式等。
  • 构建file_operation结构体内容,实现硬件各个相关的操作
  • 在终端上使用mknod根据设备号来进行创建设备文件(节点) (也可以在驱动使用class_create创建设备类、在类的下面device_create创建设备节点)

因此,在Linux开发驱动,只要能够掌握了这些“套路”,开发一个驱动便不是难事。在内核源码的drivers中存放了大量的设备驱动代码, 在我们写驱动之前先查看这里的内容,说不定可以在这些目录找到想要的驱动代码。如图所示:

image-20241228150538321

只要这样根据步骤来编写我们的驱动代码倒是很简单,但是这存在着问题,我们将硬件的信息都写进了驱动里了, 根据某个硬件编写的驱动,现在需要修改一下引脚接口,这个驱动代码就得重新修改才能使用,这显然是不合理的。即便前面我们已经把硬件资源分离出来写入到单独的文件,但是,这和写成一个文件并没有太大区别,最后每次修改都还是要重新编译驱动。

那有没有其他合适的解决方案呢?答案是肯定的,Linux引入了设备驱动模型分层的概念, 将我们编写的驱动代码分成了两块:设备与驱动。设备负责提供硬件资源而驱动代码负责去使用这些设备提供的硬件资源。 并由总线将它们联系起来。这样子就构成以下图形中的关系。

image-20241228150046053

设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念:

  • 总线(bus) :负责管理挂载对应总线的设备以及驱动,提供设备之间进行通信和数据传输的基本机制。 总线是CPU和一个或多个设备之间信息交互的通道。而为了方便设备模型的抽象,所有的设备都应连接到总线上,总线可以是物理总线(如 PCI、 USB) 或虚拟总线(如虚拟设备总线) 。内核源码有一段注释,也是来解释这个总线的,可以参考 struct bus_type 的注释。

  • 设备(device) :挂载在某个总线的物理设备,例如网卡、 显示器、 键盘等。每个设备都有一个唯一的标识符, 用于在系统中进行识别和管理。 设备模型通过设备描述符来描述设备的属性和特性。

  • 驱动(driver) :与特定设备相关的软件,用于控制和管理设备的操作。Linux设备模型用driver抽象硬件设备的驱动程序,它包含设备初始化、电源管理等相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数)。

  • 类(class) :在Linux设备模型中,class的概念非常类似面向对象程序设计中的class(类),它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从class中继承即可。其实就是对于具有相同功能的设备,归结到一种类别,进行分类管理。

Tips:

(1)内核2.4之前没有统一的设备驱动模型,内核2.4到2.6之间使用devfs,设备文件挂载在/dev目录。但它需要在驱动代码中调用devfs_register来对设备文件进行命名,用户空间不可改变。内核2.6版本后使用sysfs,设备文件挂载在/sys目录。sysfs是一个虚拟文件系统,类似proc文件系统sysfs下一个目录对应一个kobject对象,sysfs下每一个目录所对应的inode节点会记录基本驱动对象kobject,从而将系统中的设备组成层次结构,用户可以通过修改这些目录下的不同文件来配置驱动对象kobject的不同属性。

(2)将设备分类、分层统一进行管理配合守护进程udev或者mdev,系统可以动态创建设备文件,命名规则可由用户自定义。

2. 总线-设备-驱动怎么工作?

“总线-设备-驱动”它们之间是如何相互配合工作的呢?这里先简单的了解一下:

image-20250105142715336

在总线上管理着两个链表,分别管理着设备和驱动,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动, 同样当我们向系统注册一个设备时,便会向设备的管理链表插入我们的新设备。在插入的同时总线会执行一个bus_type结构体中match的方法对新插入的设备/驱动进行匹配。 (它们之间最简单的匹配方式则是对比名字,存在名字相同的设备/驱动便成功匹配)。 在匹配成功的时候会调用驱动device_driver结构体中probe方法(通常在probe中获取设备资源,具体的功能可由驱动编写人员自定义), 并且在移除设备或驱动时,会调用device_driver结构体中remove方法。

以上只是设备驱动模型的 机制 ,上面的match、probe、remove等方法需要我们来实现需要的功能。

sysfs文件系统用于把内核的设备驱动导出到用户空间, 用户便可通过访问sys目录及其下的文件,来查看甚至控制内核的一些驱动设备。

3. 设备模型的核心思想

Linux设备模型的核心思想是通过什么手段,实现什么目的:

  • (1)用struct devicestruct device_driver两个数据结构,分别从“有什么用”和“怎么用”两个角度描述硬件设备。这样就统一了编写设备驱动的格式,使驱动开发从原来的论述题变为填空题,从而简化了设备驱动的开发。

  • (2)同样使用struct devicestruct device_driver两个数据结构,实现硬件设备的即插即用(热拔插)。

因为在Linux内核中,只要任何device和device driver具有相同的名字,内核就会执行device_driver结构中的初始化函数probe,该函数会初始化设备,使其为可用状态。

对大多数热拔插设备而言,它们的device driver一直存在内核中。当设备没有插入时,其device结构不存在,因而其driver也就不执行初始化操作。当设备插入时,内核会创建一个device结构(名称和driver相同),此时就会触发driver的执行。这就是即插即用的概念。

  • (3)通过”bus→device”类型的树状结构解决设备之间的依赖,而这种依赖在开关机、电源管理等过程中尤为重要。

一个设备挂载在一条总线上,要启动这个设备,必须先启动它所挂载的总线。很显然,如果系统中设备非常多、依赖关系非常复杂的时候,无论是内核还是驱动的开发人员,都无力维护这种关系。

设备模型中的这种树状结构,可以自动处理这种依赖关系。启动某一个设备前,内核会检查该设备是否依赖其它设备或者总线,如果依赖,则检查所依赖的对象是否已经启动,如果没有,则会先启动它们,直到启动该设备的条件具备为止。而驱动开发人员需要做的,就是在编写设备驱动时,告知内核该设备的依赖关系即可。

  • (4)使用class结构,在设备模型中引入面向对象的概念,这样可以最大限度地抽象共性,减少驱动开发过程中的重复劳动,降低工作量。

4. 设备模型有什么好处?

设备模型在内核驱动中扮演着重要的角色, 它提供了一种统一的方式来描述硬件设备和它们之间的关系。 以下是设备模型在内核驱动中的几个重要方面。

(1)代码复用: 设备模型允许多个设备复用同一个驱动。 通过在设备树或总线上定义不同的设备节点, 这些设备可以使用相同的驱动进行初始化和管理。 这样可以减少代码的冗余, 提高驱动的复用性和维护性。

(2)资源的动态申请和释放: 设备模型提供了一种机制来动态申请和释放设备所需的资源,如内存, 中断等。 驱动可以使用这些机制来管理设备所需的资源, 确保在设备初始化和关闭时进行正确的资源分配和释放。

(3)简化驱动编写: 设备模型提供了一组通用 API 和机制, 使得驱动编写更加简化和模块化。开发人员可以使用这些 API 来注册设备, 处理设备事件, 进行设备的读写操作等, 而无需重复实现这些通用功能。

(4)热插拔机制: 设备模型支持热插拔机制, 能够在运行时动态添加或移除设备。 当设备插入或拔出时, 内核会生成相应的热插拔事件, 驱动可以通过监听这些事件来执行相应的操作,如设备的初始化或释放。

(5)驱动的面向对象思想: 设备模型的设计借鉴了面向对象编程(OOP) 的思想。 每个设备都被看作是一个对象, 具有自己的属性和方法, 并且可以通过设备模型的机制进行继承和扩展。这种设计使得驱动的编写更加模块化和可扩展, 可以更好地应对不同类型的设备和功能需求。

设备模型的设计目的是为了提供一种统一的方式来管理和操作系统中的各种硬件设备。 通过将设备、 驱动和总线等概念进行抽象和标准化, 设备模型可以提供一致的接口和数据结构,简化驱动开发和设备管理, 并实现设备的兼容性和可移植性。

Tips:

总之一句话:为了解决这种驱动代码和设备信息耦合的问题,linux提出了设备驱动模型。

二、设备模型相关数据结构

1. 总线

1.1 总线与平台总线

在设备驱动模型中, 引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们SOC中物理总线的概念并不严格相等:

  • 物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
  • 驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为它们进行配对。

总线是连接处理器和设备之间的桥梁,总线代表着同类设备需要共同遵守的工作时序,是连接处理器和设备之间的桥梁。我们接触到的设备大部分是依靠总线来进行通信的, 它们之间的物理连接如图所示:

image-20250106140851894

总线驱动则负责实现总线的各种行为,其管理着两个链表,分别是添加到该总线的设备链表以及注册到该总线的驱动链表。当我们向总线添加(移除)一个设备(驱动)时,便会在对应的列表上添加新的节点, 同时对挂载在该总线的驱动以及设备进行匹配,在匹配过程中会忽略掉那些已经有驱动匹配的设备。

image-20250106141016266

一般对于I2C、SPI、USB这些常见类型的物理总线来说,Linux内核会自动创建与之相应的驱动总线,因此I2C设备、SPI设备、 USB设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。 它们也就没有相应的物理总线,比如led、rtc时钟、蜂鸣器、按键等等,Linux内核将不会为它们创建相应的驱动总线。

为了使这部分设备的驱动开发也能够遵循设备驱动模型,Linux内核引入了一种虚拟的总线——平台总线( platform bus )。 平台总线用于管理、挂载那些没有相应物理总线的设备,这些设备被称为平台设备,对应的设备驱动则被称为平台驱动。 平台设备驱动的核心依然是Linux设备驱动模型,平台设备使用platform_device结构体来进行表示,其继承了设备驱动模型中的device结构体。 而平台驱动使用platform_driver结构体来进行表示,其则是继承了设备驱动模型中的device_driver结构体。

1.2 struct bus_type

在内核中使用结构体 bus_type 来表示总线,它定义在:device.h - include/linux/device.h - struct bus_type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
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);
void (*shutdown)(struct device *dev);

int (*online)(struct device *dev);
int (*offline)(struct device *dev);

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

int (*num_vf)(struct device *dev);

int (*dma_configure)(struct device *dev);

const struct dev_pm_ops *pm;

const struct iommu_ops *iommu_ops;

struct subsys_private *p;
struct lock_class_key lock_key;

bool need_parent_lock;
};
  • name :指定总线的名称,当新注册一种总线类型时,会在/sys/bus目录创建一个新的目录,目录名就是该参数的值;
  • drv_groups、dev_groups、bus_groups :分别表示驱动、设备以及总线的属性。这些属性可以是内部变量、字符串等等。通常会在对应的/sys目录下在以文件的形式存在,对于驱动而言,在目录/sys/bus/<bus-name>/driver/<driver-name>存放了设备的默认属性;设备则在目录/sys/bus/<bus-name>/devices/<driver-name>中。这些文件一般是可读写的,用户可以通过读写操作来获取和设置这些attribute的值。
  • match :当向总线注册一个新的设备或者是新的驱动时,会调用该回调函数。该回调函数主要负责判断是否有注册了的驱动适合新的设备,或者新的驱动能否驱动总线上已注册但没有驱动匹配的设备;
  • uevent :总线上的设备发生添加、移除或者其它动作时,就会调用该函数,来通知驱动做出相应的对策。
  • probe :当总线将设备以及驱动相匹配之后,执行该回调函数,最终会调用驱动提供的probe函数。
  • remove :当设备从总线移除时,调用该回调函数;
  • suspend、resume :电源管理的相关函数,当总线进入睡眠模式时,会调用suspend回调函数;而resume回调函数则是在唤醒总线的状态下执行;
  • pm :电源管理的结构体,存放了一系列跟总线电源管理有关的函数,与device_driver结构体中的pm_ops有关;
  • p :该结构体用于存放特定的私有数据,其成员klist_devices和klist_drivers记录了挂载在该总线的设备和驱动;

其实上面哪些注释里面都有。

1.3 总线的注册与注销

1.3.1 bus_register()

在实际编写linux驱动模块时,Linux内核已经为我们写好了大部分总线驱动,正常情况下我们一般不会去注册一个新的总线, 内核中提供了bus_register函数来注册总线,以及bus_unregister函数来注销总线,它定义在bus.c - drivers/base/bus.c - bus_register

1
2
3
4
5
6
7
8
9
/**
* bus_register - register a driver-core subsystem
* @bus: bus to register
*
* Once we have that, we register the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the subsystem.
*/
int bus_register(struct bus_type *bus);

参数:

  • bus: bus_type类型的结构体指针

返回值:

  • 成功: 0
  • 失败: 负数

1.3.2 bus_unregister()

对应的,我们注销一个总线驱动,可以通过bus_unregister()函数,它定义在:bus.c - drivers/base/bus.c - bus_unregister

1
2
3
4
5
6
7
8
/**
* bus_unregister - remove a bus from the system
* @bus: bus.
*
* Unregister the child subsystems and the bus itself.
* Finally, we call bus_put() to release the refcount
*/
void bus_unregister(struct bus_type *bus);

参数:

  • bus :bus_type类型的结构体指针

返回值:

1.4 总结

当我们成功注册总线时,会在/sys/bus/目录下创建一个新目录,目录名为我们新注册的总线名。bus目录中包含了当前系统中已经注册了的所有总线,例如i2c,spi,platform等。我们看到每个总线目录都拥有两个子目录devices和drivers, 分别记录着挂载在该总线的所有设备以及驱动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sumu@sumu-virtual-machine:/sys$ tree bus -L 2
bus
├── ac97
|#...
├── i2c
│ ├── devices
│ ├── drivers
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
|#...
├── spi
│ ├── devices
│ ├── drivers
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
├── usb
│ ├── devices
│ ├── drivers
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
|#...

133 directories, 135 files

2. 设备

驱动开发的过程中,我们最关心的莫过于设备以及对应的驱动了。我们编写驱动的目的,最终就是为了使设备可以正常工作。在Linux中,一切都是以文件的形式存在, 设备也不例外。/sys/devices目录记录了系统中所有设备,实际上在sys目录下所有设备文件最终都会指向该目录对应的设备文件;此外还有另一个目录/sys/dev记录所有的设备节点, 但实际上都是些链接文件,同样指向了devices目录下的文件。

image-20241228155953533

2.1 struct device

在内核使用device结构体来描述我们的物理设备,这个结构体定义在 device.h - include/linux/device.h - struct device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct device {
struct device *parent;
//......
const char *init_name; /* initial name of the device */
//......
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this device */
void *platform_data; /* Platform specific data, device core doesn't touch it */
void *driver_data; /* Driver data, set and get with dev_set/get_drvdata */
//......
struct device_node *of_node; /* associated device tree node */
//......
dev_t devt; /* dev_t, creates the sysfs "dev" */
//......
struct class *class;
const struct attribute_group **groups; /* optional groups */

void (*release)(struct device *dev);
//......
};
  • parent :表示该设备的父对象,前面提到过,旧版本的设备之间没有任何关联,引入Linux设备模型之后,设备之间呈树状结构,便于管理各种设备;
  • init_name :指定该设备的名称,总线匹配时,一般会根据比较名字,来进行配对;
  • bus :表示该设备依赖于哪个总线,当我们注册设备时,内核便会将该设备注册到对应的总线。
  • platform_data :特定设备的私有数据,通常定义在板级文件中;
  • driver_data :同上,驱动层可通过dev_set/get_drvdata函数来获取该成员;
  • of_node :存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的of_match_table以及设备树的compatible属性进行比较之后,将匹配的节点保存到该变量。
  • devt :dev_t类型变量,字符设备章节提及过,它是用于标识设备的设备号,该变量主要用于向/sys目录中导出对应的设备。
  • class :指向了该设备对应类,开篇我们提到的触摸,鼠标以及键盘等设备,对于计算机而言,他们都具有相同的功能,都归属于输入设备。我们可以在/sys/class目录下对应的类找到该设备,如input、leds、pwm等目录;
  • group :指向struct attribute_group类型的指针,指定该设备的属性;
  • release :回调函数,当设备被注销时,会调用该函数。如果我们没定义该函数时,移除设备时,会提示“Device ‘xxxx’ does not have a release() function, it is broken and must be fixed”的错误。

2.2 设备的注册与注销

2.2.1 device_register()

device_register()定义在core.c - drivers/base/core.c - device_register

1
2
3
4
5
6
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);

参数:

  • dev :struct device结构体类型指针

返回值:

  • 成功: 0
  • 失败: 负数

2.2.2 device_unregister()

device_unregister()定义在core.c - drivers/base/core.c - device_unregister

1
2
3
4
5
6
7
void device_unregister(struct device *dev)
{
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
device_del(dev);
put_device(dev);
}
EXPORT_SYMBOL_GPL(device_unregister);

参数:

  • dev :struct device结构体类型指针

返回值:

2.3 总结

当成功注册总线时,会在/sys/bus目录下创建对应总线的目录,该目录下有两个子目录,分别是drivers和devices, 我们使用device_register注册的设备从属于某个总线时,该总线的devices目录下便会存在该设备文件。

3. 驱动

3.1 struct device_driver

设备能否正常工作,取决于驱动。驱动需要告诉内核, 自己可以驱动哪些设备,如何初始化设备。在内核中,使用device_driver结构体来描述我们的驱动,它定义在 device.h - include/linux/device.h - struct device_driver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 */
enum probe_type probe_type;

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);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;

const struct dev_pm_ops *pm;
void (*coredump) (struct device *dev);

struct driver_private *p;
};
  • name :指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
  • bus :表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
  • suppress_bind_attrs :布尔量,用于指定是否通过sysfs导出bind与unbind文件,bind与unbind文件是驱动用于绑定/解绑关联的设备。
  • owner :表示该驱动的拥有者,一般设置为THIS_MODULE;
  • of_match_table ::指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的compatible属性进行比较。
  • remove :当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
  • probe :当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以main函数开始执行的,但是在内核的驱动代码,都是从probe函数开始的。
  • group :指向struct attribute_group类型的指针,指定该驱动的属性;

3.2 驱动的注册与注销

3.2.1 driver_register()

driver_register()定义在 driver.c - drivers/base/driver.c - driver_register

1
2
3
4
5
6
7
8
9
/**
* driver_register - register driver with bus
* @drv: driver to register
*
* We pass off most of the work to the bus_add_driver() call,
* since most of the things we have to do deal with the bus
* structures.
*/
int driver_register(struct device_driver *drv);

参数:

  • drv :struct device_driver 结构体类型指针

返回值:

  • 成功: 0
  • 失败: 负数

3.2.2 driver_unregister()

driver_unregister()定义在 driver.c - drivers/base/driver.c - driver_unregister

1
2
3
4
5
6
7
/**
* driver_unregister - remove driver from system.
* @drv: driver.
*
* Again, we pass off most of the work to the bus-level call.
*/
void driver_unregister(struct device_driver *drv);

参数:

  • drv:struct device_drive结构体类型指针

返回值:

3.3 总结

跟设备一样,当成功注册总线时,会在/sys/bus目录下创建对应总线的目录,该目录下有两个子目录,分别是drivers和devices, 我们使用driver_register注册的驱动从属于某个总线时,该总线的drivers目录下便会存在该驱动文件。

4. 类

4.1 struct class

struct class 定义在 device.h - include/linux/device.h - struct 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
25
26
27
28
29
30
31
32
33
struct class {
// 设备类的名称
const char *name;
// 拥有该类的模块
struct module *owner;
// 类属性组,用于描述设备类的属性,在类注册到内核时,会自动在 /sys/class/xxx_class 下创建对应的属性文件
const struct attribute_group **class_groups;
// 设备属性组,用于描述设备的属性,在类注册到内核时,会自动在该类下的 设备目录创建对应的属性文件
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 (*shutdown_pre)(struct device *dev);
// 命名空间类型操作
const struct kobj_ns_type_operations *ns_type;
// 命名空间函数
const void *(*namespace)(struct device *dev);
// 获取设备所有权的函数
void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);
// 电源管理操作
const struct dev_pm_ops *pm;
// 子系统私有数据
struct subsys_private *p;
};

4.2 类的创建与销毁

4.2.1 class_create()

class_create() 定义如下:

1
2
3
4
5
6
7
8
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})

4.2.2 class_destroy()

class_destroy() 定义如下:

1
2
3
4
5
6
7
void class_destroy(struct class *cls)
{
if ((cls == NULL) || (IS_ERR(cls)))
return;

class_unregister(cls);
}

5. 总结

5.1 设备和驱动数据结构关系

到为止简单地了解了总线、设备、驱动的数据结构以及注册/注销接口函数。下图是总线关联上设备与驱动之后的数据结构关系图:

/sys/bus目录

5.2 注册流程

设备和驱动的注册流程如下:

/sys/bus目录

系统启动之后会调用buses_init函数创建/sys/bus文件目录,这部分系统在开机时已经帮我们准备好了, 接下去就是通过总线注册函数bus_register进行总线注册,注册完总线后在总线的目录下生成devices文件夹和drivers文件夹, 最后分别通过device_register以及driver_register函数注册相对应的设备和驱动。

参考资料

【1】linux驱动开发—— 6、linux 设备驱动模型_什么是kobject-CSDN博客

【2】Linux设备模型剖析系列一(基本概念、kobject、kset、kobj_type)_device下的kobj-CSDN博客

【3】Linux设备模型(1)_基本概念

【4】一张图掌握 Linux platform 平台设备驱动框架!【建议收藏】-CSDN博客