码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【linux kernel】linux内核设备驱动的注册机制


    文章目录

      • 1、简介
      • 2、driver_register分析
        • 👉(2-1)driver_find分析
        • 👉(2-2)bus_add_driver分析
      • 3、总结


    🔺【linux内核系列文章】

    👉对一些文章内容进行了勘误,本系列文章长期不定时更新,希望能分享出优质的文章!

    • 1、《linux内核数据结构分析之哈希表》
    • 2、《一文总结linux内核通知链》
    • 3、《linux内核中的debugfs》
    • 4、《linux内核数据结构分析之链表》
    • 5、《linux media子系统分析之media控制器设备》
    • 6、《V4L2-PCI驱动程序样例分析(上)》
    • 7、《v4l2框架分析之v4l2_fh》
    • 8、《 v4l2框架分析之v4l2_subdev》
    • 9、《 v4l2框架分析之v4l2_device》
    • 10、《v4l2框架分析之video_device》
    • 11、《linux内核重要函数 | do_initcalls》
    • 12、《Linux设备驱动模型 | bus》
    • 13、《linux内核裁剪随想》
    • 14、《基于ARM64分析linux内核的链接脚本vmlinux.lds.S》
    • 15、《linux内核start_kernel函数的早期操作》
    • 16、《start_kernel函数详解系列之proc_caches_init》
    • 17、《start_kernel函数详解系列之fork_init》
    • 18、《start_kernel函数详解系列之rcu_init》
    • 19、《start_kernel函数详解系列之proc_root_init》
    • 20、《start_kernel详解系列之【setup_arch】》
    • 21、《linux内核如何启动用户空间进程(上)》
    • 22、《linux内核如何启动用户空间进程(下)》
    • 23、《一文总结linux内核的完成量机制》
    • 24、《一文总结linux内核设备驱动的注册和卸载》
    • 25、《linux内核的启动加载程序的总结》
    • 26、《linux内核入口:head.o》
    • 27、《挂载根文件系统之rootfs》
    • 28、《mount系统调用剖析》
    • 29、《devtmpfs文件系统分析》
    • 30、《linux内核的kthreadd线程》
    • 31、《linux内核的进程调度—调度策略》
    • 32、《linux系统调用实践(Arm架构)》
    • 33、《对linux内核__init机制的实践》
    • 34、《linux 内核中EXPORT_SYMBOL()分析与实践》
    • 35、《linux内核如何挂载根文件系统》
    • 36、《linux内核如何唤醒线程》
    • 37、《linux内核的init线程》
    • 38、《linux内核伪文件系统—sysfs分析》
    • 39、《linux 内核设备模型的初始化(上)》
    • 40、《linux 内核设备模型的初始化(下)》
    • 41、《linux内核伪文件系统—proc分析》
    • 42、《linux中断管理—workqueue工作队列》
    • 43、《linux中断管理—软中断》
    • 44、《linux中断管理 | tasklet》
    • 45、《linux中断管理 | 中断管理框架(01)》
    • 46、《linux内存管理 | 分配物理内存页面》
    • 47、《linux内存管理 | 释放内存页面》
    • 48、《对linux内核设备的注册机制和查找机制分析》

    相关文件:
    /drivers/base/driver.c

    1、简介

    向linux内核注册驱动由driver_register()完成。它将驱动程序的信息添加到内核的驱动程序列表中,使得内核能够在需要时与该驱动程序进行交互。

    当调用driver_register()函数时,内核会将驱动程序添加到内核驱动程序列表中,并在需要时使用该驱动程序来匹配和初始化设备。驱动程序的探测函数(probe)将在设备与驱动程序匹配时调用,以便进行设备的初始化。移除函数(remove)将在设备从系统中移除时调用,以进行相关的清理操作。

    通过调用driver_register()函数,驱动程序可以将自身注册到内核中,从而使得内核能够管理和与驱动程序进行交互。这为设备的探测、初始化、配置和移除提供了必要的框架和支持。

    内核中,几乎所有的驱动子系统都会以该函数进行封装出对应驱动的注册函数,例如PCI驱动,在/drivers/pic/pci-driver.c文件中会调用该函数:

    再比如对应i2c设备驱动,在i2c驱动框架下的/driver/i2c/i2c-core.c文件中有如下代码:

    综上可知,driver_register()在几乎所有的驱动子系统中都会使用到。兜兜转转,最后都会调用到该函数。

    2、driver_register分析

    在Linux内核中,struct device_driver结构体用于表示一个设备驱动程序。它包含了驱动程序的相关信息,如名称、总线类型、探测函数、移除函数等,用于与设备进行匹配、初始化和清理操作。struct device_driver结构体提供了设备驱动程序的基本信息和回调函数,用于与设备进行匹配、初始化、清理和管理。通过使用该结构体,驱动程序能够在设备与驱动程序匹配时进行初始化操作,并在设备移除或系统关机时进行相应的清理操作。此外,还可以通过属性组和电源管理操作等扩展功能来增强驱动程序的功能和灵活性。该结构定义如下:

    struct device_driver {
    	const char		*name; //
    	struct bus_type		*bus;//驱动程序所属的总线类型。
    
    	struct module		*owner; //模块拥有者
    	const char		*mod_name;	//在构建内建模块时使用
    
    	bool suppress_bind_attrs;	//是否禁用通过sysfs bound/unbound操作
    
    	const struct of_device_id	*of_match_table; //设备树匹配表
    	const struct acpi_device_id	*acpi_match_table; //ACPI匹配表。
    
    	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;// 驱动程序的电源管理操作,用于控制设备的电源管理。
    
    	struct driver_private *p;//驱动核心的私有数据。驱动核心能访问。
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    driver_register函数用与向设备驱动模型注册一个设备驱动,实现在/drivers/base/driver.c文件中:

    int driver_register(struct device_driver *drv)
    {
    	int ret;
    	struct device_driver *other;
    
    	BUG_ON(!drv->bus->p);
    
    	if ((drv->bus->probe && drv->probe) ||
    	    (drv->bus->remove && drv->remove) ||
    	    (drv->bus->shutdown && drv->shutdown))
    		printk(KERN_WARNING "Driver '%s' needs updating - please use "
    			"bus_type methods\n", drv->name);
    
    	other = driver_find(drv->name, drv->bus);
    	if (other) {
    		printk(KERN_ERR "Error: Driver '%s' is already registered, "
    			"aborting...\n", drv->name);
    		return -EBUSY;
    	}
    
    	ret = bus_add_driver(drv);
    	if (ret)
    		return ret;
    	ret = driver_add_groups(drv, drv->groups);
    	if (ret) {
    		bus_remove_driver(drv);
    		return ret;
    	}
    	kobject_uevent(&drv->p->kobj, KOBJ_ADD);
    
    	return ret;
    }
    
    • 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

    driver_register()具体执行流程如下:

    • (1)调用driver_find()通过名字找到bus上的driver。

    • (2)调用bus_add_driver()添加一个driver到bus。

    • (3)调用driver_add_groups()将属性组(attribute_group)添加到驱动程序中。

    • (4)调用kobject_uevent()触发内核KOBJ_ADD事件,用于向用户空间发送KOBJ_ADD事件通知。

    🔺下文将展开driver_find()和bus_add_driver()进行分析。

    👉(2-1)driver_find分析

    该函数接收两个参数:

    • (1)name:驱动程序的名称。

    • (2)bus:待被扫描的bus。

    函数实现如下:

    调用kset_find_obj()根据name寻找是否有kobject,如果找到了,则使用to_driver()返解出struct driver_private,然后将driver_private->driver作为参数返回;否则返回NULL。

    该行代码中:

    struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);
    
    • 1

    bus->p->drivers_kset本质是struct kset,struct kset是linux内核对象的集合,添加的设备驱动将作为内核对象添加到对应的kset集合中。kset_find_obj()则用于在kset中搜索出对应name的内核对象,该函数实现如下:

    回到driver_register()中,如果driver_find()找到了对应的device_driver,则证明该设备驱动已经注册过了,这时候则返回退出driver_register();否则继续执行后续的bus_add_driver()操作。

    👉(2-2)bus_add_driver分析

    bus_add_driver()实现在/drivers/base/bus.c中,将执行下列具体的逻辑:

    • (1)从drv->bus中解析出bus,如果bus为NULL,则返回退出bus_add_driver:
    	bus = bus_get(drv->bus);
    	if (!bus)
    		return -EINVAL;
    
    • 1
    • 2
    • 3
    • (2)调用kzalloc()分配一个struct driver_private内存空间:

    • (3)调用klist_init()初始化设备链表klist_devices:
    klist_init(&priv->klist_devices, NULL, NULL);
    
    • 1
    • (4)设置驱动私有数据的driver和kobj.kset字段,并将驱动私有数据设置到驱动程序的->p字段:
    	priv->driver = drv;
    	drv->p = priv;
    	priv->kobj.kset = bus->p->drivers_kset;
    
    • 1
    • 2
    • 3
    • (5)调用kobject_init_and_add()初始化设备私有数据中的内核对象,并指定驱动类型driver_ktype:
    	error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
    				     "%s", drv->name);
    	if (error)
    		goto out_unregister;
    
    • 1
    • 2
    • 3
    • 4

    driver_ktype定义如下:

    static struct kobj_type driver_ktype = {
    	.sysfs_ops	= &driver_sysfs_ops,
    	.release	= driver_release,
    };
    
    • 1
    • 2
    • 3
    • 4
    • (6)使用klist_add_tail()将priv->knode_bus节点添加到bus->p->klist_drivers总线的驱动链表中。

    • (7)如果设置了驱动所属的bus的drivers_autoprobe,则调用drvier_attach()尝试将驱动程序绑定到设备:

    • (8)使用moudle_add_drvier将设备驱动程序添加到内核模块系统中,使之可以与设备进行关联。通过调用这个函数,可以将设备驱动程序注册到内核的设备驱动程序列表中,以便在设备被发现时自动加载并与之匹配。

    • (9)调用driver_create_file()为设备驱动创建sysfs中的属性文件。该文件将显示在/sys/bus//drivers//目录下,其中是设备所属的总线类型,是设备驱动程序的名称。

    • (10)调用driver_add_groups()将设备驱动程序的bus的属性组添加到sysfs中,这些属性组将显示在/sys/bus//drivers//目录下,其中是设备所属的总线类型,是设备驱动程序的名称。

    3、总结

    driver_register()是 Linux 内核中用于注册设备驱动程序的函数。它属于Linux设备模型的一部分,用于将驱动程序添加到内核的设备驱动程序列表中,以便内核可以与相应的硬件设备进行交互。在内核中的大部分驱动都会形成自己的驱动框架核心,例如:USB、i2c、spi等,这些驱动框架核心也一般都会封装出自己的注册函数,但是这些注册函数本质上都是调用driver_register()实现驱动的注册。例如下图所示:

  • 相关阅读:
    springboot215基于springboot技术的美食烹饪互动平台的设计与实现
    ubuntu16.04 安装搜狗输入法
    SparkStreaming入门
    即兴发言怎样避免语无伦次?记住这个即兴发言万能公式
    P1443 马的遍历(超详解)
    分拆计划陷入困境,英特尔还能重回巅峰吗?
    C++基础知识要点--函数(Primer C++ 第五版 · 阅读笔记)
    芯科蓝牙BG27开发笔记1-新建示例工程
    虚拟机(三)VMware Workstation 桥接模式下无法上网
    Zemax操作38--POP(物理光学传播)的用法
  • 原文地址:https://blog.csdn.net/iriczhao/article/details/133843123
  • 最新文章
  • 【JVM】编译执行与解释执行的区别是什么?JVM 使用哪种方式?
    用 Hashids 优雅解决 C 端自增 ID 暴露问题
    V8引擎 精品漫游指南--Ignition篇(上) 指令 栈帧 槽位 调用约定 内存布局 基础内容
    LLVM Pass快速入门(四):代码插桩
    milkup:桌面端 markdown AI续写和即时渲染
    基于项目工程构建SBOM(软件物料清单)的研究
    鸿蒙应用开发UI基础第二节:鸿蒙应用程序框架核心解析与实操
    .NET 中如何快速实现 List 集合去重?
    扣子Coze实战:从0到1打造抖音+小红书热点监控智能体
    浅谈数据访问层
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
小工具 小游戏
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

京公网安备 11010502049817号