-------------------------------------------------------------------------------------------------------------------------------linux
阿辉翻译自Linux内核文档:\linux-3.4.75\Documentation\kobject.txt程序员
原创翻译,欢迎转载,转载请注明出处安全
-------------------------------------------------------------------------------------------------------------------------------异步
理解kobject抽象和以此来创建的设备驱动模型的部分困难在于没有一个明显的切入点。函数
而正确使用kobjects须要了解一些不一样的类型,而这些类型是相互引用。为了更容易理解,咱们将从一些模糊的概念开始,并逐步增长细节。为此,先来介绍几个咱们会使用到术语的快速定义。学习
一、kobject是struct kobject类型的对象,它包含一个名字和引用计数,而且拥有一个父指针(这可让对象组织成层次结构),而且kobject一般是sysfs虚拟文件系统中的表述。ui
二、 通常不会对kobject自己感兴趣,相反,kobject结构一般是被嵌入到其余结构中,而这些结构才包含了真正感兴趣的数据。this
三、没有一个结构会嵌入多于一个kobject结构,若是这么作了,关于这个对象的引用计数确定会一团糟,你的code也会充满bug,因此千万不要这么作。spa
四、ktype是内嵌kobject结构的对象的类型,每一个内嵌kboject的结构都须要一个特定的ktype结构,ktype决定了kobject被建立和销毁时所采起的动做。翻译
五、kset包含了一组kobject结构,这些kobject能够有相同或者不一样的ktype。kset是一个基础的容器类型,它包含了kobject的集合。kset自身也内嵌了一个kobject结构,不过你能够不用关注这个实现的细节,由于kernel中的kset核心程序已经作好这些事情了。
六、当你看到某个sysfs目录下有不少其余目录,这些目录都对应着一样kset里包含的具体kobject
咱们会以自下到上的角度开始研究如何建立和操做这些类型;所以,咱们将会从kobject开始
极少会须要kernel代码区建立一个单独的kboject结构,除了后面所述的一个显著例外。相反,kobject被用来控制访问一个更大的,具备特定做用域的对象,为了达到这个做用,kobject将会被嵌入到其余结构体中。若是你用面向对象的角度来看,kobject结构能够被看作顶层的抽象类,其余类都是从这个类派生出来的。
kobject实现了一系列功能,这些功能一般不会用于kobject自身,但对其余对象来讲倒是很是好的功能。C语言不支持直接表达继承的语法,所以就必须使用相似于结构体内嵌的其余技术(做为旁白,对于那些对内核链表实现熟悉的人来讲,这有点相似于“list_head”结构不多会做用于自身,但老是能够发现它嵌入在在更大的结构体中)。举个例子,实现于drivers/uio/uio.c的UIO代码包含了一个以下定义的结构体,定义了uio设备对应的内存空间
struct uio_map {
struct kobject kobj;
struct uio_mem *mem;
};
若是你有一个struct uio_map结构体,经过指向kobj成员就能够找到嵌入的kobject结构。然而,包含kobject的代码一般会有相反的困扰,如何根据kobject结构指针找到包含它的指针呢?不要特地取巧(好比假设kobject在该结构的开头),而应该使用<linux/kernel.h>中的contain_of()宏:
container_of(pointer, type, member)
对应参数的含义以下:
pointer:指向内嵌kobject的指针
type:包含kobject结构的结构体的指针
member:”pointer”指针的名字(好比前面uio_map结构中的kobj)
container_of()宏的返回值是该容器类型的指针。举个例子,指针kp是kobject结构指针,内嵌于uio_map结构体,能够经过以下的代码获得获得uio_map结构体指针:
struct uio_map *u_map = container_of(kp, struct uio_map, kobj);
为了方便,程序员一般定义一个简单的宏,经过kobject指针逆向获得容器类指针。在drivers/uio/uio.c更早的代码中就是这样实现的,就像你看到的以下代码:
struct uio_map {
struct kobject kobj;
struct uio_mem *mem;
};
#define to_map(map) container_of(map, struct uio_map, kobj)
这里,宏的参数”map”是指向kobject结构的指针,随后宏就变成以下形式的调用:
struct uio_map *map = to_map(kobj);
建立kobject固然必须初始化kobject对象,kobject的一些内部成员须要(强制)经过kobject_init()初始化:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
ktyp对于kobject的正确建立是必须的,由于每一个kobject必须关联一个kobj_type。
在调用kobject_init将kobject注册到sysfs以后,必须调用kobject_add()添加:
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
它正确设置了kobject的名称和父节点,若是这个kobject和一个特定的kset关联,则kobj->kset必须在调用kobject_add以前被指定。若kobject已经跟一个kset关联,在调用kobject_add时能够将这个kobject的父节点设置为NULL,这样它的父节点就会被设置为这个kset自己。
在将kobject添加到kernel时,它的名称就已经被设定好了,代码中不该该直接操做kobject的名称。若是你必须为kobject更名,则调用kobject_rename()函数实现:
int kobject_rename(struct kobject *kobj, const char *new_name);
kobject_rename内部并不执行任何锁定操做,也没用什么时候名称是有效的概念。所以调用者必须本身实现完整性检查和保证序列性。有一个函数叫kobject_set_name(),可是它遗留了一些缺陷,目前正在被移除。若是你的代码须要调用这个函数,它是不正确的,须要被修正。能够经过kobject_name()函数来正确获取kobject的名称:
const char *kobject_name(const struct kobject * kobj);
有一个辅助函数用来在同一时间初始化和添加kobject,它被称做使人惊讶的是kobject_init_and_add():
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...);
它的参数和前面介绍的单独使用kobject_init()、kobject_add()这两个函数时同样
在注册完kobject结构以后,你须要向世界宣布,kobject已经被建立。这能够经过kobject_uevent()函数来实现:
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
若这个kobject是第一次被加到kernel里面,action参数应该设置为KOBJ_ADD。
kobject_uevent只能在kobject的全部属性或者子节点正确初始化完成以后调用,由于用户空间会在这个函数调用时立刻区查找它们。从kernel中移除kobject结构时(详细操做会在后面介绍),kobject 核心会自动建立KOBJ_REMOVE这个uevent,所以调用者不须要担忧须要手动去调用广播移除事件。
kobject结构的一个关键做用是做为它所嵌入对象的引用计数器,只要这个对象的引用还在,该对象(和支持它的代码)就必须存在。用来操做kobject引用计数的底层函数是:
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
成功调用kobject_get()函数会增长kobject的引用计数,而且返回kobject的指针。当一个对象的引用被释放时时,将会调用kobject_put函数来减少引用计数,而且有可能会释放这个对象。
记住,kobject_init将会将引用计数设置为1,所以当你建立一个kobject时,你应当确保在释放引用的时候调用kobject_put来讲释放这个引用。kobject是一个动态的概念,它们不能静态的声明或者保存在堆栈上;相反,必须动态的分配。将来的内核版本将会包含对静态建立的kobject的运行时检查,而且会警告开发者这个用法不合适。若是你使用kobject只是但愿为你的结构提供一个引用计数,那么请使用kref来代替,kobject有点大材小用了。对于如何使用kref结构的信息,请参考Linux内核源码树中的Documentation/kref.txt这个文件。
有些时候,开发者但愿的只是在sysfs中建立一个目录,而且不会被kset、show、store函数等细节的复杂概念所迷惑。这里有一个能够单首创建kobject的例外,为了建立这样一个入口,能够经过以下的函数来实现:
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
这个函数会建立一个kobject,并把它的目录放到指定父节点在sysfs中目录里面。能够经过以下的函数简单建立kobject相关联的属性:
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
或者
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
这两种方法都包含了属性类型,和经过kobject_create_and_add()建立的kobject类型;属性类型能够直接使用kobj_attribute类型,因此不须要特别建立自定义属性。读者能够经过samples/kobject/kobject-example.c中的模块例程,学习kobject和属性的实现。
前面的讨论中仍然缺失的一个重要部分是当一个kobject的引用计数为0时会发生什么。建立kobject的代码一般不知道何时会发生这件事,若是它知道,那在第一次使用kobject时使用引用计数就没有意义了。甚至,连生命周期可预测的对象也在sysfs被引入时变得更加复杂,由于内核的其余部分可能会引用任何在系统中已注册的kobject。
结论就是:kobject所保护的结构体在它的引用计数没到0以前不能够被释放。引用计数并不禁建立kobject的代码直接控制,所以这些代码必须在kobject的最后一个引用被释放时被异步的通知到。千万不能经过kfree直接去释放你经过kobject_add注册的kobject结构体,kobject_put才是惟一安全的方法。在kobject_init调用以后使用kobject_put来避免错误发生老是一个好的方法。
通知的机制经过kobject的释放函数法实现。一般这种函数有以下的实现形式:
void my_object_release(struct kobject *kobj)
{
struct my_object *mine = container_of(kobj, struct my_object, kobj);
/* Perform any additional cleanup on this object, then... */
kfree(mine);
}
一个重要的概念:每一个kobject必须有一个释放函数,而且这个kobject必须保持直到这个释放函数被调用到。若是这个条件不能被知足,则这个代码是有缺陷的。注意,假如你忘了提供释放函数,内核会提出警告的;不要尝试提供一个空的释放函数来消除这个警告,你会收到kobject维护者的无情嘲笑。
注意,kobject的名称在释放函数里仍是有效的,可是千万不要在释放函数里面修改kobject的名称,不然会致使kobject核心的内存泄漏,这会让人很是不高兴的。
有趣的是,释放函数并无保存在kobject结构自身里面。相反, 它被关联到ktype里,让咱们介绍下kobj_type这个结构体
struct kobj_type {
void (*release)(struct kobject *);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
这个结构体用于描述特定类型的kobject(或者,更准确的说是容器对象)。每一个对象都必须关联一个kobj_type结构体,当你调用kobject_init和kobject_init_and_add时必须传入一个kobj_type的指针。
kobj_type结构里的release域固然是针对这个kobject类型的释放函数指针。其余两个成员(sysfs_ops和default_attrs)控制对象在sysfs中时如何表述的,这部份内容这篇文章不会介绍到。
当kobject使用ktype这个属性类型注册到系统时,default_attrs指针包含的默认属性列表会自动为该kobject建立出来。
kset仅仅是一系列相互关联的kobject的集合。并无要求这些kobject有一样的ktype,可是若是没有一样的ktype,仍是要很是当心才行
kset的主要做用以下:
做为一个包含一组对象的容器,kset能够被内核用来追踪”全部块设备”或者”全部PCI设备驱动”。
kset也是sysfs中的子目录,全部跟kset相关联的kobject都会出如今这个目录里。每一个kset都内嵌了一个kobject结构,它能够是其余kobject的父节点,sysfs中的顶级目录结构都是这么组织的。
kset能够用于支持kobject的热插拔,而且对uevent事件如何上报到用户空间形成影响。
若用面向对象思想来看,kset是顶级的容器类;kset包含一个自身的kobject,可是这个kobject由kset代码来管理,不该该被其余用户操做到。
kset经过标准的内核链表来管理它的全部子节点,kobject经过kset成员变量指向它的kset容器。大多数状况下,kobject都是它所属kset(或者严格点,kset内嵌的kobject)的子节点。
由于kset内嵌了一个kobject结构,它应该动态的被建立,而永远不要静态或者在栈上声明,能够经过以下的函数来建立一个新的kset:
struct kset *kset_create_and_add(const char *name,
struct kset_uevent_ops *u,
struct kobject *parent);
当你结束kset的使用时,经过下面的函数来销毁它:
void kset_unregister(struct kset *kset);
kset的一个例子能够在内核源码树的samples/kobject/kset-example.c文件中找到。
若是一个kset但愿控制它所关联kobject的uevent操做,它可使用kset_uevent_ops结构来处理:
struct kset_uevent_ops {
int (*filter)(struct kset *kset, struct kobject *kobj);
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
filter函数用于让kset决定一个特定kobject的uevent是否被传递到用户空间,若该函数返回0,则该uevent将会被阻止。
name函数用于替代发送uevent到用户空间的kset的默认名字。默认状况下,该名称会和kset一致,但这个函数(name)存在的话,能够覆盖这个名字。
当uevent要被发送到用户空间时,uevent函数会被调用到,该函数容许将更多环境变量添加到uevent中。
你可能会问,一个kobject是如何添加到kset中去的,鉴于没有看到一个确切实现该功能的函数,答案是经过kobject_add函数来实现。当调用kobject_add来添加一个kobject时,须要传递一个指向该kobject所属的kset的kset参数,kobject_add会处理剩下的事情。
若是kobject没有设置父节点,它将会被添加到kset的目录底下。并非kset的全部成员都会在kset目录底下。若是在kobject添加以前就确认了父节点,在注册到kset时会添加到父节点的目录底下。
在kobject核心成功注册以后,必须在代码完成时清理掉这个kobject,能够经过kobject_put来完成。经过这个函数,kobject核心会自动清除为该kobject分配的全部内存。若已经为该对象发送过KOBJ_ADD这个uevent,对应的KOBJ_REMOVE uevent也在清理时被发送。另外,sysfs的其余清理工做也会为这个调用者启动。
若是你须要完成kobject的二级删除(表示销毁对象时不容许睡眠),能够尝试经过kobject_del从sysfs中注销kobject。这个函数会让kobject在sysfs中不可见,但此时它并无被清理掉,引用计数仍是不变。能够在后续的代码中调用kobject_put来完成清理kobject相关的内存。
若建立了循环引用,能够经过kobject_del函数来去掉对父节点的引用。这种方法在某些状况下是合法的,好比父节点引用到了子节点。循环引用必须经过一个明确的kobject_del调用来去除,这样释放函数将会被调用到,而且以前的循环引用将会释放掉。
能够经过samples/kobject/{kobject-example.c,kset-example.c}中的例程学习如何正确的使用kset和kobject。这些例程能够经过选择kernel的配置选项CONFIG_SAMPLE_KOBJECT来把它编译成模块实现。