linux下设备device_register和驱动driver_register先后注册的影响和关系

本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。

欢迎和大家交流。qq:1037701636 email:200803090209@zjut.comgzzaigcn2012@gmail.com

 

开始我的博文之前,最近一天来的感受,我只想说一下,Linux的世界,玩在表面是轻松的,但是玩在内部那就是找死。佩服那些写源码的人。真的佩服。

 

 这段世界大概半个月了,开始linux驱动的真正开发,也是从基层开始看的。相比以为linux下的驱动就是点灯之类的Led来说,现在我的感受就是,那些完全就是表面的东西。核心层次的驱动,完全不是这么随随便便写出来的。也正好说明现在很多公司正在写驱动的不多,完全独立写的也不多,因为实在太复杂。只能靠着源码或者BSP来修改。半个多月来看了linux2.6.10下面的音频驱动OSS架构,不是一般的复杂。熟悉了I2C驱动的整个架构(之前写了一下博文)。

     今天为何再写这个内容,因为最近开始看linux下面的视频架构。恍惚间看到device_register和driver_register,这两个对驱动真正核心的东西,让我察觉到这两个到底谁需要先执行,还是没关系。百度了很多,都说的是表面文章,去qq上也是一肚子的灰,因此我依旧独立解决,虽然我知道这个问题基本没人去思考(除了写内核的大神们),因为这块内容和内核走的很近很近,代码量也大。所以基本可见的分析都在表面。故把我这次的分析总结写在这里,供大家借鉴,不对之处也请多指正。

      从函数driver_register和device_register在源码中来看,这两个函数的执行顺序前后都有出现,但是之前都活在表面,没有深入的看过,因为知道内核在深入下去就是很复杂的东西。但为了解决问题只会硬着头皮去看。下面是我的一些分析,主要涉及到的是链表,kobject,kset,bus,device,device_driver几个结构体。

1. 先从driver_register的调用流程说起,主要介绍核心的调用,其间的某些函数不做解析。

driver_register->bus_add_driver->

int bus_add_driver(struct device_driver * drv)
{
	struct bus_type * bus = get_bus(drv->bus);
	int error = 0;

	if (bus) {
		pr_debug("bus %s: add driver %s\n", bus->name, drv->name);
		error = kobject_set_name(&drv->kobj, "%s", drv->name);  //给kobj赋予名字
		if (error) {
			put_bus(bus);
			return error;
		}
		drv->kobj.kset = &bus->drivers;  //kset指向bus->drivers(类型为kset)
		if ((error = kobject_register(&drv->kobj))) {
			put_bus(bus);
			return error;
		}

		down_read(&bus->subsys.rwsem);
		driver_attach(drv);
		up_read(&bus->subsys.rwsem);
		module_add_driver(drv->owner, drv);

		driver_add_attrs(bus, drv);
	}
	return error;
}

在这个函数中,我觉得核心的是语句 drv->kobj.kset = &bus->drivers;,为何会这样说。因为实际上在设备和驱动在注册的过程中,都会将自己结构体的链表成员添加到bus->drivers和bus->devices为链表头的链表中去。最后都根据这些链表遍历所在成员的地址,然后找到设备和驱动依次执行是否probe。

所以 drv->kobj.kset = &bus->drivers;这个做的事情需要结合函数 kobject_register->kobject_add函数中去

static void unlink(struct kobject * kobj)
{
	if (kobj->kset) {
		down_write(&kobj->kset->subsys->rwsem);
		list_del_init(&kobj->entry);
		up_write(&kobj->kset->subsys->rwsem);
	}
	kobject_put(kobj);
}

/**
 *	kobject_add - add an object to the hierarchy.
 *	@kobj:	object.
 */

