内核对象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的分析。当然,别忘了,按照内核里的个性化属性方法,编写自己的测试用例。

内核对象kobject和sysfs(3)——kobj分析的更多相关文章

  1. 内核对象kobject和sysfs(4)——kset分析

    内核对象kobject和sysfs(4)--kset分析 从狭义上来说,kset就是kobj的一个再封装而已.在封装之后,提供了针对kset之下所有kobj统一管理的一些方法. 我们还是从结构说起: ...

  2. 内核对象kobject和sysfs(2)——kref分析

    内核对象kobject和sysfs(2)--kref分析 在介绍ref之前,先贴上kref的结构: struct kref { atomic_t refcount; }; 可以看到,kref只是包含一 ...

  3. 内核对象kobject和sysfs(1)——概述

    内核对象kobject和sysfs(1)--概述 问题: 在走读驱动代码时,经常看见kref,kobj,sysfs这些结构,这些结构到底有什么作用?如何理解并使用这些结构呢?这将在接下来的这一系列文章 ...

  4. sysfs - 用于导出内核对象(kobject)的文件系统

    sysfs - _The_ filesystem for exporting kernel objects.sysfs - 用于导出内核对象(kobject)的文件系统Patrick Mochel & ...

  5. [4]Windows内核情景分析---内核对象

    写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...

  6. Linux 内核文档翻译 - kobject.txt

    原文地址:Linux 内核文档翻译 - kobject.txt 作者:qh997 Everything you never wanted to know about kobjects, ksets, ...

  7. Linux内核文档翻译——kobject.txt

    ==================================================================== Everything you never wanted to ...

  8. 第9章 用内核对象进行线程同步(4)_死锁(DeadLock)及其他

    9.7 线程同步对象速查表 对象 何时处于未触发状态 何时处于触发状态 成功等待的副作用 进程 进程仍在运行的时候 进程终止的时(ExitProcess.TerminateProcess) 没有 线程 ...

  9. 第9章 用内核对象进行线程同步(1)_事件对象(Event)

    9.1 等待函数 (1)WaitForSingleObject(hObject,dwMilliseonds); ①dwMilliseconds为INFINITE时表示无限等待 ②dwMilliseco ...

随机推荐

  1. springmvc 之 helloworld

    构建SPRINGMVC主要分为几个部分(大体方式为创建并配置2个XML文件.一个JAVA文件及一个JSP文件). 一.创建动态JAVA WEB项目  //创建项目并导入JAR包. 二.创建并配置ser ...

  2. AngularJS高级程序设计读书笔记 -- 过滤器篇

    一. 过滤器基础 过滤器用于在视图中格式化展现给用户的数据. 一旦定义过滤器之后, 就可在整个模块中全面应用, 也就意味着可以用来保证跨多个控制器和视图之间的数据展示的一致性. 过滤器将数据在被指令处 ...

  3. 安装旧版的docker-engine-1.12.6

    执行kubeadm init --api-advertise-addresses=172.16.160.211命令的时候,提示docker版本太新了 想要安装旧版docker,可以使用以下方法: yu ...

  4. Matlab: 作图

    控制图的大小 figure('position',[x0,y0,dx,dy]); figure(fig number); 显示图例 legend('leg1','leg2') depend on ho ...

  5. c#中foreach的一种用法

    以下定义的MyClass类中的addnum方法使用了一个数组型参数b: public class MyClass { ; public void addnum(ref int sum, params ...

  6. Spring MVC使用样例

    Spring MVC使用样例 步骤1:添加Spring的相关依赖 1 <dependency> 2 3 <groupId>com.alibaba.external</gr ...

  7. [leetcode-566-Reshape the Matrix]

    In MATLAB, there is a very useful function called 'reshape', which can reshape a matrix into a new o ...

  8. CSS雪碧图自动生成软件

    下载地址 http://www.99css.com/1524/ 包含详细的下载地址.下载步骤以及使用教程 亮点:自动合成雪碧图+自动生成雪碧图background-position代码 简单过程 下载 ...

  9. DOCKER 从入门到放弃(三)

    使用docker create [image-name] 创建一个容器 创建一个nginx镜像的容器,由于没有指定各项参数,容器实用默认参数,创建后并不会启动,并将容器的ID输出到终端,如果本地没有镜 ...

  10. Spring源码:IOC原理解析(一)

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! IOC(Inversion of Control),即控制反转,意思是将对象的创建和依赖关系交给第三方容器处理,我们要用的时候告诉容器我们 ...