内核对象kobject和sysfs(3)——kobj分析

时间:2023-03-08 21:56:00

内核对象kobject和sysfs(3)——kobj分析


在分析kobj之前,先总结下kobj的功能:

  1. 实现结构的动态管理;
  2. 实现内核对象到sysfs的映射;
  3. 实现自定义属性的管理。

关注一下kobj的结构:

struct kobject {
const char *name;// 该内核对象的名称
struct list_head entry;// 链入kset的连接件
struct kobject *parent;// 指向父对象,可以为空
struct kset *kset; // 指向的内核对象集合,可以为空
struct kobj_type *ktype; // 该内核对象使用的操作集合
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref; // 该内核对象的引用计数
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};

kobject内的注释已经注释在页面上,在初次接触的时候,先关注其中name,parent,ktype, kref这三个域即可。下面详细分析:

  • name是该内核对象的名称,在其向sysfs注册的时候,显示的目录的名字;
  • parent指示了该内核对象在sysfs中的位置,如果有父类,则目录会创建在对应的父类下,如果没有,则创建在/sysfs下;
  • ktype包含了该内核对象的操作方法,包括前面提及的kref的自定义释放函数和自定义属性操作;

下面给出ktype的及其内部相关结构:

struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
struct attribute {
const char *name;
umode_t mode;
};

我们先大致看一下这三个结构。不做深入分析,先看一下如何操作kobj。

kobj的操作和kref的流程大致相似,包括初始化、注册、注销。

先来看看初始化:

 325 void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
326 {
327 char *err_str;
328
329 if (!kobj) {
330 err_str = "invalid kobject pointer!";
331 goto error;
332 }
333 if (!ktype) {
334 err_str = "must have a ktype to be initialized properly!\n";
335 goto error;
336 }
337 if (kobj->state_initialized) {
338 /* do not error out as sometimes we can recover */
339 printk(KERN_ERR "kobject (%p): tried to init an initialized "
340 "object, something is seriously wrong.\n", kobj);
341 dump_stack();
342 }
343
344 kobject_init_internal(kobj);
345 kobj->ktype = ktype;
346 return;
347
348 error:
349 printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
350 dump_stack();
351 }
352 EXPORT_SYMBOL(kobject_init); 187 static void kobject_init_internal(struct kobject *kobj)
188 {
189 if (!kobj)
190 return;
191 kref_init(&kobj->kref);
192 INIT_LIST_HEAD(&kobj->entry);
193 kobj->state_in_sysfs = 0;
194 kobj->state_add_uevent_sent = 0;
195 kobj->state_remove_uevent_sent = 0;
196 kobj->state_initialized = 1;
197 }

可以看到,kobject_init对应kref的kref_init。函数有两个参数,一个是待初始化的内核对象,一个是该对象的操作集合。关键的步骤在344~345行。在kobject_init_internal里我们可以看到,他对内嵌的kref进行了初始化,这和上一篇描述的用法基本相同。其他的代码都是一些状态的初始化,暂且略过。不过值得提及的是INIT_LIST_HEAD宏。该宏在内核中广泛用到,用于初始化一个通用链表的头结点,将前后都指向自己。内核通过通用链表的连接件,将数据结构链入链表中加以管理,这里不再赘述。回到345行,其实发现kobject_init操作对kobj_type 的内容并不做检查。

 684 void kobject_put(struct kobject *kobj)
685 {
686 if (kobj) {
687 if (!kobj->state_initialized)
688 WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
689 "initialized, yet kobject_put() is being "
690 "called.\n", kobject_name(kobj), kobj);
691 kref_put(&kobj->kref, kobject_release);
692 }
693 }
694 EXPORT_SYMBOL(kobject_put);

kobject_put是内核对象的释放函数,对应于kref的kref_put。可以看到,kobject_put仅仅是对kref_put的一个封装而已,向kref机制注册了kobject_release函数。

 663 static void kobject_release(struct kref *kref)