int kobject_add(struct kobject * kobj)
{
	int error = 0;
	struct kobject * parent;

	if (!(kobj = kobject_get(kobj)))
		return -ENOENT;
	if (!kobj->k_name)
		kobj->k_name = kobj->name;
	parent = kobject_get(kobj->parent);

	pr_debug("kobject %s: registering. parent: %s, set: %s\n",
		 kobject_name(kobj), parent ? kobject_name(parent) : "<NULL>", 
		 kobj->kset ? kobj->kset->kobj.name : "<NULL>" );

	if (kobj->kset) {
		down_write(&kobj->kset->subsys->rwsem);

		if (!parent)
			parent = kobject_get(&kobj->kset->kobj);

		list_add_tail(&kobj->entry,&kobj->kset->list);  //kobj的list_head内容加到bus->drivers的list链表中
		up_write(&kobj->kset->subsys->rwsem);
	}
	kobj->parent = parent;

	error = create_dir(kobj);
	if (error) {
		/* unlink does the kobject_put() for us */
		unlink(kobj);
		if (parent)
			kobject_put(parent);
	} else {
		kobject_hotplug(kobj, KOBJ_ADD);
	}

	return error;
}

在这里有这么一个list_add_tail宏来完成device_driver中将成员kobject的kset链表头加入到kobj->kset->list中去,实际是drv->kobj.kset = &bus->drivers,也就是加入到链表头bus->drivers->list中去。这样就顺利完成了在设备注册时,可以遍历到这个链表头下面的驱动。

总结一下的话,其实devices和driver在注册中,最终都会挂接到bus下面,类型是kset(device和driver)下的list。最终都去遍历这个链表头下面对应的驱动和设备。而我们知道一个驱动可以有多个设备,因此device下的driver_list会添加到driver下面的链表头中去。

 

相对于driver_register的实现,device_register这方面的实现,就简单多了,一句list_add_tail(&dev->bus_list, &dev->bus->devices.list);    //将设备的bus_list加入到devoces.list的链表头中,这个简单了很多很多。很明了,而不需要像驱动那样将kobj下面的链表加入到Bus下面去用于遍历。

         总之,这个问题很小,但是和内核走的很近很近,所以,遇到这个问题我想必须解决,这样可以带给我对驱动很深入的理解。继续努力,走在linux的世界里


 

     

