Linux设备模型之kobject
阿辉原创,转载请注明出处
参考文档:LDD3-ch14、内核文档Documentation/kobject.txt,本文中使用到的代码均摘自Linux-3.4.75
--------------------------------------------------------------------------------------------------------------------
简要介绍
随着Linux内核的发展壮大,其支持的设备也越来越多,但一直没有一个很好的方法来管理慢慢增多的设备驱动。为了能够在内核中提供统一的机制来对设备进行分类,同时在更高的功能层面上描述这些设备,并使得这些设备对用户空间可见。从2.6开始,Linux内核引入一个新的设备模型来对系统结构做一般性的抽象描述,可以用于支持不同的任务需求,并提供了用户空间访问的接口。
对于驱动程序的作者来说,一般是不需要深入了解Linux设备模型的细节,而只需要理解设备模型的基本架构,懂得怎么使用设备模型提供的接口即可。因为Linux设备模型的代码已经处理了大部分模型相关的内容,并且目前看来,处理的还不错。但是,整体来说,理解Linux设备模型的内在实现原理对于学习内核驱动程序或者自己实现驱动程序大有好处,它可以让你对整体的把握,从一个宏观的角度来看问题。
接下来我会通过一系列的文章来介绍Linux设备模型,以从下到上的方式,从设备模型的底层数据结构讲起,并会逐步介绍如何通过底层的数据结构搭建Linux的设备模型,本文主要介绍Linux设备模型中的基础数据结构kobject
kobject简介
kobject是Linux设备模型的基础结构,其地位类似于面向对象中的基类,通过派生出其他的类来丰富Linux设备模型的结构,如device、kset等。具体方法就是将kobject结构嵌入到上层的数据结构中,这样如果使用了该上层结构,只要访问kboject成员就可以获得kboject结构。同样,若知道kobject结构的指针,可以通过container_of宏来找回包含该kobject的结构。
kobject结构定义于include/linux/kobject.h,如下:
1 struct kobject { 2 const char *name; 3 struct list_head entry; 4 struct kobject *parent; 5 struct kset *kset; 6 struct kobj_type *ktype; 7 struct sysfs_dirent *sd; 8 struct kref kref; 9 unsigned int state_initialized:1; 10 unsigned int state_in_sysfs:1; 11 unsigned int state_add_uevent_sent:1; 12 unsigned int state_remove_uevent_sent:1; 13 unsigned int uevent_suppress:1; 14 };
name: kobject的名称,它将以一个目录的形式出现在sysfs文件系统中
entry:list_head入口,用于将该kobject链接到所属kset的链表
parent:kobject结构的父节点
kset:kobject所属的kset
ktype:kobject相关的操作函数和属性。
sd:kobject对应的sysfs目录
kref:kobject的引用计数,本质上是atomic_t变量
state_initialize:为1代表kobject已经初始化过了
state_in_sysfs:kobject是否已经在sysfs文件系统建立入口
如下是struct device结构嵌入kobject结构的简单例子
1 struct device { 2 … 3 struct kobject kobj; 4 const char *init_name; /* initial name of the device */ 5 const struct device_type *type; 6 … 7 void (*release)(struct device *dev); 8 };
kobject结构在sysfs中的入口是一个目录,因此添加一个kobject的动作也会导致在sysfs中新建一个对应kobject的目录,目录名是kobject.name。该入口目录位于kobject的parent指针的目录当中,若parent指针为空,则将parent设置为该kobject中的kset对应的kobject(&kobj->kset->kobj)。如果parent指针和kset都是NULL,此时sysfs的入口目录将会在顶层目录下(/sys)被创建,不过不建议这么做。详细的创建目录过程可以看后面介绍的kobject_init_and_add函数的介绍。
Note:sysfs的简要介绍请查看 内核文档翻译中的sysfs - The filesystem for exporting kernel objects 这篇文章
kobject初始化
按照LDD3-ch14的建议,需要对kobject做清零初始化,然后再使用,否则可能会出现一些奇怪的错误,通常使用memset实现。
kobject常用的操作函数如下:
1 void kobject_init(struct kobject *kobj, struct kobj_type *ktype) 2 int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...) 3 int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...) 4 struct kobject *kobject_create(void) 5 struct kobject *kobject_create_and_add(const char *name, struct kobject *parent) 6 int kobject_set_name(struct kobject *kobj, const char *fmt, ...)//设置kobject名称
可以通过kobject_init初始化kobject,然后再通过kobject_add将这个kobject添加到kset中。或者也可以直接通过kobject_init_and_add 函数完成初始化和添加的任务,查看Linux源码,它只是两个函数的组合而已,目前我看过的驱动源码中,大部分的实现都是通过kobject_init_and_add函数来实现的。
kobject_create_and_add和前两种实现的差别只是多了一步分配kobject的过程,其他的内容都一样,典型的应用可以看linux电源管理源码中power_kobj的生成(kernel/power/main.c)。
我们从kobject_init_and_add函数开始分析kobject的初始化过程,这个函数在lib/kobject.c中定义,如下:
1 /** 2 * kobject_init_and_add - initialize a kobject structure and add it to the kobject hierarchy 3 * @kobj: pointer to the kobject to initialize 4 * @ktype: pointer to the ktype for this kobject. 5 * @parent: pointer to the parent of this kobject. 6 * @fmt: the name of the kobject. 7 * 8 * This function combines the call to kobject_init() and 9 * kobject_add(). The same type of error handling after a call to 10 * kobject_add() and kobject lifetime rules are the same here. 11 */ 12 int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, 13 struct kobject *parent, const char *fmt, ...) 14 { 15 va_list args; 16 int retval; 17 18 kobject_init(kobj, ktype); 19 20 va_start(args, fmt); 21 retval = kobject_add_varg(kobj, parent, fmt, args); 22 va_end(args); 23 return retval; 24 }
可以看出来,该函数分为两部分:首先通过kobject_init初始化kobject结构,然后利用kobject_add_varg将kobject添加到设备模型的体系结构中去。我们先来看看kobject_init函数
1 /** 2 * kobject_init - initialize a kobject structure 3 * @kobj: pointer to the kobject to initialize 4 * @ktype: pointer to the ktype for this kobject. 5 * 6 * This function will properly initialize a kobject such that it can then 7 * be passed to the kobject_add() call. 8 * 9 * After this function is called, the kobject MUST be cleaned up by a call 10 * to kobject_put(), not by a call to kfree directly to ensure that all of 11 * the memory is cleaned up properly. 12 */ 13 void kobject_init(struct kobject *kobj, struct kobj_type *ktype) 14 { 15 char *err_str; 16 if (!kobj) { 17 err_str = "invalid kobject pointer!"; 18 goto error; 19 } 20 if (!ktype) { 21 err_str = "must have a ktype to be initialized properly!\n"; 22 goto error; 23 } 24 if (kobj->state_initialized) { 25 /* do not error out as sometimes we can recover */ 26 printk(KERN_ERR "kobject (%p): tried to init an initialized " 27 "object, something is seriously wrong.\n", kobj); 28 dump_stack(); 29 } 30 kobject_init_internal(kobj); 31 kobj->ktype = ktype; 32 return; 33 34 error: 35 printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str); 36 dump_stack(); 37 }
主要是调用kobject_init_internal并设置kobj->ktype,当然要保证传递给kobject_init的kob、ktype参数不为空
Kobject_inint_internal函数的定义同样在lib/kobject.c,如下:
1 static void kobject_init_internal(struct kobject *kobj) 2 { 3 if (!kobj) 4 return; 5 kref_init(&kobj->kref); 6 INIT_LIST_HEAD(&kobj->entry); 7 kobj->state_in_sysfs = 0; 8 kobj->state_add_uevent_sent = 0; 9 kobj->state_remove_uevent_sent = 0; 10 kobj->state_initialized = 1; 11 }
这里是对kobject成员变量的初始化,初始为默认的状态;kref_init函数只是通过atomic_set将kobj->kref->refcount设置为1
kobject_add_varg函数同样定义于lib/kobject.c文件中:
1 static int kobject_add_varg(struct kobject *kobj, struct kobject *parent, 2 const char *fmt, va_list vargs) 3 { 4 int retval; 5 6 retval = kobject_set_name_vargs(kobj, fmt, vargs); 7 if (retval) { 8 printk(KERN_ERR "kobject: can not set name properly!\n"); 9 return retval; 10 } 11 kobj->parent = parent; 12 return kobject_add_internal(kobj); 13 }
该函数调用kobject_set_name_vargs解析可变参数并设置kobject.name的值,然后设置kobj->parent,最后通过kobject_add_internal添加kobject
kobject_add_internal函数定义于lib/kobject.c,主要作用是设置kobject的父节点、kset并创建kobject在sysfs中的目录
1 static int kobject_add_internal(struct kobject *kobj) 2 { 3 int error = 0; 4 struct kobject *parent; 5 6 if (!kobj) 7 return -ENOENT; 8 9 if (!kobj->name || !kobj->name[0]) { 10 WARN(1, "kobject: (%p): attempted to be registered with empty " 11 "name!\n", kobj); 12 return -EINVAL; 13 } 14 15 parent = kobject_get(kobj->parent); 16 17 /* join kset if set, use it as parent if we do not already have one */ 18 if (kobj->kset) { 19 if (!parent) 20 parent = kobject_get(&kobj->kset->kobj); 21 kobj_kset_join(kobj); 22 kobj->parent = parent; 23 } 24 25 pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n", 26 kobject_name(kobj), kobj, __func__, 27 parent ? kobject_name(parent) : "<NULL>", 28 kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>"); 29 30 error = create_dir(kobj); 31 if (error) { 32 kobj_kset_leave(kobj); 33 kobject_put(parent); 34 kobj->parent = NULL; 35 36 /* be noisy on error issues */ 37 if (error == -EEXIST) 38 WARN(1, "%s failed for %s with " 39 "-EEXIST, don't try to register things with " 40 "the same name in the same directory.\n", 41 __func__, kobject_name(kobj)); 42 else 43 WARN(1, "%s failed for %s (error: %d parent: %s)\n", 44 __func__, kobject_name(kobj), error, 45 parent ? kobject_name(parent) : "'none'"); 46 } else 47 kobj->state_in_sysfs = 1; 48 49 return error; 50 }
这个函数首先判断kobject.name是否有成功设置,接下来的代码,判断kobject是否设置了父节点,若没有父节点并且若kobj->kset存在,就将kobj->kset设置为当前kobject的父节点,并增加引用计数。最后,通过create_dir(kobj)创建在sysfs中的节点。若当前kobject的kobj->parent不存在并且kobj->kset为空,则会在/sys目录下为该kobject创建一个子目录。
create_dir函数会调用sysfs_create_dir为kobject创建sysfs文件系统中的目录。该函数定义于kobject.c,kernel中有几个该函数的同名函数,参数类型不同,莫要搞混了
1 static int create_dir(struct kobject *kobj) 2 { 3 int error = 0; 4 if (kobject_name(kobj)) { 5 error = sysfs_create_dir(kobj); 6 if (!error) { 7 error = populate_dir(kobj); 8 if (error) 9 sysfs_remove_dir(kobj); 10 } 11 } 12 return error; 13 }
kobject引用计数
Kobject的一个重要属性在于它的引用计数,只要kobject的引用计数不为0,kobject对象就必须存在。Kobject中保存引用计数的变量时kref,它本质上是atomic_t变量。驱动模型底层对引用计数控制的函数有
1 struct kobject *kobject_get(struct kobject *kobj) 2 void kobject_put(struct kobject *kobj)
kobject_get用于增加kobject的引用计数,并且返回kobject指针,调用该函数的话必须检查返回值,否则可能会引用到已被销毁的kobject,造成竞争的发生。
1 /** 2 * kobject_get - increment refcount for object. 3 * @kobj: object. 4 */ 5 struct kobject *kobject_get(struct kobject *kobj) 6 { 7 if (kobj) 8 kref_get(&kobj->kref); 9 return kobj; 10 }
Kobject_put用于减少kobject的引用计数
1 /** 2 * kobject_put - decrement refcount for object. 3 * @kobj: object. 4 * 5 * Decrement the refcount, and if 0, call kobject_cleanup(). 6 */ 7 void kobject_put(struct kobject *kobj) 8 { 9 if (kobj) { 10 if (!kobj->state_initialized) 11 WARN(1, KERN_WARNING "kobject: '%s' (%p): is not " 12 "initialized, yet kobject_put() is being " 13 "called.\n", kobject_name(kobj), kobj); 14 kref_put(&kobj->kref, kobject_release); 15 } 16 }
当引用计数为0的时候,需要调用release函数将kobject释放掉,对于每一个kobject,都必须有一个release函数来释放kobject结构。Linux设备模型中默认的release函数不在kobject对象中,而是在kobj_type这个结构中,kobj_type结构定义如下:
1 struct kobj_type { 2 void (*release)(struct kobject *kobj); 3 const struct sysfs_ops *sysfs_ops; 4 struct attribute **default_attrs; 5 const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); 6 const void *(*namespace)(struct kobject *kobj); 7 };
此时release函数的调用路径如下,就不一一展开介绍了
kobject_put->kref_put->kref_sub->kobject_release->kobject_cleanup->(kobj_type->release)
kobj_type
这个结构在之前介绍kobject时有介绍到它的release成员,接下来我们对它的其他成员做一个详细介绍
sysfs_ops:是struct sysfs_ops类型的常指针,用于实现kobject中属性(struct attribute)的操作,定义于include/linux/sysfs.h文件中,如下:
1 struct sysfs_ops { 2 ssize_t (*show)(struct kobject *, struct attribute *,char *); 3 ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t); 4 const void *(*namespace)(struct kobject *, const struct attribute *); 5 };
当用户空间读取属性的时候,便会调用到属性的show函数;而store函数则用于从用户空间获取新的属性值并保持,注意应用程序应该要有该属性的些权限才可以调用到store函数,并且最好在store函数中检查下用户空间写入值的合法性。
default_attrs:保存了kobject默认的属性列表,属性列表中的每个属性都会在kobject目录下生成一个对应的属性文件,属性结构在lib/kobject.h中定义,如下:
1 struct attribute { 2 const char *name; 3 umode_t mode; 4 #ifdef CONFIG_DEBUG_LOCK_ALLOC 5 struct lock_class_key *key; 6 struct lock_class_key skey; 7 #endif 8 };
name:属性名,其对应属性文件的名字,
mode:访问模式,用于指定用户空间访问属性文件的权限
关于用户空间读写属性文件是怎么调用到sysfs_ops->show和sysfs_ops->store的原理,主要涉及到的是sysfs内容,目前的话,只要记住读写属性文件会调用到sysfs_ops->show和sysfs_ops->store即可。
除了kobject的默认属性列表,程序员还可以感觉需要添加或者删除kobject的属性。可以通过如下的函数添加kobject属性:
1 /** 2 * sysfs_create_file - create an attribute file for an object. 3 * @kobj: object we're creating for. 4 * @attr: attribute descriptor. 5 */ 6 int sysfs_create_file(struct kobject * kobj, const struct attribute * attr) 7 { 8 BUG_ON(!kobj || !kobj->sd || !attr); 9 10 return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR); 11 }
只需要填充attribute结构并传递给sysfs_create_file即可,如果该函数返回0,说明创建属性成功,否则将返回对应的错误码。
可以通过如下的函数来删除属性:
1 /** 2 * sysfs_remove_file - remove an object attribute. 3 * @kobj: object we're acting for. 4 * @attr: attribute descriptor. 5 * 6 * Hash the attribute name and kill the victim. 7 */ 8 void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr) 9 { 10 const void *ns; 11 12 if (sysfs_attr_ns(kobj, attr, &ns)) 13 return; 14 15 sysfs_hash_and_remove(kobj->sd, ns, attr->name); 16 }
这个函数调用之后,属性文件将不会再出现在kobject在sysfs入口中录中
kobject实例
本想自己写一个kobject使用的例子来和大家分享,不想前两天翻译内核、kobject.txt文档发现内核中有现成的kobject例子,就在samples/kobject/目录下的kobject-sample.c文件中。
这个例子的作用是在/sys/kernel目录下新建一个kobject-example的目录,并在该目录下生成baz、bar、foo这三个属性文件。详细代码可以在内核源码目录下找到。
使用该例子需要在kernel的配置中选择CONFIG_SAMPLE_KOBJECT,并把它编译成模块,详细步骤如下:
make menuconfig
kernel hacking->Sample kernel code->build kernel object
选择编译成模块即可
注意build module的kernel版本要和目前使用的kernel版本一致,否则可能在insmod时会出错,我住Ubuntu中试的时候就出现过因为kernel版本不对导致insmod错误
insmod成功后,就会发现在/sys/kernel目录下多了一个kobject_example子目录,目录中包含三个属性文件bar、baz、foo,该模块的使用如下所示: