您的位置:首页 > 移动开发 > Objective-C

kobject && kset

2016-03-06 17:23 597 查看
在Linux2.6之后,提出了新的设备模型,新设备模型的核心概念是内核对象与内核集合,并在此基础上,采用面向对象的思想提出了许多新的数据类型,如设备、总线等,以对各种外围设备进行有效的管理。

一、引用计数:

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");
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: