总述

Linux 系统下的驱动最后都是以如下这个结构体呈现在系统中的,注意其中的dev_pm_ops是内核新增的内容来准备替换platform_driver中的电源管理相关的内容。这里内容是先进行总体的流程梳理后面再结合Linux内核代码的内容来学习。

struct device_driver {
const char *name;
struct bus_type *bus; /*Linux 下驱动都是应该挂在总线地下的*/ struct module *owner;
const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* 如果他为1 则取消暴露到文件系统中的绑定相关操作接口 */ 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和driver_unregister两个接口常使用以完成驱动的注册和卸载。

驱动注册流程

这里先进行总体的执行过程分析,后续参考源码进行分析(后面有时间会补上),3.x的内核和4.0的内核区别会有一些但是不是很大。依然有参考意义

1、driver_register
1、驱动如果有probe函数并且总线也有就需要出一个警告,
要求使用总线提供的方法(也就是说以总线的探测接口优先)
2、driver_find 检查目标总线上是否已经有同名驱动
(因为驱动和设备的match有时是靠名字进行匹配的)
3、bus_add_driver(drv)
1、bus_get() 其实kobject.refcnt++
2、申请分配驱动私有管理数据结构内存并和驱动双向绑定
这部分私有数据用于驱动管理驱动上的设备和所属总线等逻辑      
主要是klist_devices和kobj相关的。
3、kobject_init_and_add 主要是kobject相关调用
4、klist_add_tail 将驱动添加到bus的klist中去
5、driver_attach(drv);/*驱动和设备的匹配 新内核新增的新异步匹配
机制这里和旧内核有区别 */
1、bus_for_each_dev(bus,device_start,data,fn(struct device*,void))
这个函数就是会遍历总线上的设备依次执行其中的fn传入的是接口函数
__driver_attach这一部分处理和设备的注册十分相似
2、遍历bus上的klist_devices 依次__driver_attach(dev,drv)进行匹配
1、__driver_attach
1、如果驱动总线的mach函数存则在调用驱动总线的mach函数
match(dev,drv)返回0就是匹配成功
2、前一步未匹配成功继续调用driver_probe_device(drv,dev)做了一堆检查
后实际调用really_probe(dev,drv)正如函数名一样他会真正执行probe函数。
这里有个机制内核会优先执行对应总线的probe函数,如果它不存在则会查看对
应驱动的probe函数是否存在,存在就调用驱动的probe操作,在这之前这个函数
还将sysfs下建立好了对应的文件,如果失败后面会删除并返回同时将dev的driver
句柄置空这次的驱动和设备匹配失败反之成功后将进行驱动和设备的绑定 执行driver_bound
6、上一步如果失败就退出驱动注册,成功了继续。
7、module_add_driver
8、前面成功匹配以将在sysfs下建立了驱动的文件夹和文件,后的
操作就是继续创建一些驱动文件接口和属性文件在驱动目录下。
9、至此驱动注册完成,在这之中驱动的探测和绑定均以执行完毕。

下面来仔细看看驱动的注册过程

driver_register

 1 int driver_register(struct device_driver *drv)
2 {
3 int ret;
4 struct device_driver *other;
5
6 BUG_ON(!drv->bus->p);
7 /*
8 这里给出警告是因为在驱动和设备的探测过程和卸载过程中
9 如果总线和驱动同时实现了同名的方法则只会执行总线的方法。
10 */
11 if ((drv->bus->probe && drv->probe) ||
12 (drv->bus->remove && drv->remove) ||
13 (drv->bus->shutdown && drv->shutdown))
14 printk(KERN_WARNING "Driver '%s' needs updating - please use "
15 "bus_type methods\n", drv->name);
16 /*
17 找总线上是否有同名的驱动,驱动不允许重名
18 */
19 other = driver_find(drv->name, drv->bus);
20 if (other) {
21 printk(KERN_ERR "Error: Driver '%s' is already registered, "
22 "aborting...\n", drv->name);
23 return -EBUSY;
24 }
25 /*
26 给总线上添加驱动,依附同一总线的驱动和设备才会进行匹配
27 */
28 ret = bus_add_driver(drv);
29 if (ret)
30 return ret;
31 /*
32 在sys目录地下创建 驱动文件;如果失败就需要从总线上删除驱动
33 */
34 ret = driver_add_groups(drv, drv->groups);
35 if (ret) {
36 bus_remove_driver(drv);
37 return ret;
38 }
39 /*
40 产生并发送一个内核对象增加事件,这里参考设备添加过程或kobject相关的内容
41 */
42 kobject_uevent(&drv->p->kobj, KOBJ_ADD);
43
44 return ret;
45 }

最重要的是其中的bus_add_driver 接口:

 1 int bus_add_driver(struct device_driver *drv)
2 {
3 struct bus_type *bus;
4 struct driver_private *priv;
5 int error = 0;
6 /*Kobject 相关*/
7 bus = bus_get(drv->bus);
8 if (!bus)
9 return -EINVAL;
10
11 pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
12 /* 申请一块内存作为驱动的私有数据块 用来管理驱动地下的设备等属性 */
13 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
14 if (!priv) {
15 error = -ENOMEM;
16 goto out_put_bus;
17 }
18 //设备list初始化,一个驱动可以和多个设备匹配
19 klist_init(&priv->klist_devices, NULL, NULL);
20 priv->driver = drv;
21 drv->p = priv;
22 //C实现的继承
23 priv->kobj.kset = bus->p->drivers_kset;
24 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
25 "%s", drv->name);
26 if (error)
27 goto out_unregister;
28 /*
29 将驱动添加到总线的drvlist中
30 */
31 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
32 /*
33 这里和设备的添加过程呼应
34 */
35 if (drv->bus->p->drivers_autoprobe) {
36 /*
37 驱动添加时匹配设备
38 */
39 error = driver_attach(drv);
40 if (error)
41 goto out_unregister;
42 }
43 module_add_driver(drv->owner, drv);
44
45 error = driver_create_file(drv, &driver_attr_uevent);
46 if (error) {
47 printk(KERN_ERR "%s: uevent attr (%s) failed\n",
48 __func__, drv->name);
49 }
50 error = driver_add_groups(drv, bus->drv_groups);
51 if (error) {
52 /* How the hell do we get out of this pickle? Give up */
53 printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
54 __func__, drv->name);
55 }
56 /*
57 如果驱动支持从文件接口配置驱动则创建sys下的文件
58 */
59 if (!drv->suppress_bind_attrs) {
60 error = add_bind_files(drv);
61 if (error) {
62 /* Ditto */
63 printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
64 __func__, drv->name);
65 }
66 }
67
68 return 0;
69
70 out_unregister:
71 kobject_put(&priv->kobj);
72 /* drv->p is freed in driver_release() */
73 drv->p = NULL;
74 out_put_bus:
75 bus_put(bus);
76 return error;
77 }

执行过程:增加当前总线的kobject的引用次数,分配驱动私有数据的内存这是驱动核心用来管理驱动的私有数据主要是涉及到kobject的内容。然后就是判断驱动所属的总线是否支持自动探测设备这里一般都是默认支持的,所以继续到 driver_attach然后就是其中的bus_add_driver 接口如下这里注意在bus_add_driver 接口还传入了一个函数接口__driver_attach 如果看过前面设备添加过程,这里对这个和接口函数的作用应该很清楚了。

int driver_attach(struct device_driver *drv)
{
//处理也很简单,遍历总线上的设备挨个匹配
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}

bus_add_driver 这个函数和设备添加过程遍历总线上的驱动的处理过程十分相似,就是遍历总线上的 bus->p->klist_devices 依次调用 __driver_attach 接口探测驱动是否支持当前设备。主要区别就是传进来的匹配处理的接口函数的具体实现。

int bus_for_each_dev(struct bus_type *bus, struct device *start,
void *data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device *dev;
int error = 0; if (!bus || !bus->p)
return -EINVAL; klist_iter_init_node(&bus->p->klist_devices, &i,
(start ? &start->p->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}

所以接下来仔细看__driver_attach  接口,前提是知道上面给这个函数传了什么参数,很明显第一个参数是每个不同的device地址,第二个就是正在添加的驱动。函数接口的实现如下

 1 static int __driver_attach(struct device *dev, void *data)
2 {
3 struct device_driver *drv = data;
4 /*
5 这里的实现和__device_attach 接口有两点区别
6 这个接口恒返回0
7 总线的match接口匹配失败后要锁定设备在继续尝试。这里调用的接口和device相同
8 */
9 if (!driver_match_device(drv, dev))
10 return 0;
11
12 if (dev->parent) /* Needed for USB */
13 device_lock(dev->parent);
14 device_lock(dev);
15 if (!dev->driver)
16 driver_probe_device(drv, dev);
17 device_unlock(dev);
18 if (dev->parent)
19 device_unlock(dev->parent);
20
21 return 0;
22 }

driver_probe_device这个接口在设备添加过程中也调用了。

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0; if (!device_is_registered(dev))
return -ENODEV; pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name); pm_runtime_barrier(dev);
ret = really_probe(dev, drv);
pm_request_idle(dev); return ret;
}

检查了设备是否注册确认是注册设备后才执行.....继续到really_probe 。突然豁然开朗这不是就是前面设备注册过程最后调用的那个探测接口吗;这里仅仅是传入的参数顺序不同。前面是不停的遍历驱动传进来,设备固定执行这个接口匹配;现在相反的传入不同的设备而驱动固定进行匹配。所以这里就不继续再继续探究了有兴趣上面的链接去设备的添加过程看看,这里我就偷个懒了。然后就是后续的处理操作主要都是对应的文件在/sys目录下的创建。

驱动卸载流程

这个过程就是上面过程的逆向操作,所以不在详细看了,简单的走读记录一下调用栈,方便日后用到的时候查,基本也都是基于device_driver的高层封装。

void bus_remove_driver(struct device_driver *drv)
{
if (!drv->bus)
return; if (!drv->suppress_bind_attrs)
remove_bind_files(drv);
driver_remove_groups(drv, drv->bus->drv_groups);
driver_remove_file(drv, &driver_attr_uevent);
klist_remove(&drv->p->knode_bus);
pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name);
driver_detach(drv);
module_remove_driver(drv);
kobject_put(&drv->p->kobj);
bus_put(drv->bus);
}