664 {
665 struct kobject *kobj = container_of(kref, struct kobject, kref);
666 #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
667 unsigned long delay = HZ + HZ * (get_random_int() & 0x3);
668 pr_info("kobject: '%s' (%p): %s, parent %p (delayed %ld)\n",
669 kobject_name(kobj), kobj, __func__, kobj->parent, delay);
670 INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);
671
672 schedule_delayed_work(&kobj->release, delay);
673 #else
674 kobject_cleanup(kobj);
675 #endif
676 }

由于向kref注册的函数参数只能是kref,所以必须通过container_of宏来获取到封装该kref的kobject,这也是上一篇中说明kref必须是内嵌的原因之一。只有内嵌结构,才可以使用container_of宏进行操作。container_of宏在此不再赘述。

获取到kobj的地址后,在674行释放资源。

初学者看到这里,不妨停下来,也许你会发现,kobject实际上,从这两个函数看来。就是上一篇我们自己写的内核模块的一个更专业版本。kobj_type 在这两个函数里,也就是赋值的作用,并没有起到真正的功能。因此,也许我们可以把上一篇的代码改进一下,写一个不提供任何功能的内核模块,仅仅是为了验证一下kobj拥有kref的功能,不过需要注意的是,一定要实现其中的release函数,这一点在后面详细说明。

验证之后,分析kobject_add:

 394 int kobject_add(struct kobject *kobj, struct kobject *parent,
395 const char *fmt, ...)
396 {
397 va_list args;
398 int retval;
399
400 if (!kobj)
401 return -EINVAL;
402
403 if (!kobj->state_initialized) {
404 printk(KERN_ERR "kobject '%s' (%p): tried to add an "
405 "uninitialized object, something is seriously wrong.\n",
406 kobject_name(kobj), kobj);
407 dump_stack();
408 return -EINVAL;
409 }
410 va_start(args, fmt);
411 retval = kobject_add_varg(kobj, parent, fmt, args);
412 va_end(args);
413
414 return retval;
415 }
416 EXPORT_SYMBOL(kobject_add); 354 static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
355 struct kobject *parent,
356 const char *fmt, va_list vargs)
357 {
358 int retval;
359
360 retval = kobject_set_name_vargs(kobj, fmt, vargs);
361 if (retval) {
362 printk(KERN_ERR "kobject: can not set name properly!\n");
363 return retval;
364 }
365 kobj->parent = parent;
366 return kobject_add_internal(kobj);
367 } 200 static int kobject_add_internal(struct kobject *kobj)
201 {
202 int error = 0;
203 struct kobject *parent;
204
205 if (!kobj)
206 return -ENOENT;
207
208 if (!kobj->name || !kobj->name[0]) {
209 WARN(1, "kobject: (%p): attempted to be registered with empty "
210 "name!\n", kobj);
211 return -EINVAL;
212 }
213
214 parent = kobject_get(kobj->parent);
215
216 /* join kset if set, use it as parent if we do not already have one */
217 if (kobj->kset) {
218 if (!parent)
219 parent = kobject_get(&kobj->kset->kobj);
220 kobj_kset_join(kobj);
221 kobj->parent = parent;
222 }
223
224 pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
225 kobject_name(kobj), kobj, __func__,
226 parent ? kobject_name(parent) : "<NULL>",
227 kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
228
229 error = create_dir(kobj);
230 if (error) {
231 kobj_kset_leave(kobj);
232 kobject_put(parent);
233 kobj->parent = NULL;
234
235 /* be noisy on error issues */
236 if (error == -EEXIST)
237 WARN(1, "%s failed for %s with "
238 "-EEXIST, don't try to register things with "
239 "the same name in the same directory.\n",
240 __func__, kobject_name(kobj));
241 else
242 WARN(1, "%s failed for %s (error: %d parent: %s)\n",
243 __func__, kobject_name(kobj), error,
244 parent ? kobject_name(parent) : "'none'");
245 } else
246 kobj->state_in_sysfs = 1;
247
248 return error;
249 }

