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

参考文档: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,如下:

 struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:;
unsigned int state_in_sysfs:;
unsigned int state_add_uevent_sent:;
unsigned int state_remove_uevent_sent:;
unsigned int uevent_suppress:;
};

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结构的简单例子

 struct device {

struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;

void (*release)(struct device *dev);
};

  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常用的操作函数如下:

 void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...)
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...)
struct kobject *kobject_create(void)
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
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中定义,如下:

 /**
* kobject_init_and_add - initialize a kobject structure and add it to the kobject hierarchy
* @kobj: pointer to the kobject to initialize
* @ktype: pointer to the ktype for this kobject.
* @parent: pointer to the parent of this kobject.
* @fmt: the name of the kobject.
*
* This function combines the call to kobject_init() and
* kobject_add(). The same type of error handling after a call to
* kobject_add() and kobject lifetime rules are the same here.
*/
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
{
va_list args;
int retval; kobject_init(kobj, ktype); va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}

  可以看出来,该函数分为两部分:首先通过kobject_init初始化kobject结构,然后利用kobject_add_varg将kobject添加到设备模型的体系结构中去。我们先来看看kobject_init函数

 /**
* kobject_init - initialize a kobject structure
* @kobj: pointer to the kobject to initialize
* @ktype: pointer to the ktype for this kobject.
*
* This function will properly initialize a kobject such that it can then
* be passed to the kobject_add() call.
*
* After this function is called, the kobject MUST be cleaned up by a call
* to kobject_put(), not by a call to kfree directly to ensure that all of
* the memory is cleaned up properly.
*/
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) {
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) {
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack();
}
kobject_init_internal(kobj);
kobj->ktype = ktype;
return; error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}

  主要是调用kobject_init_internal并设置kobj->ktype,当然要保证传递给kobject_init的kob、ktype参数不为空

  Kobject_inint_internal函数的定义同样在lib/kobject.c,如下:

 static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = ;
kobj->state_add_uevent_sent = ;
kobj->state_remove_uevent_sent = ;
kobj->state_initialized = ;
}

  这里是对kobject成员变量的初始化,初始为默认的状态;kref_init函数只是通过atomic_set将kobj->kref->refcount设置为1

  kobject_add_varg函数同样定义于lib/kobject.c文件中:

 static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval; retval = kobject_set_name_vargs(kobj, fmt, vargs);
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent;
return kobject_add_internal(kobj);
}

该函数调用kobject_set_name_vargs解析可变参数并设置kobject.name的值,然后设置kobj->parent,最后通过kobject_add_internal添加kobject

kobject_add_internal函数定义于lib/kobject.c,主要作用是设置kobject的父节点、kset并创建kobject在sysfs中的目录

 static int kobject_add_internal(struct kobject *kobj)
{
int error = ;
struct kobject *parent; if (!kobj)
return -ENOENT; if (!kobj->name || !kobj->name[]) {
WARN(, "kobject: (%p): attempted to be registered with empty "
"name!\n", kobj);
return -EINVAL;
} parent = kobject_get(kobj->parent); /* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
kobj->parent = parent;
} pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>"); error = create_dir(kobj);
if (error) {
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL; /* be noisy on error issues */
if (error == -EEXIST)
WARN(, "%s failed for %s with "
"-EEXIST, don't try to register things with "
"the same name in the same directory.\n",
__func__, kobject_name(kobj));
else
WARN(, "%s failed for %s (error: %d parent: %s)\n",
__func__, kobject_name(kobj), error,
parent ? kobject_name(parent) : "'none'");
} else
kobj->state_in_sysfs = ; return error;
}

  这个函数首先判断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中有几个该函数的同名函数,参数类型不同,莫要搞混了

 static int create_dir(struct kobject *kobj)
{
int error = ;
if (kobject_name(kobj)) {
error = sysfs_create_dir(kobj);
if (!error) {
error = populate_dir(kobj);
if (error)
sysfs_remove_dir(kobj);
}
}
return error;
}

kobject引用计数

  Kobject的一个重要属性在于它的引用计数,只要kobject的引用计数不为0,kobject对象就必须存在。Kobject中保存引用计数的变量时kref,它本质上是atomic_t变量。驱动模型底层对引用计数控制的函数有

 struct kobject *kobject_get(struct kobject *kobj)
void kobject_put(struct kobject *kobj)

  kobject_get用于增加kobject的引用计数,并且返回kobject指针,调用该函数的话必须检查返回值,否则可能会引用到已被销毁的kobject,造成竞争的发生。

 /**
* kobject_get - increment refcount for object.
* @kobj: object.
*/
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj)
kref_get(&kobj->kref);
return kobj;
}

Kobject_put用于减少kobject的引用计数

 /**
* kobject_put - decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call kobject_cleanup().
*/
void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_put() is being "
"called.\n", kobject_name(kobj), kobj);
kref_put(&kobj->kref, kobject_release);
}
}

