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


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

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

关注一下kobj的结构:

  1. struct kobject {
  2. const char *name;// 该内核对象的名称
  3. struct list_head entry;// 链入kset的连接件
  4. struct kobject *parent;// 指向父对象,可以为空
  5. struct kset *kset; // 指向的内核对象集合,可以为空
  6. struct kobj_type *ktype; // 该内核对象使用的操作集合
  7. struct kernfs_node *sd; /* sysfs directory entry */
  8. struct kref kref; // 该内核对象的引用计数
  9. #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
  10. struct delayed_work release;
  11. #endif
  12. unsigned int state_initialized:1;
  13. unsigned int state_in_sysfs:1;
  14. unsigned int state_add_uevent_sent:1;
  15. unsigned int state_remove_uevent_sent:1;
  16. unsigned int uevent_suppress:1;
  17. };

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

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

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

  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. };
  8. struct sysfs_ops {
  9. ssize_t (*show)(struct kobject *, struct attribute *, char *);
  10. ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
  11. };
  12. struct attribute {
  13. const char *name;
  14. umode_t mode;
  15. };

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

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

先来看看初始化:

  1. 325 void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
  2. 326 {
  3. 327 char *err_str;
  4. 328
  5. 329 if (!kobj) {
  6. 330 err_str = "invalid kobject pointer!";
  7. 331 goto error;
  8. 332 }
  9. 333 if (!ktype) {
  10. 334 err_str = "must have a ktype to be initialized properly!\n";
  11. 335 goto error;
  12. 336 }
  13. 337 if (kobj->state_initialized) {
  14. 338 /* do not error out as sometimes we can recover */
  15. 339 printk(KERN_ERR "kobject (%p): tried to init an initialized "
  16. 340 "object, something is seriously wrong.\n", kobj);
  17. 341 dump_stack();
  18. 342 }
  19. 343
  20. 344 kobject_init_internal(kobj);
  21. 345 kobj->ktype = ktype;
  22. 346 return;
  23. 347
  24. 348 error:
  25. 349 printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
  26. 350 dump_stack();
  27. 351 }
  28. 352 EXPORT_SYMBOL(kobject_init);
  29. 187 static void kobject_init_internal(struct kobject *kobj)
  30. 188 {
  31. 189 if (!kobj)
  32. 190 return;
  33. 191 kref_init(&kobj->kref);
  34. 192 INIT_LIST_HEAD(&kobj->entry);
  35. 193 kobj->state_in_sysfs = 0;
  36. 194 kobj->state_add_uevent_sent = 0;
  37. 195 kobj->state_remove_uevent_sent = 0;
  38. 196 kobj->state_initialized = 1;
  39. 197 }

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

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

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

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

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

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

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

