【linux kernel】linux内核设备驱动的注册机制


🔺【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 ksetstruct 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)设置驱动私有数据的driverkobj.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()实现驱动的注册。例如下图所示:

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux默认内核是由Linus Torvalds开发的Linux内核。要进行内核配置,可以使用make menuconfig或make config命令进行交互式配置,也可以使用make defconfig命令使用默认配置。以下是更详细的步骤: 1. 下载Linux内核源代码,并解压缩。 2. 进入内核源代码目录,运行make menuconfig命令,进入交互式配置界面。 3. 在界面中,可以选择不同的内核配置选项。例如,选择“General setup”选项,可以配置内核的基本设置,如内核版本号、支持的处理器架构、系统时间等。 4. 选择“Processor type and features”选项,可以配置内核的处理器类型和特性,如是否支持超线程、是否支持SSE指令集等。 5. 选择“Networking support”选项,可以配置内核对网络的支持,如是否启用IPv6、是否支持网络设备驱动等。 6. 选择“File systems”选项,可以配置内核对文件系统的支持,如是否支持ext4、NTFS等。 7. 配置完成后,可以保存配置并退出。此时,内核配置文件会被保存为.config文件。 8. 运行make命令,编译内核。编译完成后,会生成一个内核镜像文件。 9. 将内核镜像文件拷贝到/boot目录下。 10. 配置引导程序,使其启动新的内核。具体步骤因引导程序而异,这里不再赘述。 以上是Linux内核配置的基本步骤。需要注意的是,配置内核时需要根据实际需求进行选择,不要选择过多或不必要的功能,以免浪费系统资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值