当引用计数为0的时候,需要调用release函数将kobject释放掉,对于每一个kobject,都必须有一个release函数来释放kobject结构。Linux设备模型中默认的release函数不在kobject对象中,而是在kobj_type这个结构中,kobj_type结构定义如下:

 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);
};

此时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文件中,如下:
 struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *,char *);
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
const void *(*namespace)(struct kobject *, const struct attribute *);
};

  当用户空间读取属性的时候,便会调用到属性的show函数;而store函数则用于从用户空间获取新的属性值并保持,注意应用程序应该要有该属性的些权限才可以调用到store函数,并且最好在store函数中检查下用户空间写入值的合法性。

  default_attrs:保存了kobject默认的属性列表,属性列表中的每个属性都会在kobject目录下生成一个对应的属性文件,属性结构在lib/kobject.h中定义,如下:

 struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};

  name:属性名,其对应属性文件的名字,

  mode:访问模式,用于指定用户空间访问属性文件的权限

  关于用户空间读写属性文件是怎么调用到sysfs_ops->show和sysfs_ops->store的原理,主要涉及到的是sysfs内容,目前的话,只要记住读写属性文件会调用到sysfs_ops->show和sysfs_ops->store即可。

  除了kobject的默认属性列表,程序员还可以感觉需要添加或者删除kobject的属性。可以通过如下的函数添加kobject属性:

 /**
* sysfs_create_file - create an attribute file for an object.
* @kobj: object we're creating for.
* @attr: attribute descriptor.
*/
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
{
BUG_ON(!kobj || !kobj->sd || !attr); return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR);
}

只需要填充attribute结构并传递给sysfs_create_file即可,如果该函数返回0,说明创建属性成功,否则将返回对应的错误码。

可以通过如下的函数来删除属性:

 /**
* sysfs_remove_file - remove an object attribute.
* @kobj: object we're acting for.
* @attr: attribute descriptor.
*
* Hash the attribute name and kill the victim.
*/
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr)
{
const void *ns; if (sysfs_attr_ns(kobj, attr, &ns))
return; sysfs_hash_and_remove(kobj->sd, ns, attr->name);
}

  这个函数调用之后,属性文件将不会再出现在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. B2. Character Swap (Hard Version)

    链接: http://codeforces.com/contest/1243/problem/B2 题目大意: 两个字符串,判断能否通过交换为从而使得这两个字符串完全一致,如不可以的话,直接输出NO, ...

  2. C++调用TensorFlow

    在使用C++调用TensorFlow接口时出现的问题,网上没有资料,问了老师才知道的. Exception ignored in: <module 'threading' from 'E:\\t ...

  3. JSP中引用CSS样式文件却无法显示的问题解决方案

    你也遇到过这种问题吗,CSS写好了,JSP写好了,在JSP中调用CSS文件,路径检查后也正确,但是无法显示渲染后的页面 原因:罪魁祸首就是过滤器响应数据的时候,响应头设置为了“text/html”,但 ...

  4. Jquery中 $.Ajax() 参数详解

    1.url:要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type:要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如pu ...

  5. 每天都在用,但你知道 Tomcat 的线程池有多努力吗?

    这是why的第 45 篇原创文章.说点不一样的线程池执行策略和线程拒绝策略,探讨怎么让线程池先用完最大线程池再把任务放到队列中. 荒腔走板 大家好,我是 why,一个四川程序猿,成都好男人. 先是本号 ...

  6. 23-Java-Spring框架(一)

    一.Spring框架了解 Spring框架是一个开源的框架,为JavaEE应用提供多方面的解决方案,用于简化企业级应用的开发,相当于是一种容器,可以集成其他框架(结构图如下). 上图反映了框架引包的依 ...

  7. tp5中的input助手函数

    详见手册:https://www.kancloud.cn/manual/thinkphp5/118044

  8. 20199310《Linux内核原理与分析》第十二周作业

    1.问题描述 2014年9月24日,Bash中发现了一个严重漏洞shellshock,该漏洞可用于许多系统,并且既可以远程也可以在本地触发.在本实验中,通过学习重现攻击该漏洞,加深对于ShellSho ...

  9. POJ1475 推箱子---模块化思想

    自古逢秋悲寂寥,我言秋日胜春朝. 晴空一鹤排云上,便引诗情到碧霄. --刘禹锡 题目:推箱子 网址:http://poj.org/problem?id=1475 推箱子游戏相信大家都不陌生,在本题中, ...

  10. vue项目中上拉加载和下拉刷新页面的实现

    功能:上拉加载,下拉刷新 使用方法: 自己创建一个.vue的文件(我自己是创建了一个PullToRefresh.vue的文件),将代码粘贴进去,具体的样式问题自己在该文件中调整. <templa ...