文章目录
- 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;//驱动核心的私有数据。驱动核心能访问。
};
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;
}
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);
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;
- (2)调用
kzalloc()
分配一个struct driver_private
内存空间:
- (3)调用
klist_init()
初始化设备链表klist_devices
:
klist_init(&priv->klist_devices, NULL, NULL);
- (4)设置驱动私有数据的
driver
和kobj.kset
字段,并将驱动私有数据设置到驱动程序的->p
字段:
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
- (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;
driver_ktype
定义如下:
static struct kobj_type driver_ktype = {
.sysfs_ops = &driver_sysfs_ops,
.release = driver_release,
};
-
(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/<bus_name>/drivers/<driver_name>/目录下,其中<bus_name>是设备所属的总线类型,<driver_name>是设备驱动程序的名称。 -
(10)调用
driver_add_groups()
将设备驱动程序的bus
的属性组添加到sysfs中,这些属性组将显示在/sys/bus/<bus_name>/drivers/<driver_name>/目录下,其中<bus_name>是设备所属的总线类型,<driver_name>是设备驱动程序的名称。
3、总结
driver_register()
是 Linux 内核中用于注册设备驱动程序的函数。它属于Linux设备模型的一部分,用于将驱动程序添加到内核的设备驱动程序列表中,以便内核可以与相应的硬件设备进行交互。在内核中的大部分驱动都会形成自己的驱动框架核心,例如:USB、i2c、spi等,这些驱动框架核心也一般都会封装出自己的注册函数,但是这些注册函数本质上都是调用driver_register()
实现驱动的注册。例如下图所示: