阿辉原创,转载请注明出处

参考文档: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:;
  10. unsigned int state_in_sysfs:;
  11. unsigned int state_add_uevent_sent:;
  12. unsigned int state_remove_uevent_sent:;
  13. unsigned int uevent_suppress:;
  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. struct kobject kobj;
  3. const char *init_name; /* initial name of the device */
  4. const struct device_type *type;

  5. void (*release)(struct device *dev);
  6. };

  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 = ;
  8. kobj->state_add_uevent_sent = ;
  9. kobj->state_remove_uevent_sent = ;
  10. kobj->state_initialized = ;
  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 = ;
  4. struct kobject *parent;
  5.  
  6. if (!kobj)
  7. return -ENOENT;
  8.  
  9. if (!kobj->name || !kobj->name[]) {
  10. WARN(, "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(, "%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(, "%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 = ;
  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 = ;
  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(, 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成员,接下来我们对它的其他成员做一个详细介绍

  1. 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,该模块的使用如下所示:

Linux设备模型之kobject的更多相关文章

  1. Linux 设备模型之 (kobject、kset 和 Subsystem)(二)

    问题描写叙述:前文我们知道了/sys是包括内核和驱动的实施信息的,用户能够通过 /sys 这个接口.用户通过这个接口能够一览内核设备的全貌.本文将从Linux内核的角度来看一看这个设备模型是怎样构建的 ...

  2. linux设备模型_转

    建议原博文查看,效果更佳. 转自:http://www.cnblogs.com/wwang/category/269350.html Linux设备模型 (1) 随着计算机的周边外设越来越丰富,设备管 ...

  3. Linux设备模型(总线、设备、驱动程序和类)

    Linux设备驱动程序学习(13) -Linux设备模型(总线.设备.驱动程序和类)[转] 文章的例子和实验使用<LDD3>所配的lddbus模块(稍作修改). 提示:在学习这部分内容是一 ...

  4. Linux设备模型 学习总结

    看LDD3中设备模型一章,觉得思维有些混乱.这里从整体的角度来理理思路.本文从四个方面来总结一些内容: 1.底层数据结构:kobject,kset.2.linux设备模型层次关系:bus_type,d ...

  5. Linux设备模型——设备驱动模型和sysfs文件系统解读

    本文将对Linux系统中的sysfs进行简单的分析,要分析sysfs就必须分析内核的driver-model(驱动模型),两者是紧密联系的.在分析过程中,本文将以platform总线和spi主控制器的 ...

  6. Linux 设备模型浅析之 uevent 篇(2)

    Linux 设备模型浅析之 uevent 篇 本文属本人原创,欢迎转载,转载请注明出处.由于个人的见识和能力有限,不可能面 面俱到,也可能存在谬误,敬请网友指出,本人的邮箱是 yzq.seen@gma ...

  7. linux设备模型:扩展篇

    Linux设备模型组件:总线  一.定义:总线是不同IC器件之间相互通讯的通道;在计算机中,一个总线就是处理器与一个或多个不同外设之间的通讯通道;为了设备模型的目的,所有的设备都通过总线相互连接,甚至 ...

  8. Linux设备模型(总结)

    转:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字 ...

  9. Linux设备模型 (2)

    上一篇文章<Linux设备模型 (1)>主要介绍了Linux设备模型在用户空间的接口sysfs,用户通过这个接口可以一览内核设备的全貌.本文将从Linux内核的角度来看一看这个设备模型是如 ...

随机推荐

  1. spring源码阅读笔记08:bean加载之创建bean

    上文从整体视角分析了bean创建的流程,分析了Spring在bean创建之前所做的一些准备工作,并且简单分析了一下bean创建的过程,接下来就要详细分析bean创建的各个流程了,这是一个比较复杂的过程 ...

  2. Spring Boot 中使用自定义注解,AOP 切面打印出入参日志及Dubbo链路追踪透传traceId

    一.使用背景 开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境中一般使用 ELK 来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,由于大量的其他用户/其他线程的日志也一起输出穿 ...

  3. 2.react-插件

    PC: antd(蚂蚁金服)https://ant.design/index-cn 移动: mobile-antd(蚂蚁金服)https://mobile.ant.design =========== ...

  4. bugku ctf 逆向题

    1.逆向入门 2.Easy_vb 直接找出来. 3.easy_re 4.游戏过关 摁着嗯着就出来了... 5.Timer{阿里ctf} apk文件,不会搞. 6.逆向入门 发现是base64,直接转图 ...

  5. 被折磨致死的heroku——herku部署

    最近一直在弄heroku部署上线,但是因为中国墙和英语问题,一直弄不好,,很是烦躁,所有暂时先放弃了,但是因为查询了一些资料,有些文档链接有必要放到下面,方便各位和自己查看: heroku官方网站: ...

  6. Java讲解RPC的基本实现

    RPC远程过程调用可以说是分布式系统的基础,本文将通过Java演示一次普通的rpc调用到底发生了什么. 我曾经在网上看到有人提问,为什么RPC要叫作远程过程调用,而不叫作RMC远程方法调用.个人认为R ...

  7. 域名和服务器绑定及https协议更换

    服务器是之前已经购买了的 1.腾讯云产品中搜索域名注册(产品太多了懒得找,直接搜索来得快些) 2.进去之后可以选择各种后缀的域名,输入自己喜欢的,看看哪些后缀是没有被注册的.自己挑选一个就可以,按照指 ...

  8. Istio架构详解

    Istio架构及其组件概述 Istio 架构总体来说分为控制面和数据面两部分.控制面是 Istio 的核心,管理 Istio 的所有功能,主要包括Pilot.Mixer.Citadel等服务组件;数据 ...

  9. PHP 把MYSQL重复ID 二维数组重组为三维数组

    应用场景 MYSQL在使用关联查询时,比如 产品表 与 产品图片表关联,一个产品多张产品图片,关联查询结果如下: $arr=[['id'=>1,'img'=>'img1'],['id'=& ...

  10. SpringBoot中使用Fastjson/Jackson对JSON序列化格式化输出的若干问题

    来源 :https://my.oschina.net/Adven/blog/3036567 使用springboot-web编写rest接口,接口需要返回json数据,目前国内比较常用的fastjso ...