kobject && kset
2016-03-06 17:23
597 查看
在Linux2.6之后,提出了新的设备模型,新设备模型的核心概念是内核对象与内核集合,并在此基础上,采用面向对象的思想提出了许多新的数据类型,如设备、总线等,以对各种外围设备进行有效的管理。
一、引用计数:
Linux内核中每一个对象都包含有一个引用计数器strut kref,在linux/kref.h文件中:
引用计数器使用原子操作来完成引用计数的加减操作,基本操作如下:
在实际的应用中,支持引用计数的数据类型,会嵌套一个struct kref类型的成员,并提供一个释放函数。在后续的分析内核对象的时候,可以很清楚的看到引用计数是怎么使用的。
二、内核对象kobject:
内核对象是设备模型中最基本的数据类型,每一个内核对象都对应sysfs文件系统中的一个目录,内核对象的父子关系对应着目录的层次关系。
内核对象数据类型在linux/kobject.h头文件声明:
当我们要使用一个内核对象的时候,首先是初始化这个内核对象,然后将其添加到内核中。常用操作如下:
此外,还提供了动态创建kobject的接口:
内核对象的引用计数操作:
1) 增加引用计数:
2) 减少引用计数:当引用计数减为0时,会自动调用kobject_release()方法
下面简单的分析一下内核对象的注册与释放函数的内部实现:
1. 注册内核对象kobject_add(): 在注册之前,必须先调用kobject_init()函数进行初始化
看来内核对象注册的真正操作是在kobject_add_internal()函数内部完成的:
大致的代码逻辑都有注释,可见每一个kobject类型的内核对象,都会与sysfs文件系统中的sysfs_dirent对象对应起来!这个关系会在后续的分析sysfs文件系统中看到,这里赞不分析。
2. 释放内核对象kobject_put():
在前面已经看到了kobject_put()方法会在内核对象的引用计数为0时,调用kobejct_release()方法进行释放:
在这里我们看到内核对象的释放函数,是在kobj_type结构体中定义的:
每一个属性,采用strut attribute结构体表示:
每一个属性对应于sysfs中的此内核对象目录下的一个文件,文件名记为name,文件权限即为mode。属性既然表现为文件的形式,那么就一定可以读写。当应用程序读写属性文件时,内核将回调由成员sysfs_ops指向的操作:
当应用程序读取属性文件时,会调用show指向的回调函数。当应用程序写属性文件时,会调用store执行的回调函数。与文件的读read相比,show操作只传入了一个缓冲区地址,并没有传递缓冲区的长度。实际上,buf执行的缓冲区是由内核自动分配的,长度是一页内存,一般是4KB。上述的show与store操作的buf,并不是用户态内存指针,所以可以直接访问。
在实际的操作中,我们可能无法在初始化的时候就把所有的属性添加进去,有可能在运行过程中动态创建属性文件,因此内核提供了动态添加和删除属性接口:
使用上述接口,我们可以在内核代码中动态的为内核对象增加或去除属性。
仔细看struct kobj_type类型,会发现sysfs_ops只提供了show与store操作,即内核对象的所有属性的访问,都会调用到sysfs_ops提供的show与store操作。Linux内核为了可以让我们在定义属性的时候更加的灵活,由为我们提供了如下的数据类型:
这样子我们就可以指定每一个属性对应的show和store方法。那内核是如何实现的呢?核心就在于container_of宏的灵活使用:
从上述的源码可以看出,我们可以在定义属性时,把struct attribute嵌套到自定义属性类型中,然后编写一个统一的show和store操作,在统一的show和store操作内部再回调属性的真正show与store方法。当然我们也是可以直接把struct kobj_attribute嵌套到我们自定义的属性类型中的。
内核为了方便我们初始化struct kobj_attribute对象,提供了如下的宏:
这个宏在device, device_driver和bus中都有用到,后面分析总线、设备、驱动的时候会看到其使用。
三、内核集合kset:
内核集合是基于内核对象设计的,它可以收纳内核对象,将收纳的内核对象添加到链表中保存,同时管理其收纳的内核对象所发送的用户态事件。
当我们要使用一个内核集合对象时,首先是初始化内核集合对象,然后将其注册到内核中,常用接口如下:
这里要注意的是,如果我们使用kset_register()方法注册内核集合,需要在注册前,初始化好uevent_ops和kobj对象的name成员,因为kset_init()方法内部并没有初始化此成员。
内核集合的注册实现源码并不复杂,只要理解了kset本身也是一个kobject,就很容易其过程了。
四、一个简单的例子:
在/sys目录下创建一个persons目录,包含有3个子目录 ,结构如下所示,name可读写,sex是只读的。
example:
一、引用计数:
Linux内核中每一个对象都包含有一个引用计数器strut kref,在linux/kref.h文件中:
struct kref { atomic_t refcount; };
引用计数器使用原子操作来完成引用计数的加减操作,基本操作如下:
void kref_init(struct kref *kref); // 初始化引用计数的值为1 void kref_get(struct kref *kref); // 引用计数加1 int kref_put(struct kref *kref, void (*release) (struct kref *kref)); // 引用计数减1,如果引用计数的值降为0,则调用release方法释放对象
在实际的应用中,支持引用计数的数据类型,会嵌套一个struct kref类型的成员,并提供一个释放函数。在后续的分析内核对象的时候,可以很清楚的看到引用计数是怎么使用的。
二、内核对象kobject:
内核对象是设备模型中最基本的数据类型,每一个内核对象都对应sysfs文件系统中的一个目录,内核对象的父子关系对应着目录的层次关系。
内核对象数据类型在linux/kobject.h头文件声明:
struct kobject { const char *name; // 对象名字,即我们在sysfs文件系统下显示的目录名 struct list_head entry; // 用于链接到集合链表中 struct kobject *parent; //父kobject对象 struct kset *kset; // 对象所属的集合 struct kobj_type *ktype; //对象的属性与访问方法 struct sysfs_dirent *sd; // 对象对应的sysfs目录项 struct kref kref; // 对象的引用计数 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; };
当我们要使用一个内核对象的时候,首先是初始化这个内核对象,然后将其添加到内核中。常用操作如下:
// 初始化内核对象 void kobject_init(struct kobject *kobj, struct kobj_type *ktype); // 设置内核对象的名字 int kobject_set_name(struct kobject * kobj, const char * fmt, ...); // 添加到内核中 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, ...); // 删除kobj内核对象:此操作内部会自动将引用计数减1,如果降为0,调用kobject_put ()方法释放内核对象 void kobject_del(struct kobject *kobj);
此外,还提供了动态创建kobject的接口:
struct kobject *kobject_create(void); struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
内核对象的引用计数操作:
1) 增加引用计数:
struct kobject *kobject_get(struct kobject *kobj) { if (kobj) kref_get(&kobj->kref); //直接调用引用计数的get方法 return kobj; }
2) 减少引用计数:当引用计数减为0时,会自动调用kobject_release()方法
void kobject_put(struct kobject *kobj) { if (kobj) { if (!kobj->state_initialized) WARN(1, 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); // 直接调用引用计数的put方法 } }
下面简单的分析一下内核对象的注册与释放函数的内部实现:
1. 注册内核对象kobject_add(): 在注册之前,必须先调用kobject_init()函数进行初始化
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...) { va_list args; int retval; if (!kobj) return -EINVAL; if (!kobj->state_initialized) { //内核对象未初始化 printk(KERN_ERR "kobject '%s' (%p): tried to add an " "uninitialized object, something is seriously wrong.\n", kobject_name(kobj), kobj); dump_stack(); return -EINVAL; } va_start(args, fmt); retval = kobject_add_varg(kobj, parent, fmt, args); va_end(args); return retval; } 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_add_internal()函数内部完成的:
static int kobject_add_internal(struct kobject *kobj) { int error = 0; struct kobject *parent; if (!kobj) return -ENOENT; if (!kobj->name || !kobj->name[0]) { // 必须设置好内核对象的名字,否则会注册失败 WARN(1, "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 */ // 如果没有设置其所属的父对象,则将其所属的内核集合kset作为其父对象 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); //在sysfs文件系统中创建kobj对应的目录 if (error) { kobj_kset_leave(kobj); kobject_put(parent); kobj->parent = NULL; /* be noisy on error issues */ if (error == -EEXIST) printk(KERN_ERR "%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 printk(KERN_ERR "%s failed for %s (%d)\n", __func__, kobject_name(kobj), error); dump_stack(); } else kobj->state_in_sysfs = 1; // 表示成功在sysfs文件系统中创建对应目录 return error; }
大致的代码逻辑都有注释,可见每一个kobject类型的内核对象,都会与sysfs文件系统中的sysfs_dirent对象对应起来!这个关系会在后续的分析sysfs文件系统中看到,这里赞不分析。
2. 释放内核对象kobject_put():
在前面已经看到了kobject_put()方法会在内核对象的引用计数为0时,调用kobejct_release()方法进行释放:
static void kobject_release(struct kref *kref) { kobject_cleanup(container_of(kref, struct kobject, kref)); } /* * kobject_cleanup - free kobject resources. * @kobj: object to cleanup */ static void kobject_cleanup(struct kobject *kobj) { struct kobj_type *t = get_ktype(kobj); const char *name = kobj->name; pr_debug("kobject: '%s' (%p): %s\n", kobject_name(kobj), kobj, __func__); // 如果我们没有为内核对象设置一个release的方法,则会打印下面的这个信息! if (t && !t->release) pr_debug("kobject: '%s' (%p): does not have a release() " "function, it is broken and must be fixed.\n", kobject_name(kobj), kobj); /* send "remove" if the caller did not do it but sent "add" */ if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) { pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n", kobject_name(kobj), kobj); kobject_uevent(kobj, KOBJ_REMOVE); // 发生KOBJ_REMOVE类型的用户态事件 } /* remove from sysfs if the caller did not do it */ if (kobj->state_in_sysfs) { pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n", kobject_name(kobj), kobj); kobject_del(kobj); //删除内核对象对应的sysfs_dirent目录,减少父对象的引用计数等操作 } if (t && t->release) { pr_debug("kobject: '%s' (%p): calling ktype release\n", kobject_name(kobj), kobj); t->release(kobj); // 回调我们自定义的释放函数 } /* free name if we allocated it */ if (name) { pr_debug("kobject: '%s': free name\n", name); kfree(name); } }
在这里我们看到内核对象的释放函数,是在kobj_type结构体中定义的:
struct kobj_type { void (*release)(struct kobject *kobj); //内核对象的释放回调函数 struct sysfs_ops *sysfs_ops; // 属性访问方法 struct attribute **default_attrs; //属性数组,以NULL结束 };
每一个属性,采用strut attribute结构体表示:
struct attribute { const char *name; //属性名 struct module *owner;//属性所有者,已不再使用 mode_t mode;//属性权限 };
每一个属性对应于sysfs中的此内核对象目录下的一个文件,文件名记为name,文件权限即为mode。属性既然表现为文件的形式,那么就一定可以读写。当应用程序读写属性文件时,内核将回调由成员sysfs_ops指向的操作:
struct sysfs_ops { ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buf); // read ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size); // write };
当应用程序读取属性文件时,会调用show指向的回调函数。当应用程序写属性文件时,会调用store执行的回调函数。与文件的读read相比,show操作只传入了一个缓冲区地址,并没有传递缓冲区的长度。实际上,buf执行的缓冲区是由内核自动分配的,长度是一页内存,一般是4KB。上述的show与store操作的buf,并不是用户态内存指针,所以可以直接访问。
在实际的操作中,我们可能无法在初始化的时候就把所有的属性添加进去,有可能在运行过程中动态创建属性文件,因此内核提供了动态添加和删除属性接口:
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr); void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);
使用上述接口,我们可以在内核代码中动态的为内核对象增加或去除属性。
仔细看struct kobj_type类型,会发现sysfs_ops只提供了show与store操作,即内核对象的所有属性的访问,都会调用到sysfs_ops提供的show与store操作。Linux内核为了可以让我们在定义属性的时候更加的灵活,由为我们提供了如下的数据类型:
struct kobj_attribute { struct attribute attr; ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf); ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); };
这样子我们就可以指定每一个属性对应的show和store方法。那内核是如何实现的呢?核心就在于container_of宏的灵活使用:
/* default kobject attribute operations */ static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct kobj_attribute *kattr; ssize_t ret = -EIO; // 从attr地址得到其所在的kobj_attribute属性对象的指针 kattr = container_of(attr, struct kobj_attribute, attr); // 调用kobj_attribute属性对象的具体show方法 if (kattr->show) ret = kattr->show(kobj, kattr, buf); return ret; } static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct kobj_attribute *kattr; ssize_t ret = -EIO; // 从attr地址得到其所在的kobj_attribute属性对象的指针 kattr = container_of(attr, struct kobj_attribute, attr); // 调用kobj_attribute属性对象的具体store方法 if (kattr->store) ret = kattr->store(kobj, kattr, buf, count); return ret; } struct sysfs_ops kobj_sysfs_ops = { .show = kobj_attr_show, .store = kobj_attr_store, };
从上述的源码可以看出,我们可以在定义属性时,把struct attribute嵌套到自定义属性类型中,然后编写一个统一的show和store操作,在统一的show和store操作内部再回调属性的真正show与store方法。当然我们也是可以直接把struct kobj_attribute嵌套到我们自定义的属性类型中的。
内核为了方便我们初始化struct kobj_attribute对象,提供了如下的宏:
#define __ATTR(_name,_mode,_show,_store) { \ .attr = {.name = __stringify(_name), .mode = _mode }, \ .show = _show, \ .store = _store, \ }
这个宏在device, device_driver和bus中都有用到,后面分析总线、设备、驱动的时候会看到其使用。
三、内核集合kset:
内核集合是基于内核对象设计的,它可以收纳内核对象,将收纳的内核对象添加到链表中保存,同时管理其收纳的内核对象所发送的用户态事件。
struct kset { struct list_head list; //用于保存收纳的内核对象 spinlock_t list_lock; //用于保证原子操作上述链表 struct kobject kobj; //内核集合也表示一个内核对象 struct kset_uevent_ops *uevent_ops;//管理用户态事件的发送 };
当我们要使用一个内核集合对象时,首先是初始化内核集合对象,然后将其注册到内核中,常用接口如下:
// 初始化内核集合对象 void kset_init(struct kset *k); // 注册内核集合对象 int kset_register(struct kset *k); //在此接口内部会调用kset_init(), // 动态创建并注册内核集合对象, struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj); // 注销已注册的内核集合对象 void kset_unregister(struct kset *k); // 内核集合引用计数加1 static inline struct kset *kset_get(struct kset *k) // 内核集合引用计数减1 static inline void kset_put(struct kset *k); // 通过name查找内核集合中的内核对象 struct kobject *kset_find_obj(struct kset *kset, const char *name);
这里要注意的是,如果我们使用kset_register()方法注册内核集合,需要在注册前,初始化好uevent_ops和kobj对象的name成员,因为kset_init()方法内部并没有初始化此成员。
内核集合的注册实现源码并不复杂,只要理解了kset本身也是一个kobject,就很容易其过程了。
四、一个简单的例子:
在/sys目录下创建一个persons目录,包含有3个子目录 ,结构如下所示,name可读写,sex是只读的。
persons | person-0 | sex | name | person-1 | sex | name | person-2 | sex | name
example:
#include <linux/module.h>
#include <linux/kobject.h>
#define PERSON_NUMS 3
#define ENTER() printk(KERN_DEBUG "%s() Enter", __func__)
#define EXIT() printk(KERN_DEBUG "%s() Exit", __func__)
#define ERR(fmt, args...) printk(KERN_ERR "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)
#define DBG(fmt, args...) printk(KERN_DEBUG "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)
struct person {
struct kobject kobj;
char name[16];
char sex;
};
// call when we kobject_put() to let kref to be 0
static void person_release(struct kobject *kobj)
{
struct person *per = container_of(kobj, struct person, kobj);
ENTER();
kfree(per);
EXIT();
}
// generic attr show function
static ssize_t attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct kobj_attribute *kattr;
ssize_t ret = -EIO;
kattr = container_of(attr, struct kobj_attribute, attr);
ENTER();
if (kattr->show) {
ret = kattr->show(kobj, kattr, buf);
}
EXIT();
return ret;
}
// generic attr store function
static ssize_t attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct kobj_attribute *kattr;
ssize_t ret = -EIO;
kattr = container_of(attr, struct kobj_attribute, attr);
ENTER();
if (kattr->store) {
ret = kattr->store(kobj, kattr, buf, count);
}
EXIT();
return ret;
}
static struct sysfs_ops person_attr_ops = {
.show = attr_show,
.store = attr_store,
};
/*
show and store function for spcific attributes, like sex and name.
*/
static ssize_t sex_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf)
{
ENTER();
struct person *per = container_of(kobj, struct person, kobj);
ssize_t ret = sprintf(buf, "%c\n", per->sex);
EXIT();
return ret;
}
static ssize_t sex_store(struct kobject *kobj, struct kobj_attribute *kattr,
const char *buf, size_t len)
{
ENTER();
DBG("no prividge");
return -EACCES; // it means no prividge.
}
static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf)
{
ENTER();
struct person *per = container_of(kobj, struct person, kobj);
ssize_t len = strlen(per->name);
memcpy(buf, per->name, len);
EXIT();
return len;
}
static ssize_t name_store(struct kobject *kobj, struct kobj_attribute *kattr,
const char *buf, size_t len)
{
ENTER();
DBG("buf: %s, len:%u", buf, len);
struct person *per = container_of(kobj, struct person, kobj);
snprintf(per->name, sizeof(per->name), "%s", buf);
EXIT();
return len;
}
static struct kobj_attribute attr_sex = \
__ATTR(sex, S_IRUGO, sex_show, sex_store);
static struct kobj_attribute attr_name = \
__ATTR(name, S_IRUGO | S_IWUGO, name_show, name_store);
static struct attribute *person_default_attrs[] = {
&attr_sex.attr,
&attr_name.attr,
NULL,
};
static struct kobj_type person_kobj_type = {
.release = person_release,
.sysfs_ops = &person_attr_ops,
.default_attrs = person_default_attrs,
};
static struct kset *persons = NULL;
/*
persons | person-0 | sex | name | person-1 | sex | name | person-2 | sex | name
*/
static __init int sysfs_demo_init(void)
{
struct person *per;
int i;
int err;
struct list_head *cur, *next;
struct kobject *p_cur;
ENTER();
persons = kset_create_and_add("persons", NULL, NULL);
if (!persons) {
ERR("kset_create_and_add fail");
return -ENOMEM;
}
for (i = 0; i < PERSON_NUMS; ++i) {
per = kzalloc(sizeof(struct person), GFP_KERNEL);
if (!per) {
ERR("kzalloc fail");
goto _fail;
}
per->kobj.kset = persons;
per->sex = ((i % 2) == 0) ? 'M' : 'F';
snprintf(per->name, sizeof(per->name), "person-%d", i);
DBG("name: %s", per->name);
err = kobject_init_and_add(&per->kobj, &person_kobj_type, NULL, per->name);
if (err) {
ERR("kobject_init_and_add fail");
goto _fail;
}
DBG("kobject_init_and_add success");
kobject_uevent(&per->kobj, KOBJ_ADD);
}
EXIT();
return 0;
_fail:
if (persons) {
DBG("in fail");
list_for_each_safe(cur, next, &persons->list) {
p_cur = container_of(cur, struct kobject, entry);
kobject_put(p_cur);
}
kset_unregister(persons);
persons = NULL;
}
return err;
}
static __exit void sysfs_demo_exit(void)
{
struct list_head *cur, *next;
struct kobject *p_cur;
if (persons) {
ENTER();
list_for_each_safe(cur, next, &persons->list) {
p_cur = container_of(cur, struct kobject, entry);
DBG("kobject_put begin");
kobject_put(p_cur);
}
kset_unregister(persons);
persons = NULL;
}
EXIT();
}
module_init(sysfs_demo_init);
module_exit(sysfs_demo_exit);
MODULE_LICENSE("GPL");
相关文章推荐
- iOS学习笔记(13)—— Objective-C类扩展的方法之一——分类(category)
- Object类的重要方法
- Keras笔记 -- objective
- C#取硬盘、CPU、主板、网卡的序号 ManagementObjectSearcher (WMI)
- java学习日记——Object类II
- c# 线程池RegisterWaitForSingleObject的一个Demo
- JSON 之 SuperObject(10): Merge、Clone、ForcePath
- JSON 之 SuperObject(9): TSuperType
- JSON 之 SuperObject(7): 可以省略的双引号
- JSON 之 SuperObject(6): 方法
- JSON 之 SuperObject(5): Format 与转义字符
- JSON 之 SuperObject(4): 增、删、改
- JSON 之 SuperObject(3): 访问
- JSON 之 SuperObject(1)
- JSON 之 SuperObject(12): TSuperEnumerator、TSuperAvlIterator、ObjectFindFirst...
- JSON 之 SuperObject(13): 关于 SO 与 SA 函数
- pom.xml(Project Object Model) 文件简单介绍
- Object-c基础
- Objective-C 中 @property的使用
- Objective-C概述