LDD3第十四章的学习-Linux设备模型

作者:Aningsk。本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。

 

关于十二章PCI总线和十三章的USB,我没有细看,所以也就没有写啦。感觉PCI我在嵌入式中用的不多,所以只是作为了解;USB这种东西感觉还是需要学习的,只是我目前还没有接触过USB设备驱动,然后又比较复杂,所以现在仅仅是浏览了一下十三章的内容。

 

至于第十四章Linux设备模型,书中是以2.6内核为准来讲的,与现在的内核版本已经存在一些差距。12月6号(ETS)4.4-rc4都出了,说不定等我写完这个笔记4.4就正式发布了。不过,设备模型上的不同,我大多是参考了3.18的版本而不是4.4。设备模型很大程度上体现了Linux内核的设计思想,就我目前的水平还到不了100%的把握,所以下文的描述中很可能有许多不是非常准确,只能代表我当前的理解水平了,希望大家多多指教啊。(话说,我写的这些东西貌似也没人来看,谈何"大家多多指教"……Orz)

 

struct kobject

Linux中为了维护对设备的引用计数,而引入了kobject;但随着时间的推移,kobject的功能(包含的成员)越来越多,成为Linux内核面向对象的设计中的最原始的基类,所有的对象都是它的子类——也就是说,所有对象的结构体都直接或间接的包含了一个kobject结构体。

关于Linux内核C语言中面向对象的操作,大多以对象的kobject成员为操作对象,但操作的目的常常与kobject本身没有太大关系(kobject自身并没有逻辑上的实际意义),操作的目的是处理包含kobject的对象。这就会出现两个情况:1.我们可以在代码里看到大量的container_of宏及其派生出的宏,用于获取包含kobject的对象;2.操作的函数虽然传入的参数是kobject,但这些函数关注的并不是由kobject,而是包含kobject的对象,这些函数的具体实现,也是由包含kobject的对象模块来实现的,因为只有它们才知道自己要干啥。

上图是kobject结构体的部分截图。关于kobject的初始化和对引用计数的操作参见书中363页。kobject的创建者需要直接或间接设置的成员有:parent、kset和ktype。分别代表的含义是:

parent指向包含此kobject的对象的父对象中kobject,这个父对象常常是kset;

kset(本身也包含有一个kobject)往往是聚集了一群逻辑上相关的kobject(让这些kobject的kset指针指向某个kset),可以认为kset是多个含有kobject的对象的顶层容器。

ktype则是为了表明对象的类型,即多个对象从逻辑上看有相似的"属性""操作"等,把这些内容统一放在ktype中,供不同个体却属于同一类的对象一起引用。

 

struct kobj_type

ktype关心的是对象的类型,前面已经说了我对ktype的理解。简单来说是为了表明对象的类别并复用代码,就像一个苹果和一个香蕉都是水果,拥有相同的属性(比如"水果香味"),也拥有相同的操作(比如"吃掉它"),那么这个苹果和这个香蕉的kobject的ktype指针应该指向同一个struct kobj_type。(以上是我个人理解哈。)

上图是ktype的结构体的部分成员截图。里面有release方法、sysfs操作函数和属性列表。

关于release函数。书中364页说到"当kobject的最后一个引用计数不再存在时,必须异步通知。""通知是使用kobject的release方法实现的"。那么,问题来了:这个release方法是谁来实现,又是在什么时机调用的?

我个人理解:当某个kobject的引用计数成为0,它依然存在于内核中不会消失,在调用它的release方法后,才从内核中去掉此kobject。对kobject的处理方法由包含它的模块实现,以cdev为例,在初始化设置字符设备时:
    cdev_init(&dev->cdev, &mycdev_fops);
而cdev_init中有
    kobject_init(&cdev->kobj, &ktype_cdev_default);

在这里ktype_cdev_default是一个struct kobj_type的指针,将赋给cdev->kobj->ktype。ktype就含有release方法,并已经有cdev部分实现了这个release方法,而不是kobject自己去实现。

一个kobject的引用计数何时"不再存在"?