<think>我们正在讨论的是platform_driver_register函数在注册设备驱动时的具体过程方法。根据用户提供的引用,特别是引用[5]中的代码片段,我们可以详细分析注册过程。 首先,platform_driver_register函数用于注册一个平台驱动(platform_driver)。平台驱动用于匹配平台设备(platform_device),这些设备通常是在系统启动时由板级支持包(BSP)或者通过设备树(Device Tree)静态定义的,或者由内核在发现硬件时动态创建。 注册过程的关键步骤(基于引用[5]): 1. 定义一个platform_driver结构体,其中包含probe、remove等重要函数指针,以及driver结构体(内含名称、设备树匹配表等)。 2. 调用platform_driver_register宏(实际上是__platform_driver_register函数)来注册这个驱动。 在__platform_driver_register函数中,主要完成以下操作: - 设置driver结构体的owner字段(通常为THIS_MODULE)。 - 将driver的bus字段设为platform_bus_type,表示该驱动属于平台总线。 - 设置driver的probe、removeshutdown函数为平台总线提供的通用函数(这些函数内部会调用我们定义的驱动中的probe等)。 - 最后调用driver_register函数将驱动注册到总线。 当驱动注册到总线(这里是平台总线)后,总线会遍历所有已经注册到该总线的设备,尝试将设备驱动进行匹配。匹配过程是通过比较驱动设备的名字,或者比较设备树中的兼容性字符串(如果有设备树)等。如果匹配成功,则会调用驱动的probe函数。 下面详细说明各个步骤: ### 1. 定义platform_driver结构体 例如: ```c static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "my_device", .of_match_table = my_of_match_table, // 设备树匹配表 }, }; ``` 其中,`my_probe``my_remove`是驱动开发者实现的函数,分别在设备匹配成功移除时调用。`of_match_table`用于设备树匹配,它是一个`of_device_id`数组。 ### 2. 注册驱动 调用`platform_driver_register(&my_driver);` ### 3. 注册过程内部细节(引用[5]的展开) ```c int __platform_driver_register(struct platform_driver *drv, struct module *owner) { drv->driver.owner = owner; drv->driver.bus = &platform_bus_type; // 绑定到平台总线 // 设置总线的通用probe函数,这个函数内部会调用我们定义的drv->probe drv->driver.probe = platform_drv_probe; drv->driver.remove = platform_drv_remove; drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } ``` ### 4. 驱动的匹配probe调用 当调用`driver_register`后,总线会执行匹配操作(如引用[1]所述:`bus_add_driver` -> `driver_attach` -> `bus_for_each_dev`)。对于平台总线,匹配函数是`platform_match`(定义在`drivers/base/platform.c`中)。该函数会尝试以下几种匹配方式: - 基于设备树的匹配:如果设备驱动都提供了设备树兼容性字符串,则比较这些字符串(通过`of_driver_match_device`函数)。 - 基于ACPI的匹配。 - 基于ID表的匹配(传统的平台设备名称匹配)。 如果匹配成功,总线会调用`driver.probe`,即上面设置的`platform_drv_probe`,该函数内部会调用我们实际定义的`platform_driver`结构体中的`probe`函数(即`my_probe`)。 ### 5. 设备树匹配表 设备树匹配表是一个`of_device_id`数组,例如: ```c static const struct of_device_id my_of_match_table[] = { { .compatible = "vendor,my-device" }, { } // 结束标志 }; MODULE_DEVICE_TABLE(of, my_of_match_table); ``` 当设备树中某个节点的`compatible`属性与这个表中的某一项匹配时,驱动就会被选中。 ### 6. 传统匹配(无设备树) 在没有设备树的情况下,平台设备通常是静态定义的,例如: ```c static struct platform_device my_device = { .name = "my_device", .id = -1, }; ``` 然后在驱动中,我们只需要在platform_driverdriver.name中设置为相同的名字(如"my_device"),即可匹配。 ### 总结注册过程 1. 定义platform_driver结构体,包括probe、remove等函数,以及driver成员(nameof_match_table等)。 2. 调用platform_driver_register注册驱动。 3. 注册过程中,将驱动绑定到平台总线,并设置总线操作函数。 4. 驱动注册到总线后,总线开始匹配设备驱动。 5. 匹配成功后,调用驱动的probe函数。 ### 示例代码 ```c #include <linux/module.h> #include <linux/platform_device.h> static int my_probe(struct platform_device *pdev) { printk(KERN_INFO "Probe function called\n"); // 初始化设备 return 0; } static int my_remove(struct platform_device *pdev) { printk(KERN_INFO "Remove function called\n"); return 0; } static const struct of_device_id my_of_match[] = { { .compatible = "vendor,my-device", }, { } }; MODULE_DEVICE_TABLE(of, my_of_match); static struct platform_driver my_driver = { .driver = { .name = "my_driver", .of_match_table = my_of_match, }, .probe = my_probe, .remove = my_remove, }; module_platform_driver(my_driver); MODULE_LICENSE("GPL"); ``` 注意:在实际开发中,我们经常使用`module_platform_driver`宏来简化注册注销操作,它内部会自动调用`platform_driver_register``platform_driver_unregister`。 ### 相关问题 根据用户的问题我们的回答,可以提出以下相关问题: §§相关问题§§ 1. 如何定义平台设备(platform_device)? 2. 设备树中的节点如何与平台驱动匹配? 3. 平台总线(platform_bus)在内核中是如何工作的? 4. 在平台驱动中,probe函数的作用是什么? </think>### `platform_driver_register` 注册设备驱动的详细过程 #### 一、核心作用 `platform_driver_register()` 是 Linux 内核中用于**注册平台驱动**的关键函数,主要实现: 1. **设备-驱动绑定**:将驱动关联到平台总线(`platform_bus_type`)[^2][^5] 2. **匹配机制**:通过设备树(DTS)或传统 ID 表匹配硬件设备 3. **生命周期管理**:注册驱动的探测(probe)、移除(remove)等回调函数 #### 二、驱动注册流程 ```mermaid graph TD A[platform_driver_register] --> B[__platform_driver_register] B --> C[设置driver.bus=platform_bus_type] C --> D[绑定总线操作函数] D --> E[driver_register注册到内核] E --> F[bus_add_driver扫描设备] F --> G{匹配设备?} G -- 是 --> H[调用probe函数] G -- 否 --> I[等待设备出现] ``` (流程图说明驱动注册的核心过程[^1][^5]) #### 三、具体实现步骤 ##### 1. 定义平台驱动结构体 ```c static struct platform_driver my_driver = { .probe = my_probe, // 设备匹配时调用 .remove = my_remove, // 设备移除时调用 .driver = { .name = "my_device", // 驱动名称 .of_match_table = dt_ids, // 设备树匹配表 }, }; ``` 关键字段说明: - **probe**:设备匹配时的初始化函数 - **name**:传统匹配方式使用的设备名 - **of_match_table**:设备树匹配表(DTS compatible 字符串) ##### 2. 调用注册函数 ```c // 注册驱动到平台总线 int ret = platform_driver_register(&my_driver); if (ret) pr_err("Driver registration failed\n"); ``` ##### 3. 匹配设备过程 当驱动注册时,内核执行: 1. 将驱动关联到 `platform_bus_type` 总线[^5] 2. 触发总线扫描(`bus_for_each_dev`): - 比较 `.name` 与 `platform_device.name` - 或匹配设备树节点的 `compatible` 属性 3. 匹配成功则调用 `probe()` 函数[^1] #### 四、设备树(DTS)匹配流程 ```c /* 1. 定义设备树匹配表 */ static const struct of_device_id dt_ids[] = { { .compatible = "vendor,device123" }, // 匹配DTS compatible { } }; /* 2. 注册驱动结构体 */ .driver = { .of_match_table = dt_ids, } /* 3. 触发probe的条件 */ // DTS中有:device123: device@0 { compatible = "vendor,device123"; }; ``` 匹配优先级:**设备树 > 传统名称匹配**[^3] #### 五、与传统设备的区别 | 特性 | 平台驱动 | 传统驱动(如PCI/USB) | |---------------------|----------------------------------|--------------------------| | **总线类型** | 虚拟平台总线 | 物理总线(PCI/USB等) | | **设备发现** | 静态定义或设备树 | 硬件枚举 | | **典型应用** | SoC外设(GPIO/I2C等) | 扩展卡设备 | | **注册函数** | `platform_driver_register()` | `pci_register_driver()`等 | #### 六、完整代码示例 ```c #include <linux/module.h> #include <linux/platform_device.h> static int my_probe(struct platform_device *pdev) { printk("Device detected! Initializing...\n"); // 初始化硬件、注册字符设备等 return 0; } static int my_remove(struct platform_device *pdev) { printk("Removing device driver\n"); // 释放资源 return 0; } static const struct of_device_id dt_ids[] = { { .compatible = "mycorp,mydevice" }, { } }; MODULE_DEVICE_TABLE(of, dt_ids); static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "my_platform_drv", .of_match_table = dt_ids, }, }; module_platform_driver(my_driver); // 自动注册/注销 MODULE_LICENSE("GPL"); ``` #### 七、最佳实践 1. **优先使用设备树**:现代嵌入式系统首选匹配方式[^3] 2. **资源获取**:在 `probe()` 中使用 `platform_get_resource()` 获取设备资源 3. **错误处理**:确保 `probe()` 失败时释放所有资源 4. **模块宏**:推荐使用 `module_platform_driver()` 自动处理注册/注销 > 当匹配到 compatible="vendor,device123" 的设备树节点时,内核自动调用 `my_probe()` 初始化设备[^3][^5]。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值