验证之后,分析kobject_add:

  1. 394 int kobject_add(struct kobject *kobj, struct kobject *parent,
  2. 395 const char *fmt, ...)
  3. 396 {
  4. 397 va_list args;
  5. 398 int retval;
  6. 399
  7. 400 if (!kobj)
  8. 401 return -EINVAL;
  9. 402
  10. 403 if (!kobj->state_initialized) {
  11. 404 printk(KERN_ERR "kobject '%s' (%p): tried to add an "
  12. 405 "uninitialized object, something is seriously wrong.\n",
  13. 406 kobject_name(kobj), kobj);
  14. 407 dump_stack();
  15. 408 return -EINVAL;
  16. 409 }
  17. 410 va_start(args, fmt);
  18. 411 retval = kobject_add_varg(kobj, parent, fmt, args);
  19. 412 va_end(args);
  20. 413
  21. 414 return retval;
  22. 415 }
  23. 416 EXPORT_SYMBOL(kobject_add);
  24. 354 static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
  25. 355 struct kobject *parent,
  26. 356 const char *fmt, va_list vargs)
  27. 357 {
  28. 358 int retval;
  29. 359
  30. 360 retval = kobject_set_name_vargs(kobj, fmt, vargs);
  31. 361 if (retval) {
  32. 362 printk(KERN_ERR "kobject: can not set name properly!\n");
  33. 363 return retval;
  34. 364 }
  35. 365 kobj->parent = parent;
  36. 366 return kobject_add_internal(kobj);
  37. 367 }
  38. 200 static int kobject_add_internal(struct kobject *kobj)
  39. 201 {
  40. 202 int error = 0;
  41. 203 struct kobject *parent;
  42. 204
  43. 205 if (!kobj)
  44. 206 return -ENOENT;
  45. 207
  46. 208 if (!kobj->name || !kobj->name[0]) {
  47. 209 WARN(1, "kobject: (%p): attempted to be registered with empty "
  48. 210 "name!\n", kobj);
  49. 211 return -EINVAL;
  50. 212 }
  51. 213
  52. 214 parent = kobject_get(kobj->parent);
  53. 215
  54. 216 /* join kset if set, use it as parent if we do not already have one */
  55. 217 if (kobj->kset) {
  56. 218 if (!parent)
  57. 219 parent = kobject_get(&kobj->kset->kobj);
  58. 220 kobj_kset_join(kobj);
  59. 221 kobj->parent = parent;
  60. 222 }
  61. 223
  62. 224 pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
  63. 225 kobject_name(kobj), kobj, __func__,
  64. 226 parent ? kobject_name(parent) : "<NULL>",
  65. 227 kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
  66. 228
  67. 229 error = create_dir(kobj);
  68. 230 if (error) {
  69. 231 kobj_kset_leave(kobj);
  70. 232 kobject_put(parent);
  71. 233 kobj->parent = NULL;
  72. 234
  73. 235 /* be noisy on error issues */
  74. 236 if (error == -EEXIST)
  75. 237 WARN(1, "%s failed for %s with "
  76. 238 "-EEXIST, don't try to register things with "
  77. 239 "the same name in the same directory.\n",
  78. 240 __func__, kobject_name(kobj));
  79. 241 else
  80. 242 WARN(1, "%s failed for %s (error: %d parent: %s)\n",
  81. 243 __func__, kobject_name(kobj), error,
  82. 244 parent ? kobject_name(parent) : "'none'");
  83. 245 } else
  84. 246 kobj->state_in_sysfs = 1;
  85. 247
  86. 248 return error;
  87. 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,但没有具体分析,现代码列如下:

  1. 615 static void kobject_cleanup(struct kobject *kobj)
  2. 616 {
  3. 617 struct kobj_type *t = get_ktype(kobj);
  4. 618 const char *name = kobj->name;
  5. 619
  6. 620 pr_debug("kobject: '%s' (%p): %s, parent %p\n",
  7. 621 kobject_name(kobj), kobj, __func__, kobj->parent);
  8. 622
  9. 623 if (t && !t->release)
  10. 624 pr_debug("kobject: '%s' (%p): does not have a release() "
  11. 625 "function, it is broken and must be fixed.\n",
  12. 626 kobject_name(kobj), kobj);
  13. 627
  14. 628 /* send "remove" if the caller did not do it but sent "add" */
  15. 629 if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) {
  16. 630 pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n",
  17. 631 kobject_name(kobj), kobj);
  18. 632 kobject_uevent(kobj, KOBJ_REMOVE);
  19. 633 }
  20. 634
  21. 635 /* remove from sysfs if the caller did not do it */
  22. 636 if (kobj->state_in_sysfs) {
  23. 637 pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n",
  24. 638 kobject_name(kobj), kobj);
  25. 639 kobject_del(kobj);
  26. 640 }
  27. 641
  28. 642 if (t && t->release) {
  29. 643 pr_debug("kobject: '%s' (%p): calling ktype release\n",
  30. 644 kobject_name(kobj), kobj);
  31. 645 t->release(kobj);
  32. 646 }
  33. 647
  34. 648 /* free name if we allocated it */
  35. 649 if (name) {
  36. 650 pr_debug("kobject: '%s': free name\n", name);
  37. 651 kfree_const(name);
  38. 652 }
  39. 653 }

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

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

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

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

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

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

  1. 66 static int create_dir(struct kobject *kobj)
  2. 67 {
  3. 68 const struct kobj_ns_type_operations *ops;
  4. 69 int error;
  5. 70
  6. 71 error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
  7. 72 if (error)
  8. 73 return error;
  9. 74
  10. 75 error = populate_dir(kobj);
  11. 76 if (error) {
  12. 77 sysfs_remove_dir(kobj);
  13. 78 return error;
  14. 79 }
  15. 80
  16. 81 /*
  17. 82 * @kobj->sd may be deleted by an ancestor going away. Hold an
  18. 83 * extra reference so that it stays until @kobj is gone.
  19. 84 */
  20. 85 sysfs_get(kobj->sd);
  21. 86
  22. 87 /*
  23. 88 * If @kobj has ns_ops, its children need to be filtered based on
  24. 89 * their namespace tags. Enable namespace support on @kobj->sd.
  25. 90 */
  26. 91 ops = kobj_child_ns_ops(kobj);
  27. 92 if (ops) {
  28. 93 BUG_ON(ops->type <= KOBJ_NS_TYPE_NONE);
  29. 94 BUG_ON(ops->type >= KOBJ_NS_TYPES);
  30. 95 BUG_ON(!kobj_ns_type_registered(ops->type));
  31. 96
  32. 97 sysfs_enable_ns(kobj->sd);
  33. 98 }
  34. 99
  35. 100 return 0;
  36. 101 }
  37. 102

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

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

  1. 49 static int populate_dir(struct kobject *kobj)
  2. 50 {
  3. 51 struct kobj_type *t = get_ktype(kobj);
  4. 52 struct attribute *attr;
  5. 53 int error = 0;
  6. 54 int i;
  7. 55
  8. 56 if (t && t->default_attrs) {
  9. 57 for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
  10. 58 error = sysfs_create_file(kobj, attr);
  11. 59 if (error)
  12. 60 break;
  13. 61 }
  14. 62 }
  15. 63 return error;
  16. 64 }

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

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

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

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

  1. 113 static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
  2. 114 char *buf)
  3. 115 {
  4. 116 struct device_attribute *dev_attr = to_dev_attr(attr);
  5. 117 struct device *dev = kobj_to_dev(kobj);
  6. 118 ssize_t ret = -EIO;
  7. 119
  8. 120 if (dev_attr->show)
  9. 121 ret = dev_attr->show(dev, dev_attr, buf);
  10. 122 if (ret >= (ssize_t)PAGE_SIZE) {
  11. 123 print_symbol("dev_attr_show: %s returned bad count\n",
  12. 124 (unsigned long)dev_attr->show);
  13. 125 }
  14. 126 return ret;
  15. 127 }

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

  1. 548 struct device_attribute {
  2. 549 struct attribute attr;
  3. 550 ssize_t (*show)(struct device *dev, struct device_attribute *attr,
  4. 551 char *buf);
  5. 552 ssize_t (*store)(struct device *dev, struct device_attribute *attr,
  6. 553 const char *buf, size_t count);
  7. 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. Linux上的防病毒软件ClamAV

    Clam AntiVirus(ClamAV)是免费而且开放源代码的防毒软件,软件与病毒码的更新皆由社群免费发布.目前ClamAV主要是使用在由Linux.FreeBSD等Unix-like系统架设的邮 ...

  2. Python基础-类变量和实例变量

    Python基础-类变量和实例变量 写在前面 如非特别说明,下文均基于Python3 大纲: 1. 类变量和实例变量 在Python Tutorial中对于类变量和实例变量是这样描述的: Genera ...

  3. 基于 HTML5 WebGL 的 3D 网络拓扑图

    在数据量很大的2D 场景下,要找到具体的模型比较困难,并且只能显示出模型的的某一部分,显示也不够直观,这种时候能快速搭建出 3D 场景就有很大需求了.但是搭建 3D 应用场景又依赖于通过 3ds Ma ...

  4. GitBash学习1

    昨晚学了一点GitBash,建立库,向库里添加文件,对比修改的内容等等. 自己做了以下总结 git mkdir <dirname> //建立文件 git cd <dirname> ...

  5. redis可视化工具redisClient

    下载连接:百度网盘 直接解压就可以用了

  6. H5学习第二周

    怎么说,在各种感觉中h5学习的第二周已经过来了,先总结一下,感觉学习h5是一件让我爱恨交加的事,学会一些新的知识并把它成功运行出来的时候是非常激动和兴奋的,但是有时候搞不懂一个标签或者属性的时候,就有 ...

  7. WebAssembly:随风潜入夜

    What? WebAssembly 是一种二进制格式的类汇编代码,可以被浏览器加载和并进一步编译成可执行的机器码,从而在客户端运行.它还可以作为高级语言的编译目标,理论上任何语言都可以编译为 WebA ...

  8. MySQL慢查询日志

    实验环境: OS X EI Captian + MySQL 5.7 一.配置MySQL自动记录慢查询日志 查看变量,也就是配置信息 show (global) variables like '%slo ...

  9. [图形学] Chp14 GLU曲面裁剪函数程序示例及样条表示遗留问题

    样条表示这章已经看完,最后的GLU曲面裁剪函数,打算按书中的示例实现一下,其中遇到了几个问题. 先介绍一下GLU曲面裁剪函数的使用方法. 1 裁剪函数是成对出现的: gluBeginTrim和gluE ...

  10. JS的get和set使用示例

    javascript中set与get方法详解 其中get与set的使用方法: 1.get与set是方法,因为是方法,所以可以进行判断. 2.get是得到 一般是要返回的   set 是设置 不用返回 ...