调用栈

1、driver_unregister
1、driver_remove_groups 删除对应目录下的文件
2、bus_remove_driver
1、删除文件
2、klist的维护
3、driver_detach 这个是关建,就是将之前匹配的驱动和设备解绑。
循环
1、 list_entry(drv->p->klist_devices.k_list.prev,。。。 找到驱动上的设备
2、 __device_release_driver
1、删除sysfs下的文件 因为驱动下会有文件指向设备,设备下也有文件直线驱动所以这里会删两次,
           一次是驱动目录下的第二次是设备下的。
2、设备驱动句柄置空,此时设备还是在的只是没有对应驱动。

drive_detach

void driver_detach(struct device_driver *drv)
{
struct device_private *dev_prv;
struct device *dev; for (;;) {
spin_lock(&drv->p->klist_devices.k_lock);
//判断驱动私有数据中的设备list是否为空,这个list中是与驱动匹配的全部设备
if (list_empty(&drv->p->klist_devices.k_list)) {
//没有设备和驱动绑定了则直接返回
spin_unlock(&drv->p->klist_devices.k_lock);
break;
}
//取出一个设备的私有数据,设备也是靠私有数据来管理所属子设备的
dev_prv = list_entry(drv->p->klist_devices.k_list.prev,
struct device_private,
knode_driver.n_node);
//取出设备
dev = dev_prv->device;
get_device(dev);
spin_unlock(&drv->p->klist_devices.k_lock); if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
//确定这个设备使用的是这个驱动
if (dev->driver == drv)
//驱动设备解绑
__device_release_driver(dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
put_device(dev);
}
}

处理的基本过程在代码中也有对应的注释内容,实际上就是遍历驱动的私有数据中的已匹配的设备list然后挨个执行对应的解绑操作,具体的解绑操作如下:

static void __device_release_driver(struct device *dev)
{
struct device_driver *drv; drv = dev->driver;
if (drv) {
pm_runtime_get_sync(dev);
//删除/sys目录下匹配时创建的文件等
driver_sysfs_remove(dev);
// 通知 卸载驱动,支持则通知对应的总线接口解绑事件
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_UNBIND_DRIVER,
dev); pm_runtime_put_sync(dev);
//执行总线或驱动自己的卸载接口
if (dev->bus && dev->bus->remove)
dev->bus->remove(dev);
else if (drv->remove)
drv->remove(dev);
//设备资源的释放,然后就是句柄的清空和对应内核对象的操作
devres_release_all(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
klist_remove(&dev->p->knode_driver);
// 通知 卸载驱动完成,支持则通知对应的总线接口解绑事件
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_UNBOUND_DRIVER,
dev); }
}

总结

驱动的注册过程整体还是比设备添加过程简单一点的主要要明白一下几点

  1. 驱动和总线同时实现probe,remove,shutdown方法内核会屏蔽驱动的方法仅使用用总线的方法。
  2. 不能有同名的驱动注册到系统中。

剩下的驱动和设备的匹配的相关的操作基本就和设备匹配驱动的过程十分相似了。这就是驱动的注册和卸载操作过程,这也是后续等platform相关的设备驱动,I2C等类似总线的设备和驱动注册和注销的相似过程,所以掌握这一部分很重要后续会反复复习下以加深印象。

参考:https://blog.csdn.net/qq_16777851/article/details/81459931

最新文章

  1. Mountains(CVTE面试题)解题报告
  2. 使用 Linq 对多个对象进行join操作 C#
  3. JSP中文乱码问题
  4. [原创]用C++类实现单向链表的增删查和反转操作
  5. 配置jetty 远程调试
  6. TCP/IP详解学习笔记(11)-- TFTP:简单文本传输协议,BOOTP:引导程序协议
  7. C:内存分配、内存中五大区
  8. iOS self和super的区别
  9. docker - 容器里安装mysql
  10. Linux的 Shell 理解和使用
  11. Go语言之高级篇beego框架之日志收集系统
  12. GCC链接的几个注意点
  13. Windows CreateFont:创建自己的字体
  14. java过滤emoji表情(成功率高)
  15. 如何配置windows定时任务
  16. 命令:man
  17. 【ArcGIS】WebAdaptorIIS 安装前准备及配置Portal For ArcGIS的问题解决
  18. C++ compile Microsoft Visual C++ Static and Dynamic Libraries
  19. Spring Boot 响应jsp
  20. HBase 系列(三)HBase Shell

热门文章

  1. EntityFramework Core如何映射动态模型?
  2. Nacos 服务配置中心
  3. JS实现植物大战僵尸小游戏,代码记录及效果展示
  4. 太极图HTML+CSS(可旋转)代码记录
  5. django之orm单表查询
  6. Flutter--Flutter开发环境搭建
  7. odoo-nginx 配置之80端口
  8. Spring Boot的进阶和高级
  9. 数据库MySQL——SQL语句(命令)
  10. IP路由__静态路由