分类: 嵌入式
2013-01-15 17:48:03
sysfs是kobject的表达,所以这里翻译了Documention下的kobjct.txt,并加上了一些自己的注释,这样基本就对kobject和sysfs有了一个比较深刻的理解,我们可以简单的将sysfs看成最bottom的操作,然后kobject的想关操作是架构在sysfs之上,再然后kobject和attribute所嵌入的结构体再构成上一层结构来操作kobject,最后就实现了kernel内部各个portion通过sysfs与userspace进行交互。
要想了解linux驱动模型,以及驱动模型之上的kobject抽象,有一个难点就是我们没有一个明确的入手点。要想了解kobject我们必须了解很多不同的类型,而这些类型之间又会交叉引用,所以为了让理解更容易我们从多方面入手,首先看一些可能模糊不清的概念,然后随着深入我们会加入细节,在最后我们会定义一些我们工作上需要使用的术语。
kobject是一个结构体,它包含name,reference count以及指向一个parent的指针(这个指针等于就是将kobject加入了一个体系结构中),一个特定的类型,以及在sysfs文件系统中的一个相关表达。kobject一般不会单独使用,大部分的时候它会被嵌入其他的结构体,而这些结构体才是我们代码中真正需要的。一个结构体最多只能嵌入一个kobject,不然reference count就会乱掉,出错。
(PS:实际上我们可以理解这个kobject实际上是我们与sysfs进行交互的一个接口,我们在设备驱动以及总线相关的结构体中嵌入这个kobject,那么我们的设备驱动就可以通过sysfs与userspace进行交互了。虽然sysfs是kobject的表达,但我们可以近似的将kobject看成是架在sysfs上面的,在kobject上面封装的一系列操作实际上到底层都是调用的sysfs的公共接口)
struct kobject {
const char *name;
struct list_head entry;
struct kobejct *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigend int state_remove_uevent_sent:1;
}
ktype定义了嵌入了kobject的结构体的类型,每一个嵌入了kobject的结构体都必须有一个想对应的ktype,ktype定义了这个kobject创建和销毁时候需要做的事情。
struct kobj_type {
void (*release)(struct kobject *kobj);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
}
(PS: 看过代码以后感觉这个定义了kobject创建和销毁时候应该做的事情实际上并没有,kobj_type结构体有3个成员,release函数指针由自己定义来实现,定义了引用计数为0的时候对kobject的处理;sysfs_ops里面有两个成员变量分别是show和store函数指针,分别是对attribute文件进行读写时候的处理,也需要我们来实现;attribute_attrs是一个attribute数组,是这个kobject被创建的时候会自动创建的attributes)
kset说穿了就是一个kobjects的集合,这些kobjects可以有相同的ktype也可以不同ktype。kset应该来说是一个容器类型,用来存放kobjects。
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
struct kset_uevent_ops *uevent_ops;
}
(PS: kset我们看kernel的注释,它定义了attribute callback以及一个kobject可能发生的event,从结构提本身来说我们可以看到它本身有一个链表来记载它所包含的kobject,而且它本身包含一个kobject,对于事件的处理应该是这个uevent_ops了,从官方的解释来看(kset的一组uevent的相关操作)它就是涉及对uevent的处理,这里还不知道它具体是如何处理的)
我们知道单独使用的kobject在kernel里面是极少的,kobject主要通过嵌套的方式用来控制对一个大的,某个领域的结构体的访问。如果有面向对象的思想的话,我们可以将kobject理解为一个最顶端的抽象类,其他结构体都继承它的基础特性,并加入自己的特性。c语言没有继承的技术,但是可以通过结构体嵌套的方式实现类似的逻辑。
举个例子,比如一个UIO设备拥有一块内存区域,这个内存区域的结构体的定义如下:
struct uio_mem {
struct kobject kobj;
unsigned long addr;
unsigned long size;
int memtype;
void __iomem *internal_addr;
}
(PS: 在实际编程中我们经常需要完成从kobject,及其所嵌入的结构体之间的转换,比如说知道kobject实体,我们要找到它所嵌入的结构体实体,从上面的例子就是我们知道kobj的时候,我们要找到它所在的uio_mem实体。这里就涉及一个很有用的宏container_of(pointer, type, member),这个宏定义在linux/kernel.h里面,第一个参数就是指向kobject的指针,type是指嵌入kobject的结构体类型,member就是在结构体类型里面kobject的成员名,以上面的uio_mem为例就是:
sturct uio_mem *u_mem = container_of(kp, struct uio_mem, kobj) )
我们使用下面的函数来初始化一个kobject,其中ktype是必须的:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
(PS: 我们可以简单看看这个init干了什么事情,首先将kobj->kref置为1,也就是初始化的kobject的引用计数为1;然后kobj->entry这个双向链表都指向自身;然后就是对标志位的一系列初始化,主要是初始化为真,在sysfs为假,相关的event也全为0)
初始化以后我们就可以通过kobject_add将我们的kobject加入sysfs文件系统了:
int kobject_add(struct kobject *kobj, struct kobject *parent, const char * fmt, ……);
(PS: 这个函数第一个参数是要加入sysfs的kobject;第二个参数是kobject的parent,可以为空,如果为空的话首先判断kset是否为空,如果不为空则parent设置为kset,如果都为空则默认这个kobject会被建立在sysfs的顶层目录下;fmt是定义了kobject的name。同样我们可以看看这个add的具体过程,首先根据fmt设置kobject的name成员,然后kset存在的话会将这个kobject加入kset,加入kset的过程就是将kobject的entry放入kobj->kset->list这个双向循环链表中,由此我们可以看到kobject的entry的功能就是放入kset的list链表中。这里还会设置kobject的parent,然后就会调用sysfs_create_dir来创建目录,创建目录的过程会allocate一个sysfs_dirent的结构体,从kernel对这个结构体的描述来看,sysfs上面每一个结点都是使用这个结构体来描述的,由此我们可以理解kobject里面的sd成员[这里的sd个人猜测应该是sysfs directory吧]变量作用了,他是在注册到sysfs的时候由sysfs来创建的,创建完目录以后如果ktype里面有attribute数组的话,还会创建默认的attribute文件。)
kobject的目录名不能粗暴的直接去改kobject的name成员,应该使用相应的API去改:
int kobject_rename(struct kobject *kobj, const char *new_name);
(PS: 我们看过这个函数的实现后就清楚,它会先调用sysfs_rename_dir来修改目录名以后再才会修改kobject的name成员,最后它还会将发生改变的目录加上目录改变的事件以环境变量的方式通过kobjec_uevent_env()发送出去。)
这个函数没有锁操作,也不会检查name的有效性,所以调用这个函数的时候我们要进行相应的合法性检测,并加入自己的互斥操作。另外我们也可以通过下面的函数来取得kobject的name成员:
const char *kobject_name(const struct kobject *kobj);
另外这里还一个对kobject的init和add合二为一的操作:
int kobject_init_and_add(sturct kobject *kobj, struct kobj_type *ktype,
struct kobjcet *parent, const char * fmt, ……)
当我们将一个kobject注册到kobject core以后,我们可能需要告诉其他模块或者userspace我们已经注册了一个kobject,这里有一个函数可以帮我们实现这一点:
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
当我们第一次add一个kobject的时候可以用KOBJ_ADD事件,但是这个事件的通知必须是在这个kobject所有的attibure或者它的子目录都创建以后才行,因为一旦调用这个函数用户空间就知道了,就可能会去操作这个kobject下面的attribute文件。当这个kobject被删除的时候,KOBJ_REMOVE事件会自动产生。
(PS: 把这个函数基本过了一遍,大致是这样的,首先找到kobj属于哪个kset的,如果当前kobj没有kset就会寻找他父kobje的kset,直到找到kset为止,没有找到就会报错;找到kset以后会看它的uevent_ops里面的filter指针是否为空,不为空的就话就会调用这个函数,如果这个函数返回0的话说明uevent被过滤掉了,会直接返回;然后会调用uevent_ops里面的name函数来取得subsystem的name,如果name指针为空就会直接使用kset的name作为subsystem的name,如果name的值为空的话会直接返回;然后会申请一个kobj_uevent_env来存放环境变量,在这里先获得kobj所在的目录,这个目录是从sysfs根目录一直到kobj的绝对目录,然后就会将前面我们获得的kobj的根目录,subsystem的name,以及我们的action都会放入kobj_uevent_env这个结构体中;完成上面的步骤以后还会判断uevent_ops的uevent指针是否为空,不为空的话会调用这个函数,这个函数的功能应该是在前面的kobj_uevent_env加入一些kset本身的uevent;如果是KOBJ_ADD或者KOBJ_REMOVE还会修改kobj内部的标志位;最后就是在env结构体中加入一个uevent系列号,然后根据CONFIG_NET是否使用将env的内容使用netlink message的方式广播给userspace,最后还有一个uevent_helper,没太整明白这个意思,应该来说是通过call_usermodehelper这个函数来开启uevent_helper这个进程来处理要上传的事件,uevent_helper默认是sbin/hotplug。关于这个hotplug的东西,会在以后的文档中专门讲一下的。经过这个过程我们大致可以了解kset里面的uevent_ops的几个函数指针的功能,分别是过滤事件,获取subsystem的name,加入这个kset特定的uevent。)
kobject一个很核心的功能就是计算它所嵌入的结构体的引用计数,只要引用计数不为0这个结构体就不会被释放,下面的API分别是增加和减少引用计数的,分别将引用计数加1和减1:
struct kobject *kobject_get(struct kobjcet *kobj);
void kobject_put(sturct kobject *kobj);
我们应该养成很好的习惯,要获取一个kobject的时候使用get,不使用的时候put。
因为kobject是动态的可能涉及到共享,所以不要用静态的方式声明一个kobject,而是使用动态分配的方式申请kobject。
如果仅仅是想在sysfs里面创建一个目录,而不用管什么kset,show/store以及其他细节,可以通过下面的方式:
struct kobject *kobject_create_and_add(char *name, struct kobject *parent)
然后我们可以直接调用sysfs的API的方式在这个目录下创建属性文件:sysfs_create_file/sysfs_create_group
我们可以看一个例子:samples/kobject/kobject-example.c
(PS: 这个example没有什么好说的,唯一要注意的是_ATTR()这个宏,写代码的时候应该会经常用到,主要用来对自己定义的attribute结构体进行赋值,我们知道kobject一般不会单独使用,attribute也一样,一般会将attribute结构体嵌入到其他结构体里面,比如说什么device_attribute, kobj_attribute等等,一般都是嵌入attribute结构体以后加上两个对其操作的函数,一般是show和store,而这个宏就是对这些结构体成员赋值用的。)
到现在为止我们还漏掉了一个重要的事情没讲,那就是一个kobject的引用为0的时候会发生什么事情。创建kobject的代码并不知道kobject的引用什么时候会变成0,当这个kobject注册到sysfs并且有其他的客人呢了模块来引用它的时候,这个情况会变得更复杂。当一个结构体内部的kobject引用不为0的时候,我们不能够认为的free掉,而应该使用kobject_put来操作它。当一个kobject的引用为0的时候会自动调用ktype里面的release函数,所以我们必须实现我们的ktype里面的release函数。一般来说这些release函数都有如下面一样类似的格式:
void my_object_releas(struct kobject *kobj){
struct my_object *mine = container_of(kobj, sturct my_object, kobj);
kfree(mine);
}
每个ktype必须实现这个函数,如果不实现你就等着kernel kobject的代码维护者来A你吧~
记住不要在release这个回调函数里面修改kobject的name成员变量,否则会造成内存泄露。
kset是一个kobjects的集合,没有限制说他们必须是同样的ktype,但是如果不是相同的ktype在操作的时候必须万分小心。kset一般来说完成3个功能:1、它是一个kobject包,在kernel里面可以用来跟踪“所有的块设备”或者“所有的pci设备驱动”;2、kset本身也是sysfs里面的一个子目录,每个keset内嵌一个kobject,可以看成是这个kset->list里面所有的kobjects的parent,kernel sysfs体系中的最顶层目录就是这么构成的;kset支持kobject的热拔插,而且会影响到报告到userspace的uevent。在面向对象的概念里,我们可以将kset看成一个最顶层的容器类。kset使用kernel的标准链表来链接它下面所有的kobjects,kobjtets通过kset成员指向他们的kset,在大多数的情况下kobjects的kset就是他们的parent。
(PS: 这里虽然说了那么多关于kset的东西,但是我们分析它这个结构体就可以看出,它的主要作用除了本身是一个包含目录的目录之外,其他的就是影响到uevent的发送,前面看kobject_uevent的时候我们已经分析过了)
因为kset包含一个内嵌的kobject,所以kset也必须动态分配,而不能用静态的方式创建。我们可以通过下面的方式来创建一个kset:
struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *u, struct kobject *parent);
(PS: 同样我们分析这个函数,实际上是由create和register两部分组成的,create一个kset比较简单,就是allocate一个结构体,值得注意的是对内嵌的kobject的赋值,name和parent是根据参数来的,另外内嵌kobject的ktype是固定的kset_ktype,这个kset_type定义了release指针和sysfs_ops,另外内嵌kobject的kset为NULL,也就是说kset类型没有默认的attribute文件,也不再属于哪个kset,且对其的sysfs_ops以及release都是固定的。然后register的过程也是比较简单的先初始化内部的list,然后将内嵌的kobject加入sysfs,最后会自动产生一个KOBJ_ADD事件。综上我们可以知道kset注册的过程基本上是kobject一样的,也就是说其本质上还是还是一个kobject,只是是一个特殊的kobject而已)
当我们不再使用kset的时候可以使用void kset_unregister(struct kset *kset);来销毁它。
(PS: 时间上这个unregister就是将其内嵌的kobject调用一下kobject_put()。从这点上来看我们更可以说所谓kset就是一个特殊的kobject而已,不过是多了个链表来包含一组kobjects)
前面提到了这个kset_uevent_ops结构体,kset通过这个结构体来控制kobject相关的uevent操作,下面我们就详细解释下这个结构体的几个参数:
struct kset_ueven_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 export到userspace的uevent,如果这个函数返回0那么uevent就不会报到用户空间。
name函数可以覆盖默认的要报到用户空间的kset的内嵌kobject的name,一般来说报到用户空间的name是kset的内嵌kobject的name,我们可以在这里修改这个默认值。
uevent函数在uevent被报告到用户空间的时候会被调用,可以允许加入更多的环境变量到env结构体中。
一个kobject一般在其kset子目录下,但并不是确定的,因为确定一个object在哪个目录首先是判断parent,然后再判断kset。
一个kobject被成功注册到kobject core以后,如果我们的代码不再需要他们的时候我们要删除它。一般来说我们可以使用kobjet_put()来实现这一点。kobject core会自动在引用为0的时候调用release函数来删除相对应的结构体,并同时exoport出KOBJ_REMOVE这个消息。如果你需要分两步来删除一个kobject(删除kobject的时候不能进入休眠),你可以首先使用kobject_del()来将一个kobject从sysfs注销,这样kobject对用户空间就不可见了,但这个时候koject的引用计数是没有变的。然后最后再调用kobjct_put来最终删除kobject。
在环形引用的情况下,可以使用kobject_del()函数来减去对parent kobject的引用。在某些情况下parent object引用它的child object是有效的,在这种情况下必须使用kobject_del()来破除这种环形引用,从而调用release函数删除父子kobject。
(PS: 我们可以具体看看kobject_del的实现,它首先调用sysfs_remove_dir来删除kobject的目录,然后将kobj->state_in_sysfs置为0,然后将kobject从其kset的list中删除,最后会调用kobject_put(kobj->parent);由此我们就看到kobject的功能就是将kobject从sysfs和kset->list中删除,并降低kobj->parent的引用计数,所以它可以用来消除环形引用)
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yili_xie/archive/2010/08/24/5835935.aspx