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");
相關文章
相關標籤/搜索