在驱动模块中初始设置cdev后,需要调用cdev_add函数,add函数将会增加cdev的kobject的引用计数;而驱动模块卸载时,需要调用cdev_del函数,del函数调用于不再使用此驱动模块的时刻,即此时kobject的引用计数将由1变为0,"不再存在"。这时,会有类似如下代码:

    cdev_del(&mydev->cdev);
    kfree(mydev); //这对应于364页上的那个kfree。

如何调用到了release?

    cdev_del(&mydev->cdev);
->    kobject_put(&p->kobj);
->    kref_put(&kobj->kref, kobject_release);
->    kref_sub(kref, 1, release);
内有:(struct kref中有记录引用计数的原子变量)
    if (atomic_sub_and_test((int) count, &kref->refcount)) {
        release(kref);
        return 1;
    }
    return 0;

即在引用计数(原子量)减1为0时,则调用函数kobject_release。在kobject_release函数中将完成kobject的清理工作,并调用kobject_cleanup函数,其中最终会调用kobject->ktype中的release函数(就是cdev模块实现的那个)。

这样就头绪就理清了。(PS.理清思路-厘清真相 (:P))

 

struct kset

与ktype不同的是,kset关心的对象的聚集与集合。个人理解,kset就是内核中组织"子系统"概念的基础结构之一,比如说sysfs中的bus,以及bus下的各种总线(i2c、spi等)都是一个个子系统——它们实现的众多对象的聚集与集合管理,这些对象有的是设备、有的是驱动:这些对象不一定是同一个ktype,但是他们应该同属一个kset(父级的,也可能是爷爷级的)。

上图体现了kset与它所集合的kobject之间的关系。

书中365页中,谈到了内核代码一处"令人困惑"(吐槽一下中文翻译,有错别字"另")的地方:作者说,kobject结构体中包含了一个kobj_type的指针,如果kobject是kset的一个成员,kset会提供一个kobj_type指针。我看了一下3.18内核,kset中是没有kobj_type指针的!2.6.32.69中也没有。书是以2.6.10写的,可能是有区别的。现在的内核不存在这个困惑。

另外一点,书中367页有一段描述:kset中的ktype的使用优先于kobject中的ktype。这种情况,我认为现在是不存在的!其一,kset中已经没有了ktype成员;其二,kset影响它聚集的kobject的ktype,并不符合完美的面向对象——kset已经向"子系统"的概念演化,"子系统"将会包含多种类别的对象;而ktype是通过对象的属性与动作描述对象的类别——如果kset含有的ktype优先使用于kobject本身的ktype,那么一个"子系统"中呈现出来的就只有一种类别。这是不科学的。内核开发者可能是出于类似的想法,所以将ktype成员从kset中去掉了。

另外,需要记录的一点是:kobject在sysfs体现为一个目录,kset也是含有kobject的对象,也是对应于sysfs中的目录;而sysfs中的文件则是ktype中struct attribute **default_attrs(属性链表)。

关于"子系统"。Linux内核将它管理的所有对象通过"子系统"的方式组织起来。现在的内核实现与书中讲述的(367页的内容)已经存在较大的差异了。事实上,现在的内核中找不到struct subsystem了,kset中也不存在任何指针指向类似的结构体。现在存在struct subsys_private,包含了kset,在bus_type结构和class结构中存在指向struct subsys_private的指针;而struct subsys_private中也含有bus和class指针来表明自己从属于哪个bus和class。

也就是说kset是构建子系统(subsys_private)的基础,bus、class等结构体通过指向subsys_private的指针实现"子系统"功能。(这种结构比之前的更为合适,之前的是kset包含了子系统的东西,就会把kset这种逻辑上底层的东西抬到了比较上层的位置了。)

总之,bus等结构体现了"子系统"的概念,实现统一管理;子系统以kset为核心来实现,kset聚合了多个kobject,这些kobject属于各种各样的对象(包含device和device_driver)。不同的设备与驱动就这样纳入了统一的管理体系。不同类型的对象可以由kobject中的ktype来指认区分。嗯嗯,我感觉Linux设备模型就是这么个节奏。

 

关于sysfs、类、热插拔和固件的一些说明

关于sysfs中属性文件。在struct kobj_type中default_attrs保存了属性列表,列表的最后一个必须用NULL填充表示结束;sysfs_ops提供了这些属性的实现方法,包含了show和store方法,分别代表"读"和"写",通过传递进函数的属性中的name字段,来确定上层请求的是哪一个属性,读写的内容不应超过PAGE_SIZE。