可以看到,kobject_add有三个参数。该函数功能是将该内核对象添加到sysfs内。因此第一个参数指明了要注册的内核对象,第二个参数配置了该对象的父类,可以为NULL。第三个参数为该内核对象的名称。我之前会疑惑为什么内核对象的名称不在初始化的时候指定,现在想想,在注册的时候才指定,其实也可以理解。毕竟只有注册进了sysfs,名称才有意义。

函数的主题功能集中在kobject_add_internal内。下面逐一分析:

  • 214行对父类加以引用。parent作为本内核模块的父类,那么对于本内核模块来说是一个依赖。对其加引用保证了在自身被释放之前,父类不会被释放。为了形成对应,在注销函数内,一定会释放这个引用;
  • 229行在sysfs下创建目录和之下的属性文件,具体创建的位置,由parent决定;
  • kset暂时略过,在别的篇章详细分析。

    由此可得,kobject_add唯一作用就是在sysfs里创建层次化的目录,而这种层次都是parent带来的。逻辑上的父子概念,在sysfs内形成了目录的包含概念。

    到了这里,其实不妨对之前的测试模块添加注册和注销函数,感受下父子关系与目录子目录关系的转换。

在这之后,我们终于可以深入开始分析kobj_type。在分析之后,争取写一个真正的ktype。

release函数实际上就是自定义的kobject的释放函数。我们曾在674行调用kobject_cleanup释放obj,但没有具体分析,现代码列如下:

 615 static void kobject_cleanup(struct kobject *kobj)
616 {
617 struct kobj_type *t = get_ktype(kobj);
618 const char *name = kobj->name;
619
620 pr_debug("kobject: '%s' (%p): %s, parent %p\n",
621 kobject_name(kobj), kobj, __func__, kobj->parent);
622
623 if (t && !t->release)
624 pr_debug("kobject: '%s' (%p): does not have a release() "
625 "function, it is broken and must be fixed.\n",
626 kobject_name(kobj), kobj);
627
628 /* send "remove" if the caller did not do it but sent "add" */
629 if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) {
630 pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n",
631 kobject_name(kobj), kobj);
632 kobject_uevent(kobj, KOBJ_REMOVE);
633 }
634
635 /* remove from sysfs if the caller did not do it */
636 if (kobj->state_in_sysfs) {
637 pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n",
638 kobject_name(kobj), kobj);
639 kobject_del(kobj);
640 }
641
642 if (t && t->release) {
643 pr_debug("kobject: '%s' (%p): calling ktype release\n",
644 kobject_name(kobj), kobj);
645 t->release(kobj);
646 }
647
648 /* free name if we allocated it */
649 if (name) {
650 pr_debug("kobject: '%s': free name\n", name);
651 kfree_const(name);
652 }
653 }

关键在645行,我们可以看见,最终的释放,调用的是我们注册进去的release函数。

attribute是一个属性,每一个属性在sysfs中对应一个文件,在该内核对象的目录下生成。在kobj_type内,attribute是一个二级指针,可以提供多个属性,一起注册。详细使用方法可以参考内核代码,有很多种范例。一定要谨记二级指针和数组指针的区别。这个我在编写测试用例的时候,没有参考内核代码是如何赋值的,结果犯了低级错误,导致了内核的崩溃。

209 struct sysfs_ops {
210 ssize_t (*show)(struct kobject *, struct attribute *, char *);
211 ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
212 };

操作方法实际上只有两个,一个show,一个store。上面介绍的属性只是一个单纯的属性,sysfs负责将属性和sysfs_ops联系起来,这涉及到文件系统的相关知识,这里不再赘述。只简单说一下过程:

attribute在sysfs下表现出是一个文件,打开该文件,将导致该文件的操作表和具体的sysfs_ops关联起来,对任意一个属性的读,将最终调用show,而写将调用store。写的内容或为读出内容而准备的缓冲区都被sysfs传递了下来,在函数里就是第三个参数。

