linux设备驱动2

原文出处:http://blog.csdn.net/weekyhuang/article/details/4435517
 


        这仍然是摘自自己工作笔记的第二部分,主要记述了我对在内核中注册驱动程序(我们这里以我的SDIO总线及wlan网卡举例)后,在内核中调用了哪些内核函数?其相应的设备树是如何形成的?以及我看《linux设备驱动第三版》linux设备模型章节的诸多疑惑的总结。仍然要强调一遍,这里仅仅是我的理解而已,正确与否我无法判断。所以对军爷问题的回答,也希望军爷和我多多探讨。

        首先我要做的是总领一下 kobject、kset、subsystem 这三个结构之间的关系。同样以下内容摘自我的工作笔记,但顺序颠倒了,这里先行介绍可能对我们之后的代码介绍要有些许帮助。
 
        关于  kobject 结构,首先  每个目录代表一个  kobject  对象,每个文件代表  kobject  的属性(这里我实在不敢肯定!)。kobject  是组成设备模型的基本结构,它只是一个类似  c++  中基类的东西。
        我想给出这样一个比喻(我不知道这个比喻是否恰当):对于每一个目录,它们都有一些共同的特点,比如都有名字,都有父子目录。而对于“方法”来说,它们都 有用来实现引用计数的方法。我们将这些共同点封装起来形成了基类。但是这个基类并不能真正的代表一个目录,加上了其它的特征之后才能成为一个真正的目录, 所以我们需要将  kobject  这个结构嵌入所有的  sysfs  下的目录中以获得  kobjec t  提供的一些特征,在加上每个目录自己所独有特征从而形成一个  sysfs  中的目录。这也就是我理解的kobject 结构的意义。那么kobject 结构是否真的就对应于一个目录呢?回答是肯定的,因为我在代码中找到了这样的答案,每一次调用kobject_add 函数,都会在这个函数中调用create_dir 来创建一个目录。
        那么kset 又是什么呢?为什么需要kset ?还有subsystem 呢?kobject、kset、subsystem 这三个结构到底在设备树中各自承担什么样的角色?  kset  是是嵌入相同类型结构的  kobject 的  集合,对于c++来说意味着将继承自所有基类的子类放在一起,这有什么意义呢?这样想是否就会觉得很合理了,kset 是用来将所有有着共同特点的目录联系在一起的东西。可能你还是要在内心不停的问,这太抽象了 太抽象了,能举个例子吗?是的,比如我们的设备树下的pci总线目录/sys/bus/pci 下挂接着很多的设备和驱动(当然有很多的设备和驱动可以挂着在pci总线下),那么我们如何将它们联系在一起呢?是的,是通过kset 结构,那么kset 结构是个什么样的地位呢?噢,我想之前我理解错了,它不是/sys/bus/pci 目录,也不是/sys/bus/pci/drivers 目录中的某个具体驱动,它刚好就是/sys/bus/pci/drivers 目录。不相信? 那么打开你们的linux 操作系统,看一看是否每一条总线都有着drivesdevices 两个目录?它们两个都是嵌入了kset 结构;那么/sys/bus/pci/drivers 目录中的某个具体驱动呢?它就是嵌入了kobject 结构的目录;/sys /bus/pci 目录呢?猜对了,它就是subsystem 结构的嵌入。 还有要补充的吗?是呢,现在subsystem 貌似被取消了,取代它的就是kset 结构。这样也就是说一系列的 kset 就组成了subsystem 。
        我似乎忘记了自己问了三个问题,对的,我还需要问答自己看《linux设备驱动第三版 》提出来的第三个问题:为什么需要kset ?提出这个问题的想法很幼稚,难道我们不能够只用kobject 结构将挂接在一条总线上的驱动或者设备都链接在一起吗?只要我们有list_head !linux内核的作者回答了我这个问题,你需要一个容器来管理它们,就像c++中的容器一样。但这至少说明了kset 并不是必要的存在。那么kset 是如何扮演容器的角色的呢?这个问题,我们需要看后面的图解来回答。
        以上是摘自我穿插在工作笔记中的关于kobject、ket、subsystem三个结构的描述,可能大家读完这段解释仍然无法形成具体的概念,仍然觉得很抽象。没关系,因为我在代码分析之后还会有总结性的举例。那时还不明白我就没辙了!!!
        好 了,现在我们得费劲心思的捋一遍我们的驱动注册代码,以便找到设备树添加的关键部分。我想我又得强调一下,我的介绍是SDIO驱动,所以请大家看着 linux内核代码drivers/mmc中关于sdio的驱动来理解我下面的笔记中的内容(想不看内核代码就理解设备树,我想太难太难)
        有关 sbi_register 函数(这是在我的wlan驱动代码中的函数,并不需要你太多的关注 )中 sdio_register_driver函数(从现在开始就都是内核函数了) 注册驱动的介绍,在 sdio_register_driver 中将会指明驱动的名称(这里是”wlan_sdio ” ),此函数的参数为 sdio_driver 结构。 驱动所挂接的总线 sdio_bus_type ,其结构类型为:
static struct bus_type sdio_bus_type = {
    .name             = "sdio",                 // 总线类型
    .dev_attrs       = sdio_dev_attrs,          // 属性
    .match           = sdio_bus_match,         //ops
    .uevent           = sdio_bus_uevent,
    .probe            = sdio_bus_probe,  
    .remove          = sdio_bus_remove,
};
        这个结构将在 sdio_register_driver 函数中被赋值以产生 device_driver 结构。也就是说 device_driver 被包含在 sdio_driver 中。 随后调用函数 driver_register ,其参数为 device_driver (此结构中定义了 bus_type,也就是驱动挂接的总线类型 )。至此将转入所有驱动(不止是 sdio 卡驱动)的注册代码中。此时的驱动结构已经变为device_drive ( 内核定义的驱动结构 ) 。
        
        driver_register 将会完成挂接驱动至总线及生成设备树的过程,其完成的任务大致包括:
  1. 设置设备驱动中 kobject 的名字
  2. 将 kobject 添加至 sysfs 中,也就是在 sysfs 树中创建一个目录( kobject 中有一个函数 create_dir 用于创建目录。)
  3. 调用 driver_attach 函数完成 probe 
  4. driver_create_file 创建文件属性,会生成一个属性为 driver_attr_uevent 的属性文件。
  5. add_bind_files 生成属性为 driver_attr_unbind 和 driver_attr_bind 的属性文件,关于文件的属性,它定义了文件的读写方式。
        如此抽象的描述及流水账般的记述我发现还是很没有说服力,因此我决定给出一个具体的例子,并给出代码的实现。先看例子——让我们看一下 /sys/bus/ 下的目录,然后来个具体的描述:已知我们的总线的目录名字为”sdio” 。也就是说在 /sys/bus 目录下有一个目录叫 sdio ,即 /sys/bus/sdio 。它是怎么形成的?内核中有 ”sdio”总线的驱动,找到这个函数 bus_register (&sdio_bus_type ) ;就是用来注册总线的。我们在前面不也看到了driver_register 函数吗,是的,你猜对了,还有一个函数叫 device_register 。在这个函数调用完成后,就会得到/sys/bus/ 目录下的 sdio 目录了。那么驱动的名字 ”wlan_sdio” 又是如何插入到 /sys/bus/drivers/wlan_sdio 目录下的呢。在 driver_register->driver_register->bus_add_driver 函数中有个重要的语句 drv->kobj.kset = &bus->drivers ;想象一下我们折腾了那么长时间的 kobject 与 kset 的意义,是的,这里就是将 driver 的 kobj 所属的 kset 挂接上总线的 kset 。我们这里显得很绕对吗?幸运的是我在网上找到了一个非常棒的示意图:

        

        我该怎么样来形容这个图呢? 它把依赖关系说的已经足够清楚了!!我这里唯一要解释的仅仅是在驱动文件夹被正确后所谓的文件属性有在那里。同样我们顺着目录 /sys/bus/sdio/driver/wlan_sdio/ 下我们发现了 bind unbind 和 new_id 三个文件。

        好了,别忘了我们在前面提过的问题,kset 是如何扮演容器的角色的呢?图中很清楚吧,看看粉红色的箭头,kset children list用来将将同类型的kobject连接起来以达到容器的效果。

        但是我们的驱动还没有结束,关于这个图的建立,我们势必要用代码才能说得明白。

        我们还是花费一点时间来看一下内核中的代码,关于 sdio 总线注册的代码部分,其它的部分,大家类举就可以了。懂了这段自然就懂了全部:

int bus_register( struct bus_type * bus)
{
    int retval;

    BLOCKING_INIT_NOTIFIER_HEAD (&bus->bus_notifier);

    retval = kobject_set_name(&bus->subsys.kobj, "%s", bus->name);     // 总线的名字 ”sdio”, 我们说过了一个kobject 对应一个目录,这时会为这个目录赋值名字。
    if (retval)
        goto out;

    bus->subsys.kobj.kset = &bus_subsys;                         // 将其 kset 指向 bus_subsys., 如何理解? 看看ldd_bus_type 指向 bus_subsys 的那条蓝线

    retval = subsystem_register(&bus->subsys);               //bus->subsys 的注册,实际上是用 kset 指针将其链接在一起。好吧 我得承认实际上这里取消了 subsysem 结构的概念,用 kset 代替了。这里会创建一个目录。它是一个 kset 也是一个 kobject ,因为 kset 包含了 kobject 。
    if (retval)
        goto out;


    retval = bus_create_file(bus, &bus_attr_uevent);             // 创建属性文件
    if (retval)
        goto bus_uevent_fail;

    kobject_set_name(&bus->devices.kobj, "devices");          // 设置 devices kset 的名字为 devices
    bus->devices.kobj.parent = &bus->subsys.kobj;            // 参见 ldd_bus_type->devices 指向 ldd_bus_type->sub_sys 的红色箭头(注意这里也不是 subsystem, 而是 kset )

    retval = kset_register(&bus->devices);                 // 创建 devices 命名的目录
    if (retval)
        goto bus_devices_fail;

    kobject_set_name(&bus->drivers.kobj, "drivers");       // 设置 devices kset 的名字为 drivers
    bus->drivers.kobj.parent = &bus->subsys.kobj;         // 同样参见 ldd_bus_type->drivers 指向 ldd_bus_type->sub_sys 的红色箭头(注意这里也不是 subsystem, 而是 kset )。
    bus->drivers.ktype = &driver_ktype;                     // 对 kobject 默认属性的赋值
    retval = kset_register(&bus->drivers);                  // 创建 drivers 命名的目录
    if (retval)
        goto bus_drivers_fail;

    klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put);     //klist 结构的初始化,关于 klist 链接的作用我们已经说得很清楚了
    klist_init(&bus->klist_drivers, NULL, NULL);

    bus->drivers_autoprobe = 1;
    retval = add_probe_files(bus);                       // 添加探测属性
        if (retval)
            goto bus_probe_files_fail;

    retval = bus_add_attrs(bus);                        // 添加其他属性
    if (retval)
        goto bus_attrs_fail;

    pr_debug("bus type '%s' registered/n", bus->name);
    return 0;

bus_attrs_fail :
    remove_probe_files(bus);
bus_probe_files_fail :
    kset_unregister(&bus->drivers);
bus_drivers_fail :
    kset_unregister(&bus->devices);
bus_devices_fail :
    bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail :
    subsystem_unregister(&bus->subsys);
out :
    return retval;
}

    至此 我们终于理顺了整个过程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值