defaule_attrs表示了该kobject拥有的全部属性,但这不是严格的限制,我们可以根据自己的需要对kobject的属性进行添加和删除。

添加新属性,首先填写一个attribute结构,然后使用:
    int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
删除属性:
    int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);

同时应注意调整show和store中的代码实现。嗯,这些在370页都有说明,其他的像二进制属性,我就不写了。

关于"类"。其实我也不是非常了解这个"类"的具体用途,书中说"类存在的真正目的是,给作为类成员的各个设备提供一个容器。"在387页还有一个"类设备",我目前的理解是:在sysfs中,类设备是其所属类下的一个目录,类设备目录下会有指向/sys/devices/下相应设备的符号链接。说白了,就是为了方便查看。

写驱动时有一个和"类"有关的东西,在这里要记录一下:

自己写的一个小字符设备驱动,按书中的流程写,驱动安装进系统后/dev下不会出现对应的字符设备文件。书中是另外写了shell脚本来安装驱动并创建/dev下的字符设备文件的。但是,如果在安装驱动时就能生成设备文件,那就爽了不少。在网上找到了方法,就是与"类"有关,在驱动的init函数中,添加类似如下代码:
    int result;
    static struct class *foo_class;
    foo_class = class_create(THIS_MODULE, "foo");
    if (IS_ERR(foo_class)) {
        result = PTR_ERR(foo_class);
        printk(KERN_ERR "Err=%d\n", result);
        goto error_1;
    }

    struct device *class_dev;
    class_dev = device_create(foo_class, NULL, MKDEV(foo_major, 0), NULL, "%s", "foo");
    if (IS_ERR(class_dev)) {
        result = PTR_ERR(class_dev);
        printk(KERN_ERR "Err=%d\n", result);
        goto error_2;
    }

其中struct class *foo_class是全局变量,因为在exit函数中要对应调用:
    device_destroy(foo_class, devno);
    class_destroy(foo_class);

这样在安装驱动后就能自动出现/dev下的设备文件,卸载驱动对应的设备文件也就能自动消除。但是原理我还不知道。

关于"热插拔"。书中经常出现"hotplug"的字眼,在较新的内核似乎已经没有hotplug了,取而代之的是"uevent",比如struct kset_uevent_ops等;书里经常出现的/sbin/hotplug脚本我也从来没见过……写一下我的理解吧:

热插拔的实现基于uevent来实现,最开始发起于kobject_uevent函数产生事件(如kobject_uevent(kobj, KOBJ_ADD);)然后会调用kobject_uevent或kobject_uevent_env,会使用kset的uevent_ops中的函数来进行过滤、获取名字、添加环境变量。之后,使用call_usermoduhelper_setup得到用户空间程序的信息info,使用这个info,用call_usermoduhelper_exec来运行用户空间程序。

那么,如何指定uevent用户空间程序?1、可以在编译内核前,配置CONFIG_UEVENT_HELPER_PATH。2、也可以在系统运行时,将程序的路径写入/sys/kernel/uevent_helper文件中。(推荐第2种方法)

关于固件。在书中402页描述了从用户空间获取固件的函数,函数requset_fireware会在/sys/class/fireware下创建一个目录,需要热插拔脚本将固件写入对应的文件。但是书中没有给出其他的实现细节啊。

 

Linux设备模型的内容很多,这篇文章主要说了kobject、ktype、kset;至于bus、device和device_driver我没有详细说明,在我以前写的《eMMC驱动分析》中涉及了一点,可供参考;那篇文章里没有涉及的,书中374页"总线、设备和驱动程序"一节有详尽的说明。再结合本文中提到的kobject、ktype、kset之间的关系,大概可以了解Linux设备模型从上到下的架构与思想。实际上除了这些东西,还有好多知识不了解啊,比如热插拔什么的。当然,限于个人水平,本文描述的内容不一定正确哈Orz……欢迎大家前来指正!

好了,就先到这里吧。准备回家吃饭……话说,做饭时间和饭后收拾的时间,总是比吃饭所用的时间长好多……真是人生第四大恨事啊,哈哈~

 

Aningsk

2015-12-10

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值