但是这个属性文件是如何生成的呢?我们注意到229行的create_dir函数,将其展开:

  66 static int create_dir(struct kobject *kobj)
67 {
68 const struct kobj_ns_type_operations *ops;
69 int error;
70
71 error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
72 if (error)
73 return error;
74
75 error = populate_dir(kobj);
76 if (error) {
77 sysfs_remove_dir(kobj);
78 return error;
79 }
80
81 /*
82 * @kobj->sd may be deleted by an ancestor going away. Hold an
83 * extra reference so that it stays until @kobj is gone.
84 */
85 sysfs_get(kobj->sd);
86
87 /*
88 * If @kobj has ns_ops, its children need to be filtered based on
89 * their namespace tags. Enable namespace support on @kobj->sd.
90 */
91 ops = kobj_child_ns_ops(kobj);
92 if (ops) {
93 BUG_ON(ops->type <= KOBJ_NS_TYPE_NONE);
94 BUG_ON(ops->type >= KOBJ_NS_TYPES);
95 BUG_ON(!kobj_ns_type_registered(ops->type));
96
97 sysfs_enable_ns(kobj->sd);
98 }
99
100 return 0;
101 }
102

71行负责创建该obj对象对应的目录,这个函数最终会调用sysfs相关的文件系统接口创建目录,这里不再赘述。

75行负责创建属性对应的文件,我们将其展开:

  49 static int populate_dir(struct kobject *kobj)
50 {
51 struct kobj_type *t = get_ktype(kobj);
52 struct attribute *attr;
53 int error = 0;
54 int i;
55
56 if (t && t->default_attrs) {
57 for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
58 error = sysfs_create_file(kobj, attr);
59 if (error)
60 break;
61 }
62 }
63 return error;
64 }

56~62行,针对default_attr域进行检测以及遍历,对每一个attr创建文件,直到为NULL。因此,我们在对属性进行赋值的时候,最后一个属性一定需要赋值为NULL。虽然有时候系统会自动赋值为NULL,但是手动赋值为NULL更加安全。58行其实也是sysfs的创建文件的接口。具体实现略去不提。

分析到这里,其实又可以为自己的测试模块,添加一个真正的属性了。来验证一下,对属性的读写,是否真实的调用了show和store函数。

在测试之后,进入kobj的最后一个环节。我们之前提到过,kobj可以创建多个自定义的属性,对每个属性的操作方法也不同。然而,上面所说的,却是一对show和store函数统治所有属性。那么如何保证每个属性的都有自己的个性化操作呢?一个最简单的办法就是在函数内部调用swith函数判断是哪个属性下发的操作,然后进入自己的分支。但是这样的操作方式代码可读性,结构性不好。内核从来不使用这样的方法。下面我们介绍内核的使用方法。

下面是内核中一个模块实现的show方法:

 113 static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
114 char *buf)
115 {
116 struct device_attribute *dev_attr = to_dev_attr(attr);
117 struct device *dev = kobj_to_dev(kobj);
118 ssize_t ret = -EIO;
119
120 if (dev_attr->show)
121 ret = dev_attr->show(dev, dev_attr, buf);
122 if (ret >= (ssize_t)PAGE_SIZE) {
123 print_symbol("dev_attr_show: %s returned bad count\n",
124 (unsigned long)dev_attr->show);
125 }
126 return ret;
127 }

116行,实际是一个container_of宏,我们看出来,实际上,内核会将struct attribute进行再封装,通过该宏取出个性化的属性,在121行调用自己的show方法。采用这种方法,便于添加属性也便于管理。下面贴出个性化的属性定义:

 548 struct device_attribute {
549 struct attribute attr;
550 ssize_t (*show)(struct device *dev, struct device_attribute *attr,
551 char *buf);
552 ssize_t (*store)(struct device *dev, struct device_attribute *attr,
553 const char *buf, size_t count);
554 };

到这里,kobj相关的部分就分析完了,下面我们将进入kset的分析。当然,别忘了,按照内核里的个性化属性方法,编写自己的测试用例。