目录
二、Kobject、Kset、Kobject_type. 3
(5)kobject_create_and_add() 11
一、基本概念
之前都以为linux的设备驱动模型就是bus_type,device,device_driver其实不止哦,完整的应该是bus_type,device,device_driver,class;
常见的硬件拓扑如下,
Linux设备模型的核心思想是(通过xxx手段,实现xxx目的):
1. 用Device(struct device)和Device Driver(struct device_driver)两个数据结构,分别从“有什么用”和“怎么用”两个角度描述硬件设备。这样就统一了编写设备驱动的格式,使驱动开发从论述题变为填空体,从而简化了设备驱动的开发。
2. 同样使用Device和Device Driver两个数据结构,实现硬件设备的即插即用(热拔插)。
在Linux内核中,只要任何Device和Device Driver具有相同的名字,内核就会执行Device Driver结构中的初始化函数(probe),该函数会初始化设备,使其为可用状态。
而对大多数热拔插设备而言,它们的Device Driver一直存在内核中。当设备没有插入时,其Device结构不存在,因而其Driver也就不执行初始化操作。当设备插入时,内核会创建一个Device结构(名称和Driver相同),此时就会触发Driver的执行。这就是即插即用的概念。
3. 通过"Bus-->Device”类型的树状结构(见2.1章节的图例)解决设备之间的依赖,而这种依赖在开关机、电源管理等过程中尤为重要。
试想,一个设备挂载在一条总线上,要启动这个设备,必须先启动它所挂载的总线。很显然,如果系统中设备非常多、依赖关系非常复杂的时候,无论是内核还是驱动的开发人员,都无力维护这种关系。而设备模型中的这种树状结构,可以自动处理这种依赖关系。启动某一个设备前,内核会检查该设备是否依赖其它设备或者总线,如果依赖,则检查所依赖的对象是否已经启动,如果没有,则会先启动它们,直到启动该设备的条件具备为止。而驱动开发人员需要做的,就是在编写设备驱动时,告知内核该设备的依赖关系即可。
4. 使用Class结构,在设备模型中引入面向对象的概念,这样可以最大限度地抽象共性,减少驱动开发过程中的重复劳动,降低工作量。基本上的子系统都是通过class来实现。
二、Kobject、Kset、Kobject_type
1.背景
Linux 设备模型核心使用的是bus_type,device,device_driver,class四个数据结构,他们更多的是把大量的不同功能的硬件设备和对应的驱动维护成一个树形结构,从而方便kernel的统一管理。这一部分功能的主要实现在这四个数据结构的私有数据里,如device的device_private;
而这四个数据结构也有一些共同的功能,内核里对这种共同的功能都喜欢抽象出来统一实现,从而避免代码冗余,所以kobject及其相关的结构体诞生了;
确实实现这个抽象写了很多代码,当时从长远来看是必要的,目前为止,抽象出来的主要功能如下:
- 通过parent指针,可以将所有Kobject以层次结构的形式组合起来(树形结构这一块)。
- 使用一个引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放(这是Kobject诞生时的唯一功能,核心数据结构的资源释放这一块)。
- 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间(核心数据有关sysfs的这一块)。
2.主要的数据结构体
代码路径:
/include/linux/kobject.h
/lib/kobject.c
kobject.h为Kobject的头文件,包含所有的数据结构定义和接口声明。kobject.c为核心功能的实现
Kobject是基本数据类型,每个Kobject都会在"/sys/“文件系统中以目录的形式出现。
Ktype代表Kobject(严格地讲,是包含了Kobject的数据结构)的属性操作集合(由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了)。
Kset是一个特殊的Kobject(因此它也会在"/sys/“文件系统中以目录的形式出现),它用来集合相似的Kobject(这些Kobject可以是相同属性的,也可以不同属性的)。
2.1 Kobject
name,该Kobject的名称,同时也是sysfs中的目录名称。由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。
entry,用于将Kobject加入到Kset中的list_head。
parent,指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。
kset,该kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent(别忘了Kset是一个特殊的Kobject)。
ktype,该Kobject属于的kobj_type。每个Kobject必须有一个ktype,或者Kernel会提示错误。
sd,该Kobject在sysfs中的表示。
kref,"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。
state_initialized,指示该Kobject是否已经初始化,以在Kobject的Init,Put,Add等操作时进行异常校验。
state_in_sysfs,指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。
state_add_uevent_sent/state_remove_uevent_sent,记录是否已经向用户空间发送ADD uevent,如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,以便让用户空间正确处理。
uevent_suppress,如果该字段为1,则表示忽略所有上报的uevent事件。
注4:Uevent提供了“用户空间通知”的功能实现,通过该功能,当内核中有Kobject的增加、删除、修改等动作时,会通知用户空间。
2.2 Kset
list/list_lock,用于保存该kset下所有的kobject的链表。
kobj,该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。
uevent_ops,该kset的uevent操作函数集。当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量(填充好对应的信息的结构体),或者过滤event(kset可以决定哪些event可以上报)。因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。
2.3 Kobj_type
release,通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉,所以内存得用动态分配的形式然后这里用kfree释放掉。
sysfs_ops,该种类型的Kobject的sysfs文件系统接口,里面有show/store函数,里面的实现逻辑都是,通过attribute得到包含他的xxx_attribute(比如device_attribute),然后再调用这个xxx_attribute 里的show/store函数。
default_attrs,该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的一个文件)。将会在Kobject添加到内核时,一并注册到sysfs中。
child_ns_type/namespace,和文件系统(sysfs)的命名空间有关,这里不再详细说明。
总结,Ktype以及整个Kobject机制的理解
Kobject的核心功能是:保持一个引用计数,当该计数减为0时,自动释放(由本文所讲的kobject模块负责) Kobject所占用的meomry空间。这就决定了Kobject必须是动态分配的(只有这样才能动态释放)。由kobject_put()函数实现,下面再具体分析;
而Kobject大多数的使用场景,是内嵌在大型的数据结构中(如Kset、device_driver等),因此这些大型的数据结构,也必须是动态分配、动态释放的。那么释放的时机是什么呢?是内嵌的Kobject释放时。但是Kobject的释放是由Kobject模块自动完成的(在引用计数为0时),那么怎么一并释放包含自己的大型数据结构呢?
对应Kobject释放的时候跑他的ktype里的release函数,这个函数只会kfree(kobj),错了
使用kobject_create()函数创建的kobject对象使用了默认的release函数,这个函数才只会kfree(kobj);
而我们再使用device_add()函数添加设备之前会使用device_initialize(dev)函数对具体设备的device进行初始化,里面用的是kobject_init(&dev->kobj, &device_ktype)函数,这个device_ktype里的release函数是会释放对应的设备结构体的,而且会跑bus,class上的release函数,如下,
这时Ktype就派上用场了。我们知道,Ktype中的release回调函数负责释放Kobject(甚至是包含Kobject的数据结构)的内存空间,那么Ktype及其内部函数,是由谁实现呢?是由上层数据结构所在的模块!因为只有它,才清楚Kobject嵌在哪个数据结构中,并通过Kobject指针以及自身的数据结构类型,找到需要释放的上层数据结构的指针,然后释放它。
讲到这里,就清晰多了。所以,每一个内嵌Kobject的数据结构,例如kset、device、device_driver等等,都要实现一个Ktype,并定义其中的回调函数。同理,sysfs相关的操作也一样,必须经过ktype的中转,因为sysfs看到的是Kobject,而真正的文件操作的主体,是内嵌Kobject的上层数据结构!
比如,我们再cat 一个device下的属性的时候,syfs看到的是这个device的kobject,cat的时候跑的函数是这个device的kobject中ktype里的sysfs_ops,然后再sysfs_ops对应的读写函数里去通过contain_of宏从attribute得到xxx_attribute,再跑xxx_attribute下的读写函数,如下,
3.主要函数及其使用分析
3.1 Kobject使用流程
Kobject大多数情况下(有一种例外,下面会讲)会嵌在其它数据结构中使用,其使用流程如下:
- 定义一个struct kset类型的指针,并在初始化时为它分配空间,添加到内核中
- 根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有Kobject
- 定义一个适合自己的ktype,并实现其中回调函数
- 在需要使用到包含Kobject的数据结构时,动态分配该数据结构,并分配Kobject空间,添加到内核中
- 每一次引用数据结构时,调用kobject_get接口增加引用计数;引用结束时,调用kobject_put接口,减少引用计数
- 当引用计数减少为0时,Kobject模块调用ktype所提供的release接口,释放上层数据结构以及Kobject的内存空间
可以参考power supply子系统的注册和注销函数来分析
上面有提过,有一种例外,Kobject不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs中创建一个目录,而不需要其它的kset、ktype的操作。这时可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到kernel中。
比如在devices_init()函数里,在sysfs中创建/sys/devices 、/sys/dev/block/ 、/sys/dev/char三个目录,如下,
3.2 主要函数
3.2.1 Kobject 的分配和释放
前面讲过,Kobject必须动态分配,而不能静态定义或者位于堆栈之上,它的分配方法有种。
1. 通过kmalloc自行分配(一般是跟随上层数据结构分配),并在初始化后添加到kernel。这种方法涉及如下接口:
(1)kobject_init()
kobject_init,初始化通过kmalloc等内存分配函数获得的struct kobject指针。主要执行逻辑为:
- 确认kobj和ktype不为空
- 如果该指针已经初始化过(判断kobj->state_initialized),打印错误提示及堆栈信息(但不是致命错误,所以还可以继续)
- 初始化kobj内部的参数,包括引用计数、list、各种标志等//kobject_init_internal()
- 根据输入参数,将ktype指针赋予kobj->ktype
(2)kobject_add()
kobject_add,将初始化完成的kobject添加到kernel中,参数包括需要添加的kobject、该kobject的parent(用于形成层次结构,可以为空)、用于提供kobject name的格式化字符串。主要执行逻辑为:
- 确认kobj不为空,确认kobj已经初始化,否则错误退出
- 调用内部接口kobject_add_varg,完成添加操作
(3) kobject_init_and_add()
kobject_init_and_add,是上面两个接口的组合,不再说明。
2. 使用kobject_create创建
Kobject模块可以使用kobject_create自行分配空间,并内置了一个ktyp(dynamic_kobj_ktype),用于在计数为0是释放空间。代码如下,
(4)kobject_create()
kobject_create,该接口为kobj分配内存空间,并以dynamic_kobj_ktype为参数,调用kobject_init接口,完成后续的初始化操作。
(5)kobject_create_and_add()
kobject_create_and_add,是kobject_create和kobject_add的组合,不再说明
最基本的两个函数就是kobject_init() 和 kobject_add();
3.2.2 Kobject引用计数的修改
通过kobject_get和kobject_put可以修改kobject的引用计数,并在计数为0时,调用ktype的release接口,释放占用空间。
(1)kobject_get()
kobject_get,调用kref_get,增加引用计数
(2)kobject_put()
kobject_put,以内部接口kobject_release为参数,调用kref_put。kref模块会在引用计数为零时,调用kobject_release,
kobject_release,通过kref结构,获取kobject指针,并调用kobject_cleanup接口继续。
kobject_cleanup,负责释放kobject占用的空间,主要执行逻辑如下:
- 检查该kobject是否有ktype,如果没有,打印警告信息
- 如果该kobject向用户空间发送了ADD uevent但没有发送REMOVE uevent,补发REMOVE uevent
- 如果该kobject有在sysfs文件系统注册,调用kobject_del接口,删除它在sysfs中的注册
- 调用该kobject的ktype的release接口,释放内存空间
- 释放该kobject的name所占用的内存空间
3.2.3 Kset的初始化、注册
Kset是一个特殊的kobject,因此其初始化、注册等操作也会调用kobject的相关接口,除此之外,会有它特有的部分。另外,和Kobject一样,kset的内存分配,可以由上层软件通过kmalloc自行分配,也可以由Kobject模块负责分配,具体如下,
kset_init,该接口用于初始化已分配的kset,主要包括调用kobject_init_internal初始化其kobject,然后初始化kset的链表。需要注意的时,如果使用此接口,上层软件必须提供该kset中的kobject的ktype。
kset_register,先调用kset_init,然后调用kobject_add_internal将其kobject添加到kernel。
kset_unregister,直接调用kobject_put释放其kobject。当其kobject的引用计数为0时,即调用ktype的release接口释放kset占用的空间。
kset_create_and_add,会调用内部接口kset_create动态创建一个kset,并调用kset_register将其注册到kernel。
==========================内部接口======================================
kset_create,该接口使用kzalloc分配一个kset空间,并定义一个kset_ktype类型的ktype,用于释放所有由它分配的kset空间。
三、UEVENT
http://ww.wowotech.net/device_model/uevent.html