kobject && kset

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

1、引用计数:数组

    Linux内核中每个对象都包含有一个引用计数器strut kref,在linux/kref.h文件中:函数

struct kref {
    atomic_t refcount;
};

引用计数器使用原子操做来完成引用计数的加减操做,基本操做以下:atom

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类型的成员,并提供一个释放函数。在后续的分析内核对象的时候,能够很清楚的看到引用计数是怎么使用的。spa

2、内核对象kobject:
debug

    内核对象是设备模型中最基本的数据类型,每个内核对象都对应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;
};

    当咱们要使用一个内核对象的时候,首先是初始化这个内核对象,而后将其添加到内核中。经常使用操做以下:code

// 初始化内核对象
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上述的showstore操做的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只提供了showstore操做,即内核对象的全部属性的访问,都会调用到sysfs_ops提供的showstore操做。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);
};

    这样子咱们就能够指定每个属性对应的showstore方法。那内核是如何实现的呢?核心就在于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嵌套到自定义属性类型中,而后编写一个统一的showstore操做,在统一的showstore操做内部再回调属性的真正showstore方法。固然咱们也是能够直接把struct kobj_attribute嵌套到咱们自定义的属性类型中的。

    内核为了方便咱们初始化struct kobj_attribute对象,提供了以下的宏:

#define __ATTR(_name,_mode,_show,_store) { \
    .attr = {.name = __stringify(_name), .mode = _mode }, \
    .show = _show,     \
    .store = _store,     \
}

    这个宏在device, device_driverbus中都有用到,后面分析总线、设备、驱动的时候会看到其使用。

3、内核集合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_opskobj对象的name成员,由于kset_init()方法内部并无初始化此成员。

    内核集合的注册实现源码并不复杂,只要理解了kset自己也是一个kobject,就很容易其过程了。


4、一个简单的例子:

    在/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");
相关文章
相关标签/搜索