[TOC]

drivers/base/core.c 设备核心(Device Core) 设备模型的运转中枢
drivers/base/core.c 是Linux内核统一设备模型的心脏。它不是一个孤立的功能,而是整个设备模型框架的中央实现和调度器。文件中包含了设备注册与注销、设备与驱动的绑定与解绑、设备生命周期管理、sysfs 核心接口以及电源管理回调等最核心的逻辑。可以说,内核中任何设备的任何状态变化,都离不开core.c中代码的直接或间接执行。
历史与背景
这项技术是为了解决什么特定问题而诞生的?
在统一设备模型被构想出来时,需要一个集中的地方来实现所有设备和驱动都必须遵循的通用规则和流程。core.c正是为了这个目的而创建的,它解决了以下核心问题:
- 通用设备管理:提供一个单一、标准的接口(
device_register, device_unregister)来处理系统中所有类型设备的添加和移除,避免每个子系统(PCI, USB等)重复造轮子。
- 驱动绑定协调:建立一个通用的机制,在设备或驱动被添加时,主动触发匹配过程,并在匹配成功后,以标准的顺序调用驱动的
probe函数来初始化设备。
- Sysfs 视图生成:作为
kobject和sysfs的直接用户,core.c负责为每个注册的struct device在/sys/devices下创建对应的目录和基础属性文件(如uevent, power子目录)。
- 生命周期与引用计数:实现围绕
struct device的引用计数机制(get_device, put_device),确保在有内核代码或用户空间文件正在使用设备时,该设备的数据结构不会被过早释放,这是维持系统稳定性的关键。
- 电源管理框架集成:提供调用设备驱动中电源管理回调函数(
suspend, resume)的中心调度点。
它的发展经历了哪些重要的里程碑或版本迭代?
core.c的发展史就是设备模型本身的发展史。
- 内核 2.5 系列:与设备模型一同诞生,实现了设备注册、
kobject集成和基础的sysfs文件创建。
- 内核 2.6 系列:功能大规模完善,与
udev机制紧密结合的uevent机制在这里实现。probe/remove的调用逻辑变得更加健壮。
- 异步探测的引入:为了解决多核时代系统启动速度的瓶颈,
core.c中加入了对设备异步探测的支持。这允许内核在满足依赖关系的前提下,并行地初始化多个设备,是重要的性能里程碑。
- 设备链接(Device Links)的实现:为解决复杂的跨总线设备依赖问题(例如,一个I2C设备依赖于某个GPIO控制器先完成初始化),
core.c中加入了管理设备链接的逻辑,这使得内核可以更精确地控制设备的探测顺序。
- 持续的健壮性改进:多年来,
core.c中的锁机制、错误处理和资源清理路径一直在不断地被审查和优化,以应对日益复杂的硬件和并发场景。
目前该技术的社区活跃度和主流应用情况如何?
core.c是内核驱动子系统中最核心、最活跃的文件之一。任何对设备模型核心行为的修改、对电源管理的改进、对启动性能的优化,几乎都会涉及到对这个文件的修改。它不是一个可选的应用,而是Linux内核强制性的基础设施,所有设备驱动的运行都依赖于它。
核心原理与设计
它的核心工作原理是什么?
core.c的核心是围绕struct device的生命周期管理和状态转换。
设备注册 (device_add):当一个驱动调用如platform_device_add等高级接口时,最终会调用到device_add。这个函数执行一系列关键操作:
- 初始化内嵌的
kobject,并将其添加到设备模型的层次结构中(设置其parent指针)。
- 在
sysfs中创建对应的目录,例如 /sys/devices/platform/serial8250/tty/ttyS0。
- 创建默认的
sysfs属性文件,如uevent。
- 将设备添加到其所属总线的设备列表(
bus->p->klist_devices)中。
- 触发驱动匹配:调用
bus_probe_device(dev),这是最关键的一步。此举会遍历总线上的所有驱动,尝试为这个新设备寻找一个“主人”。
驱动与设备的绑定 (driver_attach -> device_bind_driver -> really_probe):
- 当
bus_probe_device在总线上找到一个match成功的驱动后,会调用driver_attach。
driver_attach最终会调用really_probe,该函数负责实际的绑定工作。
- 它会检查设备的电源状态,确保设备处于可用状态。
- 调用驱动的
probe 函数:ret = drv->probe(dev)。这是驱动获得设备控制权、进行硬件初始化的入口点。
- 如果
probe成功(返回0),则将dev->driver指针指向该驱动,完成绑定。设备和驱动的sysfs目录下会创建符号链接,相互指向对方。
设备注销 (device_del):
- 首先,检查设备是否已绑定驱动。如果绑定了,就调用该驱动的
remove函数,让驱动释放硬件资源。
- 将
dev->driver指针设为NULL,解除绑定。
- 从总线的设备列表中移除该设备。
- 从
sysfs中移除对应的目录和文件。
- 从设备模型的父子关系中脱离。
引用计数:
get_device(dev)会增加设备内嵌kobject的引用计数。
put_device(dev)会减少引用计数。当计数减至零时,会触发一个释放函数(device_release),该函数负责释放struct device本身占用的内存。这套机制确保了只要有任何地方还在“使用”一个设备,它的数据结构就不会消失。
它的主要优势体现在哪些方面?
- 逻辑集中化:将所有设备共有的复杂逻辑(注册、绑定、
sysfs管理、电源管理)集中实现,极大地降低了驱动开发的复杂性。
- 强制统一模型:确保了所有驱动都遵循相同的生命周期和状态模型,使得整个驱动子系统行为一致、可预测。
- 解耦:将设备的枚举(由总线驱动完成)、驱动的匹配(由总线
match函数完成)和设备的初始化(由驱动probe函数完成)清晰地分离开。
- 强大的基础:为
sysfs、udev、电源管理等高级功能提供了坚实的基础。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
core.c本身没有“不适用”的场景,因为它是强制性的。其主要特点是复杂性:
- 陡峭的学习曲线:要完全理解
core.c中的锁交互(如device_hotplug_lock)、异步执行流程和复杂的错误处理路径,需要对内核有深入的了解。
- 调试困难:由于其核心地位,
core.c中的一个微小bug或竞态条件都可能导致难以复现的系统崩溃,调试起来非常困难。
- 性能关键点:
device_add和really_probe的执行路径直接影响系统启动速度和热插拔响应时间,是内核性能优化的重点和难点。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
core.c中的函数是底层API,驱动开发者通常不直接调用它们,而是调用特定总线或子系统提供的封装函数。但理解其场景至关重要。
所有设备驱动开发:无论是编写一个PCI网卡驱动、USB鼠标驱动还是一个嵌入式系统的I2C传感器驱动,其注册、探测、移除的整个生命周期都由core.c中的逻辑来驱动和管理。
- 例子:当你在驱动中调用
pci_register_driver()时,这个函数内部会将你的驱动结构注册到PCI总线,然后遍历总线上所有未被驱动的设备,依次调用driver_attach()来尝试绑定,最终触发你的probe函数。
内核子系统开发:任何需要管理一组虚拟或物理设备的内核子系统,都会基于core.c提供的struct device来进行构建。例如,Linux的输入子系统、块设备层、DRM(Direct Rendering Manager)图形子系统等。
是否有不推荐使用该技术的场景?为什么?
没有。在现代Linux内核中,任何需要被表示为一个“设备”的实体,都必须通过core.c提供的这套机制进行管理。绕过它意味着你将失去sysfs、电源管理、热插拔、驱动绑定等所有现代内核特性,这是不可接受的。
对比分析
请将其 与 其他相似技术 进行详细对比。
由于core.c是内核内部的核心实现,无法与另一个Linux技术对比。有意义的对比是将其代表的Linux设备模型核心实现与其他操作系统的驱动模型核心进行比较。
| 特性 |
Linux (drivers/base/core.c) |
Windows Driver Model (WDM) |
Apple macOS (I/O Kit) |
| 实现方式 |
C语言,过程式。设备和驱动是数据结构,通过函数指针回调进行交互。代码开源。 |
C语言,分层模型。通过I/O请求包(IRP)在驱动栈之间传递消息。 |
C++的子集,面向对象。设备和驱动都是对象,通过方法调用和消息传递交互。 |
| 绑定/探测 |
由core.c中的bus_probe_device和really_probe主动发起,直接调用驱动的probe函数。 |
由即插即用(PnP)管理器负责构建设备栈,并向各层驱动发送IRP_MN_START_DEVICE请求。 |
基于“个性字典”(personality dictionary)进行匹配。匹配成功后,I/O Kit会实例化驱动对象并调用其start方法。 |
| API/ABI |
无稳定内部API/ABI。驱动必须随内核一同编译。这使得core.c可以随时进行优化和重构。 |
提供稳定的二进制接口(ABI),理论上驱动可跨版本使用,但实践中常需更新。 |
提供相对稳定的C++ API,但大版本更新也常需驱动适配。 |
| 灵活性 |
极高。驱动可以直接访问内核的任何部分(这也带来了风险)。core.c的逻辑相对直接。 |
较高。分层过滤驱动模型提供了强大的扩展性,但IRP的流转也带来了开销和复杂性。 |
很高。面向对象的设计提供了优雅的抽象,但模型也相对复杂。 |
入门实践 (Hands-on Practice)
“可以提供一个简单的入门教程或关键命令列表吗?”
开发者不直接使用core.c的函数。实践的关键是理解它的影响,以及使用它的上层API。
关键函数(驱动开发者应知晓其存在和作用):
get_device(struct device *dev): 获取对一个设备的引用,增加其引用计数。当你需要在一个异步任务中或者在超出设备生命周期范围的地方安全地使用dev指针时,必须先调用此函数。
put_device(struct device *dev): 释放对设备的引用。必须与get_device成对出现,否则会导致资源泄漏。
dev_name(const struct device *dev): 安全地获取设备的名字。
dev_set_drvdata(struct device *dev, void *data) / dev_get_drvdata(struct device *dev): 在设备的probe函数中,将驱动的私有数据结构与struct device关联起来,方便在其他回调函数(如中断处理、remove)中获取。
“在初次使用时,有哪些常见的‘坑’或需要注意的配置细节?”
- 引用计数错误:这是最常见、最致命的错误。忘记
put_device会导致设备无法被移除,内存泄漏;错误地多调用put_device会导致悬空指针和use-after-free漏洞。
probe中的错误处理:probe函数中申请了多种资源(内存、中断、I/O等),如果在中途失败,必须将所有已成功申请的资源按申请的逆序精确释放。使用devm_*系列的资源管理函数可以极大地简化这一点。
probe返回-EPROBE_DEFER:当你的设备依赖的另一个设备(如一个时钟或电源regulator)尚未准备好时,probe函数应该返回-EPROBE_DEFER。core.c中的逻辑会捕获这个特定的错误码,并在稍后自动重试探测你的设备。如果不这样做,你的设备将永远无法工作。
- 并发与锁:
probe/remove函数和设备的sysfs属性回调函数可能在不同上下文中并发执行。必须使用锁来保护驱动的内部状态。
安全考量 (Security Aspects)
“使用这项技术时,需要注意哪些主要的安全风险?”
core.c本身是内核安全审查的重点,其代码被认为是高度可信的。安全风险主要体现在驱动程序如何与core.c提供的机制进行交互。
- Use-After-Free:由于引用计数错误或
remove与sysfs回调之间的竞态条件,驱动可能访问已经被core.c释放的struct device或其私有数据,这是最严重的安全漏洞之一。
- Sysfs 接口漏洞:虽然
sysfs文件的创建由core.c发起,但文件的内容和行为由驱动定义。驱动中的show/store回调函数如果存在缓冲区溢出或信息泄露,将直接导致内核漏洞。
- 竞态条件:在热插拔或驱动绑定/解绑的瞬间,是竞态条件的高发区。例如,一个
uevent已经发往用户空间,用户空间程序尝试操作设备,而此时remove函数正在清理资源,就可能导致崩溃。core.c使用device_hotplug_lock来序列化这些操作,但驱动内部仍需保持警惕。
“业界有哪些增强其安全性的最佳实践或辅助工具?”
- 使用
devm_*资源管理:这些函数将资源的生命周期与设备的生命周期绑定,在设备解绑时自动释放资源,极大地减少了错误处理代码和泄漏风险。
- 内核静态/动态分析:使用
sparse进行编译时检查,使用KASAN(Kernel Address Sanitizer)等动态工具来检测use-after-free和内存越界。
- 代码审查:遵循内核编码规范,对锁的使用、引用计数和错误处理路径进行严格的同行评审。
- Fuzzing:使用
syzkaller等工具对驱动的sysfs接口和ioctl进行模糊测试,以发现潜在漏洞。
生态系统与社区、性能与监控、未来趋势
这三个方面对于core.c来说,与整个设备模型是高度一致的。
- 生态系统与社区:
- 核心工具:
udev/systemd-udevd,sysfs文件系统。
- 社区:由内核驱动子系统维护者Greg Kroah-Hartman领导,在Linux内核邮件列表(LKML)上进行讨论。
- 性能与监控:
- 关键指标:设备探测时间,直接影响系统启动速度。
- 监控工具:
systemd-analyze,ftrace,perf。
- 调优技巧:核心是异步探测(Asynchronous Probing)。通过将驱动标记为可异步探测,可以让
core.c的调度逻辑将其与其他不相关的驱动并行初始化。
- 未来趋势:
- 发展方向:持续优化启动时间和热插拔性能,特别是在拥有数千个设备的服务器和虚拟化环境中。不断增强设备链接等依赖管理机制,以适应更复杂的硬件设计。
- 替代方案:没有。
core.c所代表的统一设备模型是Linux内核的基石,所有的发展都将是在此基础上的演进和增强。
总结
drivers/base/core.c是Linux设备模型的引擎和大脑。它不为任何特定类型的设备服务,而是为所有设备提供了一个统一的、强制性的管理框架。它负责设备的注册、驱动的绑定、生命周期的维护、sysfs的呈现以及与电源管理的交互。
关键特性总结:
- 集中化控制:所有设备生命周期中的关键节点都由
core.c调度。
- 标准执行流程:定义了标准的
probe/remove调用时机和顺序。
- 引用计数:通过
get/put_device提供了健壮的内存和资源管理基础。
- 性能优化:支持异步探测,是优化系统启动时间的关键。
学习该技术的要点建议:
- 从上层开始:不要一头扎进
core.c的源码。先熟练掌握如何使用一种总线(如platform或pci)编写驱动。
- 理解生命周期:清晰地画出设备从注册到注销,驱动从加载到卸载,两者之间
probe和remove被调用的完整流程图。
- 掌握核心API:深刻理解
devm_*系列函数、dev_get/set_drvdata和get/put_device的正确用法和必要性。
- 跟踪与调试:当你对上层API熟悉后,使用
ftrace等工具来跟踪一个设备注册的完整内核调用栈,看看它是如何一步步执行到core.c中的核心函数的。这是连接抽象概念和具体实现的桥梁。
dev_uevent_filter: 设备uevent事件过滤器
此函数作为一个过滤器, 用于在 uevent 事件发送前进行检查, 判断一个代表设备的 kobject 是否应该生成 uevent. 只有当设备关联到了一个总线(bus)或一个类别(class)时, 它才允许事件继续处理。如果一个设备既没有总线也没有类别, 那么它在 sysfs 中通常是孤立的, 也就没有必要向用户空间通知其状态变化。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
static int dev_uevent_filter(const struct kobject *kobj) {
const struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &device_ktype) {
const struct device *dev = kobj_to_dev(kobj);
if (dev->bus)
return 1;
if (dev->class)
return 1; }
return 0; }
|
dev_uevent_name: 获取设备uevent的子系统名称
此函数用于确定设备 uevent 事件中的 SUBSYSTEM 环境变量的值。它会检查设备所属的总线或类别, 并将它们的名称作为子系统名称返回。总线名称的优先级高于类别名称。这个子系统名称对于用户空间的 udev/mdev 规则匹配至关重要。
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 34
|
static const char *dev_uevent_name(const struct kobject *kobj) {
const struct device *dev = kobj_to_dev(kobj);
if (dev->bus)
return dev->bus->name;
if (dev->class)
return dev->class->name;
return NULL; }
|
dev_driver_uevent: 为设备uevent添加驱动程序信息
此函数负责向 uevent 的环境变量中添加 DRIVER=<driver_name> 键值对。由于设备的驱动绑定和解绑可能与 uevent 的发送产生并发竞争, 此函数必须小心处理。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
static void dev_driver_uevent(const struct device *dev, struct kobj_uevent_env *env) {
struct subsys_private *sp = bus_to_subsys(dev->bus);
if (sp) {
scoped_guard(spinlock, &sp->klist_drivers.k_lock) {
struct device_driver *drv = READ_ONCE(dev->driver);
if (drv)
add_uevent_var(env, "DRIVER=%s", drv->name); }
subsys_put(sp); } }
|
dev_uevent: 为设备uevent添加核心属性
此函数是设备 uevent 处理的核心, 作为 kset_uevent_ops.uevent 的实现。它负责将代表设备的最重要的一些属性添加到 uevent 环境变量中, 例如设备号(MAJOR, MINOR), 设备节点名(DEVNAME)和权限(DEVMODE)等。这些信息是用户空间 mdev 等工具创建 /dev 目录下设备节点的直接依据。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
static int dev_uevent(const struct kobject *kobj, struct kobj_uevent_env *env) {
const struct device *dev = kobj_to_dev(kobj);
int retval = 0;
if (MAJOR(dev->devt)) {
const char *tmp; const char *name; umode_t mode = 0; kuid_t uid = GLOBAL_ROOT_UID; kgid_t gid = GLOBAL_ROOT_GID;
add_uevent_var(env, "MAJOR=%u", MAJOR(dev->devt));
add_uevent_var(env, "MINOR=%u", MINOR(dev->devt));
name = device_get_devnode(dev, &mode, &uid, &gid, &tmp);
if (name) {
add_uevent_var(env, "DEVNAME=%s", name);
if (mode)
add_uevent_var(env, "DEVMODE=%#o", mode & 0777);
if (!uid_eq(uid, GLOBAL_ROOT_UID))
add_uevent_var(env, "DEVUID=%u", from_kuid(&init_user_ns, uid));
|
devices_init 设备初始化
- 此函数是内核设备模型(Driver Model)初始化的入口点之一。它的核心任务是在sysfs虚拟文件系统中,构建起用于表示和管理系统中所有设备所必需的基础目录结构。这个结构是现代Linux内核中驱动、设备和用户空间交互的基石。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| static const struct kset_uevent_ops device_uevent_ops = { .filter = dev_uevent_filter, .name = dev_uevent_name, .uevent = dev_uevent, };
int __init devices_init(void) { devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL); if (!devices_kset) return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL); if (!dev_kobj) goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj); if (!sysfs_dev_block_kobj) goto block_kobj_err; sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj); if (!sysfs_dev_char_kobj) goto char_kobj_err; device_link_wq = alloc_workqueue("device_link_wq", 0, 0); if (!device_link_wq) goto wq_err;
return 0;
wq_err: kobject_put(sysfs_dev_char_kobj); char_kobj_err: kobject_put(sysfs_dev_block_kobj); block_kobj_err: kobject_put(dev_kobj); dev_kobj_err: kset_unregister(devices_kset); return -ENOMEM; }
|
device_add 设备添加
- device_add 函数是 device_register 的第二步,也是核心步骤。它的主要作用是将一个已经通过 device_initialize 准备好的 struct device 对象正式添加到系统中。这个“添加”过程是多方面的,它包括:
- 命名设备: 为设备确定一个唯一的名称。
- 加入 sysfs: 在 /sys/devices/ 层次结构中为该设备创建对应的目录和属性文件,使其对用户空间可见。
- 建立链接: 将设备连接到其父设备、所属的总线和设备类。
- 通知系统: 向内核其他部分和用户空间(通过 uevent)宣告新设备的存在。
- 触发驱动探测: 最关键的一步,启动总线逻辑来为这个新设备寻找一个匹配的驱动程序并进行绑定(probe)。
- 一旦 device_add 成功返回,这个设备就被认为是“活的”(live),并完全参与到内核的设备管理、电源管理和驱动模型中
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
int device_add(struct device *dev) { struct subsys_private *sp; struct device *parent; struct kobject *kobj; struct class_interface *class_intf; int error = -EINVAL; struct kobject *glue_dir = NULL;
dev = get_device(dev); if (!dev) goto done;
if (!dev->p) { error = device_private_init(dev); if (error) goto done; }
if (dev->init_name) { error = dev_set_name(dev, "%s", dev->init_name); dev->init_name = NULL; } if (dev_name(dev)) { error = 0; } else if (dev->bus && dev->bus->dev_name) { error = dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id); } else { error = -EINVAL; } if (error) goto name_error;
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent); kobj = get_device_parent(dev, parent); if (IS_ERR(kobj)) { error = PTR_ERR(kobj); goto parent_error; } if (kobj) dev->kobj.parent = kobj;
if (parent && (dev_to_node(dev) == NUMA_NO_NODE)) set_dev_node(dev, dev_to_node(parent));
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); if (error) { glue_dir = kobj; goto Error; }
device_platform_notify(dev);
error = device_create_file(dev, &dev_attr_uevent); if (error) goto attrError;
error = device_add_class_symlinks(dev); if (error) goto SymlinkError; error = device_add_attrs(dev); if (error) goto AttrsError; error = bus_add_device(dev); if (error) goto BusError; if (error) goto DPMError;
if (MAJOR(dev->devt)) { error = device_create_file(dev, &dev_attr_dev); if (error) goto DevAttrError;
error = device_create_sys_dev_entry(dev); if (error) goto SysEntryError;
devtmpfs_create_node(dev); }
bus_notify(dev, BUS_NOTIFY_ADD_DEVICE); kobject_uevent(&dev->kobj, KOBJ_ADD);
if (dev->fwnode && !dev->fwnode->dev) { dev->fwnode->dev = dev; fw_devlink_link_device(dev); }
bus_probe_device(dev);
if (parent) klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children);
sp = class_to_subsys(dev->class); if (sp) { mutex_lock(&sp->mutex); klist_add_tail(&dev->p->knode_class, &sp->klist_devices);
list_for_each_entry(class_intf, &sp->interfaces, node) if (class_intf->add_dev) class_intf->add_dev(dev); mutex_unlock(&sp->mutex); subsys_put(sp); } done: put_device(dev); return error;
SysEntryError: if (MAJOR(dev->devt)) device_remove_file(dev, &dev_attr_dev); DevAttrError: device_pm_remove(dev); dpm_sysfs_remove(dev); DPMError: device_set_driver(dev, NULL); bus_remove_device(dev); BusError: device_remove_attrs(dev); AttrsError: device_remove_class_symlinks(dev); SymlinkError: device_remove_file(dev, &dev_attr_uevent); attrError: device_platform_notify_remove(dev); kobject_uevent(&dev->kobj, KOBJ_REMOVE); glue_dir = get_glue_dir(dev); kobject_del(&dev->kobj); Error: cleanup_glue_dir(dev, glue_dir); parent_error: put_device(parent); name_error: kfree(dev->p); dev->p = NULL; goto done; } EXPORT_SYMBOL_GPL(device_add);
|
device_register 设备注册
- device_initialize 函数的作用是对一个已经分配了内存的 struct device 结构体进行内部初始化。它并不将设备注册到内核或使其在 sysfs 中可见,而是为后续的注册和使用做准备。
可以将其理解为 device_register 的第一步。执行完此函数后,这个设备结构体就变成了一个功能完备的内核对象(kobject),拥有了引用计数机制和一系列被初始化的内部成员(如锁、链表等)。这使得其他内核子系统可以在该设备被正式“添加”到系统之前,就能安全地获取和释放对它的引用。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| static const struct kobj_type device_ktype = { .release = device_release, .sysfs_ops = &dev_sysfs_ops, .namespace = device_namespace, .get_ownership = device_get_ownership, };
void device_initialize(struct device *dev) {
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
INIT_LIST_HEAD(&dev->links.consumers); INIT_LIST_HEAD(&dev->links.suppliers); INIT_LIST_HEAD(&dev->links.defer_sync);
dev->links.status = DL_DEV_NO_DRIVER;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \ defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \ defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
dev->dma_coherent = dma_default_coherent; #endif
}
EXPORT_SYMBOL_GPL(device_initialize);
|
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 34 35 36 37
|
int device_register(struct device *dev) {
device_initialize(dev);
return device_add(dev); }
EXPORT_SYMBOL_GPL(device_register);
|
device_del & device_unregister: Linux设备模型的注销核心
本代码片段展示了Linux内核设备模型的心脏地带——一个设备(struct device)如何被系统性地、安全地“拆除”和注销。device_del函数是这个过程的第一阶段,负责将设备从所有相关的内核子系统(如总线、类、电源管理、sysfs等)中解耦和移除。device_unregister则是在此基础上封装的完整流程,它在调用device_del之后,通过put_device来递减设备的引用计数,从而最终触发设备的内存释放。
实现原理分析
设备注册(device_add)是一个复杂的“组装”过程,将一个设备与内核的方方面面联系起来。因此,设备注销必须是一个同样复杂、顺序严格的“拆解”过程,以确保系统的稳定性和资源的完全释放。
两阶段注销 (device_unregister):
- 第一阶段 (
device_del): “逻辑移除”。此阶段的目标是让设备对内核的其他部分不可见、不可访问。它会撤销device_add所做的一切:从总线列表中删除、从类列表中删除、从sysfs中删除、发送通知等。在此函数执行完毕后,虽然struct device的内存可能还存在(因为可能还有引用),但它在逻辑上已经与系统“失联”了。
- 第二阶段 (
put_device): “物理释放”。put_device递减设备的引用计数。当引用计数降为0时,设备模型的kobject核心会调用该设备的release回调函数,该函数最终会kfree掉struct device本身占用的内存。
device_del 的详细拆解步骤:
kill_device(dev): 这是一个关键的同步步骤。它获取设备的锁,并设置一个dead标志位。这个标志可以用来原子地通知其他可能并发的代码路径(如一个正在尝试探测此设备的驱动),该设备正在被移除,应立即停止任何操作。
bus_notify(dev, BUS_NOTIFY_DEL_DEVICE): 向总线上注册了通知回调的所有驱动程序发送一个“设备即将删除”的事件,让它们有机会在设备彻底消失前做一些清理工作。
- Sysfs 移除:
dpm_sysfs_remove, device_remove_sys_dev_entry, device_remove_file, device_remove_attrs等一系列调用,负责从/sys文件系统中逐步移除与该设备相关的所有目录和属性文件。
- 父子关系解绑:
klist_del(&dev->p->knode_parent)将其从父设备的子设备列表中移除。
- 类关系解绑: 它会遍历设备所属的类(class)上注册的所有接口(
class_interface),并调用它们的remove_dev回调(例如,之前分析过的alarmtimer_rtc_add_device就有对应的移除逻辑)。然后将设备从类的设备列表中移除。
- 总线关系解绑:
bus_remove_device(dev)会将其从所属总线(bus)的设备列表中移除,并尝试分离(detach)当前绑定在该设备上的驱动。
- 其他子系统解绑:
device_pm_remove(电源管理)、driver_deferred_probe_del(延迟探测)、device_links_purge(设备间链接)等,分别将其从各自的子系统中注销。
devres_release_all(dev): 非常重要。devres(设备资源管理)是一种自动化的资源释放机制。这一步会强制释放所有与该设备关联的、通过devm_*函数分配的资源(如devm_ioremap的内存、devm_clk_get的时钟句柄等)。这解决了即使设备没有驱动绑定时,资源也必须被释放的问题。
kobject_del(&dev->kobj): 这是最后一步,将设备的核心kobject从内核对象层次结构中移除。
代码分析
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
void put_device(struct device *dev) { if (dev)
kobject_put(&dev->kobj); } EXPORT_SYMBOL_GPL(put_device);
bool kill_device(struct device *dev) { device_lock_assert(dev);
if (dev->p->dead) return false; dev->p->dead = true; return true; } EXPORT_SYMBOL_GPL(kill_device);
void device_del(struct device *dev) {
device_lock(dev); kill_device(dev); device_unlock(dev);
bus_notify(dev, BUS_NOTIFY_DEL_DEVICE);
dpm_sysfs_remove(dev); if (parent) klist_del(&dev->p->knode_parent); if (MAJOR(dev->devt)) { devtmpfs_delete_node(dev); }
sp = class_to_subsys(dev->class); if (sp) {
mutex_lock(&sp->mutex); list_for_each_entry(class_intf, &sp->interfaces, node) if (class_intf->remove_dev) class_intf->remove_dev(dev); klist_del(&dev->p->knode_class); mutex_unlock(&sp->mutex); subsys_put(sp); } bus_remove_device(dev); device_pm_remove(dev); driver_deferred_probe_del(dev);
devres_release_all(dev);
bus_notify(dev, BUS_NOTIFY_REMOVED_DEVICE); kobject_uevent(&dev->kobj, KOBJ_REMOVE); kobject_del(&dev->kobj); put_device(parent); } EXPORT_SYMBOL_GPL(device_del);
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);
|
get_device_parent 被添加到系统中的设备 (dev) 确定其在 sysfs 文件系统中的父目
- get_device_parent 函数的核心作用是为即将被添加到系统中的设备 (dev) 确定其在 sysfs 文件系统中的父目录。这个父目录由一个 kobject(内核对象)表示
- 需要明确一个关键概念:设备的逻辑父子关系(由 dev->parent 指针定义)和它在 sysfs 中的目录结构并不总是一一对应的。get_device_parent 的任务就是根据设备的类型(是否属于一个类 class)、其逻辑父设备以及总线的规则,来智能地决定它在 /sys/devices/ 下应该挂载到哪个目录下。
- 其主要目的是为了创建一个清晰、无冲突、易于管理的 sysfs 命名空间。它通过创建所谓的“粘合目录”(glue directories) 来实现这一目标,避免了不同子系统之间的命名冲突。
- 对于属于 class 的设备:
- 它首先确定一个逻辑上的父 kobject (这可能是实际的父设备, 或是一个虚拟目录)。
- 然后, 它会在这个逻辑父 kobject 下查找或创建一个名为 “glue” (粘合) 的目录。这个 “glue” 目录的名称与设备的 class 名称相同, 作用是将所有属于同一个 class 的子设备组织在一起。
- 例如, 如果一个父设备下有多个 “net” 类的子设备, 此函数会确保它们都位于父设备的 net/ 子目录下。为了保证线程安全, 这个查找和创建过程由互斥锁 (mutex) 和自旋锁 (spinlock) 保护。
- 对于不属于 class 的设备:
- 如果设备属于一个总线 (bus) 并且没有指定父设备, 它会尝试使用该总线的根设备作为父设备。
- 如果明确指定了父设备 (parent), 则直接使用该父设备的 kobject 作为父对象。
- 如果以上条件都不满足, 则该设备没有父对象。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
static struct kobject *get_device_parent(struct device *dev, struct device *parent) { struct subsys_private *sp = class_to_subsys(dev->class); struct kobject *kobj = NULL;
if (sp) { struct kobject *parent_kobj; struct kobject *k;
if (parent == NULL) parent_kobj = virtual_device_parent(); else if (parent->class && !dev->class->ns_type) { subsys_put(sp); return &parent->kobj; } else { parent_kobj = &parent->kobj; }
mutex_lock(&gdp_mutex);
spin_lock(&sp->glue_dirs.list_lock); list_for_each_entry(k, &sp->glue_dirs.list, entry) if (k->parent == parent_kobj) { kobj = kobject_get(k); break; } spin_unlock(&sp->glue_dirs.list_lock);
if (kobj) { mutex_unlock(&gdp_mutex); subsys_put(sp); return kobj; }
k = class_dir_create_and_add(sp, parent_kobj); mutex_unlock(&gdp_mutex); subsys_put(sp); return k; }
if (!parent && dev->bus) { struct device *dev_root = bus_get_dev_root(dev->bus);
if (dev_root) { kobj = &dev_root->kobj; put_device(dev_root); return kobj; } }
if (parent) return &parent->kobj;
return NULL; }
|
virtual_device_parent (/sys/devices/virtual)
1 2 3 4 5 6 7 8 9 10
| struct kobject *virtual_device_parent(void) { static struct kobject *virtual_dir = NULL;
if (!virtual_dir) virtual_dir = kobject_create_and_add("virtual", &devices_kset->kobj);
return virtual_dir; }
|
devlink sysfs 属性文件
此代码片段定义了一组只读的sysfs属性文件, 用于从用户空间查询一个device_link实例的内部状态和配置标志。device_link是内核中用于表示两个设备之间依赖关系(一个“供应者”和一个“消费者”)的机制。这些sysfs文件使得开发者或管理工具可以方便地查看链接的状态、电源管理行为和生命周期策略。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf) {
const char *output;
switch (to_devlink(dev)->status) { case DL_STATE_NONE: output = "not tracked"; break; case DL_STATE_DORMANT: output = "dormant"; break; case DL_STATE_AVAILABLE: output = "available"; break; case DL_STATE_CONSUMER_PROBE: output = "consumer probing"; break; case DL_STATE_ACTIVE: output = "active"; break; case DL_STATE_SUPPLIER_UNBIND: output = "supplier unbinding"; break; default: output = "unknown"; break; }
return sysfs_emit(buf, "%s\n", output); }
static DEVICE_ATTR_RO(status);
static ssize_t auto_remove_on_show(struct device *dev, struct device_attribute *attr, char *buf) { struct device_link *link = to_devlink(dev); const char *output;
if (device_link_test(link, DL_FLAG_AUTOREMOVE_SUPPLIER)) output = "supplier unbind";
else if (device_link_test(link, DL_FLAG_AUTOREMOVE_CONSUMER)) output = "consumer unbind"; else output = "never";
return sysfs_emit(buf, "%s\n", output); }
static DEVICE_ATTR_RO(auto_remove_on);
static ssize_t runtime_pm_show(struct device *dev, struct device_attribute *attr, char *buf) { struct device_link *link = to_devlink(dev);
return sysfs_emit(buf, "%d\n", device_link_test(link, DL_FLAG_PM_RUNTIME)); }
static DEVICE_ATTR_RO(runtime_pm);
static ssize_t sync_state_only_show(struct device *dev, struct device_attribute *attr, char *buf) { struct device_link *link = to_devlink(dev);
return sysfs_emit(buf, "%d\n", device_link_test(link, DL_FLAG_SYNC_STATE_ONLY)); }
static DEVICE_ATTR_RO(sync_state_only);
static struct attribute *devlink_attrs[] = { &dev_attr_status.attr, &dev_attr_auto_remove_on.attr, &dev_attr_runtime_pm.attr, &dev_attr_sync_state_only.attr, NULL, };
ATTRIBUTE_GROUPS(devlink);
|
devlink_add_symlinks: 为devlink实例创建符号链接
此函数是一个回调函数, 在内核注册一个新的device_link实例时被调用。它的核心作用是在sysfs文件系统中创建一组共四个符号链接(symlinks), 用于清晰地展示一个“供应者”(supplier)设备和一个“消费者”(consumer)设备之间的依赖关系。这使得用户和系统工具可以方便地通过文件系统导航来查看和理解设备之间的连接。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
|
static int devlink_add_symlinks(struct device *dev) {
char *buf_con __free(kfree) = NULL, *buf_sup __free(kfree) = NULL;
int ret;
struct device_link *link = to_devlink(dev);
struct device *sup = link->supplier;
struct device *con = link->consumer;
ret = sysfs_create_link(&link->link_dev.kobj, &sup->kobj, "supplier"); if (ret) goto out;
ret = sysfs_create_link(&link->link_dev.kobj, &con->kobj, "consumer"); if (ret) goto err_con;
buf_con = kasprintf(GFP_KERNEL, "consumer:%s:%s", dev_bus_name(con), dev_name(con)); if (!buf_con) { ret = -ENOMEM; goto err_con_dev; }
ret = sysfs_create_link(&sup->kobj, &link->link_dev.kobj, buf_con); if (ret) goto err_con_dev;
buf_sup = kasprintf(GFP_KERNEL, "supplier:%s:%s", dev_bus_name(sup), dev_name(sup)); if (!buf_sup) { ret = -ENOMEM; goto err_sup_dev; }
ret = sysfs_create_link(&con->kobj, &link->link_dev.kobj, buf_sup); if (ret) goto err_sup_dev;
goto out;
err_sup_dev: sysfs_remove_link(&sup->kobj, buf_con); err_con_dev: sysfs_remove_link(&link->link_dev.kobj, "consumer"); err_con: sysfs_remove_link(&link->link_dev.kobj, "supplier"); out:
return ret; }
|
devlink_class_init: 注册 devlink 设备类和接口
此代码片段的作用是在内核中注册 devlink 设备类和其关联的类接口。这个过程会在sysfs中创建一个名为/sys/class/devlink/的目录, 并建立一个机制, 使得每当有设备被添加到这个devlink类时, 都会自动触发预定义的回调函数, 以执行诸如创建符号链接等操作。
devlink 是一个相对较新的内核框架, 旨在为各种复杂的网络设备 (如智能网卡、交换机芯片) 提供一个统一的、与具体总线无关的管理接口。它用于处理那些不适合放在传统网络驱动模型中的功能, 例如固件更新、设备诊断和资源报告等。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| static const struct class devlink_class = { .name = "devlink", .dev_groups = devlink_groups, .dev_release = devlink_dev_release, };
static struct class_interface devlink_class_intf = {
.class = &devlink_class,
.add_dev = devlink_add_symlinks,
.remove_dev = devlink_remove_symlinks, };
static int __init devlink_class_init(void) {
int ret;
ret = class_register(&devlink_class);
if (ret) return ret;
ret = class_interface_register(&devlink_class_intf);
if (ret)
class_unregister(&devlink_class);
return ret; }
postcore_initcall(devlink_class_init);
|
设备链接sync_state回调的延迟执行框架
此代码片段揭示了Linux内核设备模型中一个相当高级且精妙的内部机制: 设备链接sync_state回调的暂停、延迟和批量处理框架。它的核心作用是在系统进行大规模设备创建(例如, 在启动时从设备树填充平台设备)的阶段, 暂时”暂停”一个名为sync_state的设备状态同步回调的执行, 将所有本应触发的回调”延迟”并收集起来, 直到”暂停”状态结束后, 再对收集到的设备进行一次性的、批量的状态同步。
这个框架的根本原理是避免”回调风暴”(callback storm)并确保依赖关系完整性。sync_state回调函数通常在设备之间的依赖关系(即”链接”)建立或改变时被调用, 以便设备可以根据其”供应商”(supplier)的状态来调整自身。如果在of_platform_populate期间每创建一个设备链接就立即触发一次回调, 将会导致成百上千次低效的、可能是过早的函数调用。此框架通过引入”暂停/恢复”机制, 将这些回调合并成一次在更合适时机(通常是所有设备都已创建后)的批量执行, 从而极大地提高了启动效率和系统的健壮性。
核心组件与工作流程
1. device_links_supplier_sync_state_pause() / resume(): 全局暂停/恢复开关
这两个函数是该框架的主控制开关。of_platform_populate在开始工作前会调用pause(), 在结束后调用resume()。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
void device_links_supplier_sync_state_pause(void) { device_links_write_lock();
defer_sync_state_count++; device_links_write_unlock(); }
void device_links_supplier_sync_state_resume(void) { struct device *dev, *tmp; LIST_HEAD(sync_list);
device_links_write_lock(); if (!defer_sync_state_count) { WARN(true, "Unmatched sync_state pause/resume!"); goto out; } defer_sync_state_count--; if (defer_sync_state_count) goto out;
list_for_each_entry_safe(dev, tmp, &deferred_sync, links.defer_sync) {
list_del_init(&dev->links.defer_sync); __device_links_queue_sync_state(dev, &sync_list); } out: device_links_write_unlock();
device_links_flush_sync_list(&sync_list, NULL); }
|
2. __device_links_queue_sync_state: 状态同步的”排队”逻辑
此函数是一个过滤器和队列管理器。它负责判断一个设备当前是否满足被同步的条件, 如果满足, 就将其加入到一个待处理列表中。
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 34 35 36 37 38 39
|
static void __device_links_queue_sync_state(struct device *dev, struct list_head *list) { struct device_link *link;
if (!dev_has_sync_state(dev)) return; if (dev->state_synced) return;
list_for_each_entry(link, &dev->links.consumers, s_node) { if (!device_link_test(link, DL_FLAG_MANAGED)) continue; if (link->status != DL_STATE_ACTIVE) return; }
dev->state_synced = true;
if (WARN_ON(!list_empty(&dev->links.defer_sync))) return;
get_device(dev); list_add_tail(&dev->links.defer_sync, list); }
|
3. device_links_flush_sync_list: 批量执行回调
此函数是最终的执行者。它会遍历一个已准备就绪的设备列表, 并为它们一一调用sync_state回调。
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 34 35 36 37 38 39 40
|
static void device_links_flush_sync_list(struct list_head *list, struct device *dont_lock_dev) { struct device *dev, *tmp;
list_for_each_entry_safe(dev, tmp, list, links.defer_sync) { list_del_init(&dev->links.defer_sync);
if (dev != dont_lock_dev) device_lock(dev);
dev_sync_state(dev);
if (dev != dont_lock_dev) device_unlock(dev);
put_device(dev); } }
|
4. sync_state_resume_initcall: 最终的保险措施
这是一个late_initcall, 意味着它会在内核启动过程的非常后期被调用。它的作用是一个”保险”, 确保即使有任何pause()调用没有对应的resume()调用, 在启动的最后阶段, 所有被延迟的同步操作也一定会被执行一次。
1 2 3 4 5 6
| static int sync_state_resume_initcall(void) { device_links_supplier_sync_state_resume(); return 0; } late_initcall(sync_state_resume_initcall);
|
device_shutdown: 关闭系统中的所有设备
此函数是内核关机流程的核心组成部分, 位于kernel_shutdown_prepare之后。它的核心原理是以一种安全、健壮、且遵循依赖关系的方式, 遍历系统中所有已注册的设备, 并调用其驱动程序提供的.shutdown()回调函数, 以执行特定于硬件的最终关闭操作。
这个函数的设计体现了对健壮性的极致追求, 其关键机制如下:
- 同步与稳定化: 在进入主循环之前, 它首先调用
wait_for_device_probe()等待所有正在进行的设备探测完成, 然后调用device_block_probing()禁止任何新的设备探测。这确保了它即将处理的设备列表是一个稳定、不再增加的集合。
- 反向顺序遍历: 这是最关键的原则。函数从全局设备链表(
devices_kset->list)的尾部向前遍历。由于设备通常是按父子依赖顺序注册的(父设备先注册), 这种反向遍历天然地保证了子设备会在其父设备之前被关闭。例如, 一个USB存储设备会被在其所连接的USB集线器之前关闭, 而USB集线器又会在USB主控制器之前关闭。这个顺序对于避免硬件状态错误和数据损坏至关重要。
- 精妙的锁与引用计数管理: 为了在遍历一个全局链表的同时安全地执行可能休眠的
shutdown操作, 它采用了一种复杂的”锁-取-删-解锁-处理-重锁”模式。
- 它首先获取保护全局链表的自旋锁。
- 然后从链表中摘下一个设备, 并立即释放全局自旋锁。
- 在释放全局锁之前, 它通过
get_device()增加了该设备及其父设备的引用计数。这可以防止在处理当前设备时, 另一个线程(或中断)意外地移除并释放了它的父设备, 从而避免了悬空指针(use-after-free)错误。
- 接着, 它获取该设备及其父设备的私有互斥锁(
device_lock), 以防止与该设备自身的probe/release路径发生竞争。
- 在所有锁都就绪后, 它才安全地调用驱动的
.shutdown()方法。
- 处理完毕后, 它以相反的顺序释放所有锁和引用计数。
- 调用层级: 它会按照
class->shutdown_pre -> bus->shutdown 或 driver->shutdown的顺序尝试调用回调。这提供了一个分层的关闭机制, 允许从更通用(类别)到更具体(驱动)的层面执行清理操作。
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
void device_shutdown(void) { struct device *dev, *parent;
wait_for_device_probe(); device_block_probing();
cpufreq_suspend();
spin_lock(&devices_kset->list_lock);
while (!list_empty(&devices_kset->list)) {
dev = list_entry(devices_kset->list.prev, struct device, kobj.entry);
parent = get_device(dev->parent); get_device(dev);
list_del_init(&dev->kobj.entry);
spin_unlock(&devices_kset->list_lock);
if (parent) device_lock(parent); device_lock(dev);
pm_runtime_get_noresume(dev); pm_runtime_barrier(dev);
if (dev->class && dev->class->shutdown_pre) { if (initcall_debug) dev_info(dev, "shutdown_pre\n"); dev->class->shutdown_pre(dev); } if (dev->bus && dev->bus->shutdown) { if (initcall_debug) dev_info(dev, "shutdown\n"); dev->bus->shutdown(dev); } else if (dev->driver && dev->driver->shutdown) { if (initcall_debug) dev_info(dev, "shutdown\n"); dev->driver->shutdown(dev); }
device_unlock(dev); if (parent) device_unlock(parent);
put_device(dev); put_device(parent);
spin_lock(&devices_kset->list_lock); } spin_unlock(&devices_kset->list_lock); }
|