1. 程式人生 > 其它 >(todo)Linux 核心:裝置驅動模型(0)sysfs與kobject基類

(todo)Linux 核心:裝置驅動模型(0)sysfs與kobject基類

(todo)Linux 核心:裝置驅動模型(0)sysfs與kobject

背景

學習Linux 裝置驅動模型時,對 kobject 不太理解。因此,學習了一下。

現在我知道了:kobj/kset是如何作為統一裝置模型的基礎,以及到底提供了哪些功能。

以後我們就知道,在具體應用過程中,如device、bus甚至platform_device等是如何使用kobj/kset的。

參考:

核心版本:3.14

kobject, kset和ktype

要分析sysfs,首先就要分析kobject和kset,因為驅動裝置的層次結構的構成就是由這兩個東東來完成的。

sysfs與kobject密不可分。

kobject

kobject是一個物件的抽象,它用於管理物件。

kobject被用來控制訪問一個更大的,具有特定作用域的物件;為了達到這個作用,kobject將會被嵌入到其他結構體中。

如果你用面向物件的角度來看,kobject結構可以被看做頂層的抽象類,其他類都是從這個類派生出來的。

在sysfs中,每個kobject對應著一個目錄。

// include/linux/kobject.h
struct kobject {
     /* 對應sysfs的目錄名 */
    const char      *name;
    /*用於連線到所屬kset的連結串列中,用於將kobj掛在kset->list中*/
    struct list_head    entry;
    /*指向 父物件,形成層次結構,在sysfs中表現為父子目錄的關係*/
    struct kobject      *parent;
    /*
        屬於哪個kset
        表徵該kobj所屬的kset。
        kset可以作為parent的“候補”:當註冊時,傳入的parent為空時,可以讓kset來擔當。
    */
    struct kset     *kset;
     /*型別屬性,每個kobj或其嵌入的結構物件應該都對應一個kobj_type。 */
    struct kobj_type    *ktype;
    /*sysfs中與該物件對應的檔案節點物件*/
    // 在3.14以後的核心中,sysfs基於kernfs來實現。 
    struct kernfs_node  *sd;
    /*物件的應用計數*/
    struct kref     kref;

    /* 記錄初始化與否。呼叫kobject_init()後,會置位。 */
    unsigned int state_initialized:1;
    /* 記錄kobj是否註冊到sysfs,在kobject_add_internal()中置位。 */
    unsigned int state_in_sysfs:1;
    /* 當傳送KOBJ_ADD訊息時,置位。提示已經向用戶空間傳送ADD訊息。*/
    unsigned int state_add_uevent_sent:1;
    /* 當傳送KOBJ_REMOVE訊息時,置位。提示已經向用戶空間傳送REMOVE訊息。*/
    unsigned int state_remove_uevent_sent:1;
    /* 如果該欄位為1,則表示忽略所有上報的uevent事件。 */
    unsigned int uevent_suppress:1;
};

//include/linux/kref.h
struct kref {
    atomic_t refcount;
};

目前為止,Kobject主要提供如下功能:

  1. 構成層次結構:通過parent指標,可以將所有Kobject以層次結構的形式組合起來。
  2. 生命週期管理:使用引用計數(reference count),來記錄Kobject被引用的次數,並在引用次數變為0時把它釋放。
  3. 和sysfs虛擬檔案系統配合,將每一個Kobject及其特性,以檔案的形式,開放到使用者空間。

沒有一個結構會嵌入多於一個kobject結構,如果這麼做了,關於這個物件的引用計數肯定會一團糟,你的code也會充滿bug,所以千萬不要這麼做。

ktype

每個kobject物件都內嵌有一個ktype,該結構定義了kobject在建立和刪除時所採取的行為,可以理解為物件的屬性。

實際上,Kobject的生命週期管理就是由關聯的ktype來實現的。

注意,每個kobject必須關聯一個ktype。

由於通用性,多個Kobject可能共用同一個屬性操作集,因此把Ktype獨立出來了。

// include/linux/kobject.h
struct kobj_type {
    /* 處理物件終結的回撥函式。該介面應該由具體物件負責填充。 */
    void (*release)(struct kobject *kobj);
    /* 該型別kobj的sysfs操作介面。 */
    const struct sysfs_ops *sysfs_ops;
    /* 該型別kobj自帶的預設屬性(檔案),這些屬性檔案在註冊kobj時,直接pop為該目錄下的檔案。 */
    struct attribute **default_attrs;
    /*child_ns_type/namespace 是 檔案系統名稱空間相關)略*/
    const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
    const void *(*namespace)(struct kobject *kobj);
};

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

struct attribute {
    const char      *name;
    umode_t         mode;
};

當kobject的引用計數為0時,通過release方法來釋放相關的資源。

attribute為屬性,每個屬性在sysfs中都有對應的屬性檔案。

sysfs_op的兩個方法用於實現讀取和寫入屬性檔案時應該採取的行為。

kset

kset是一些kobject的集合,這些kobject可以有相同的ktype(屬性),也可以不同。

同時,kset自己也包含一個kobject;因此在sysfs中,kset也是對應這一個目錄,但是目錄下面包含著其他的kojbect。

也有一種說法,把Kset看是一個特殊的Kobject。

每個Kobject不一定出現在sys中,但Kset中的每個Kobject會出現在sys中。

// include/linux/kobject.h
/**
 * struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
 *
 * A kset defines a group of kobjects.  They can be individually
 * different "types" but overall these kobjects all want to be grouped
 * together and operated on in the same manner.  ksets are used to
 * define the attribute callbacks and other common events that happen to
 * a kobject.
 *
 * @list: the list of all kobjects for this kset
 * @list_lock: a lock for iterating over the kobjects
 * @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
 * @uevent_ops: the set of uevent operations for this kset.  These are
 * called whenever a kobject has something happen to it so that the kset
 * can add new environment variables, or filter out the uevents if so
 * desired.
 */
struct kset {
    /*屬於該kset的kobject連結串列,與kobj->entry對應,用來組織本kset管理的kobj*/
    struct list_head list;
    spinlock_t list_lock;
    /*該kset內嵌的kobj*/
    struct kobject kobj;
    
    /*
        kset用於傳送訊息的操作函式集。
        需要指出的是,kset能夠傳送它所包含的各種子kobj、孫kobj的訊息;
        即kobj或其父輩、爺爺輩,都可以傳送訊息;優先父輩,然後是爺爺輩,以此類推。 
    */
    const struct kset_uevent_ops *uevent_ops;
};

kset通過標準的核心連結串列(struct list_head list)來管理它的所有子節點,kobject通過kset成員變數指向它的kset容器。

大多數情況下,kobject都是它所屬kset(或者嚴格點,kset內嵌的kobject)的子節點。

kobject與kset的關係

下面這張圖非常經典。最下面的kobj都屬於一個kset,同時這些kobj的父物件就是kset內嵌的kobj

通過連結串列,kset可以獲取所有屬於它的kobj

sysfs角度而言,kset代表一個資料夾,而下面的kobj就是這個資料夾裡面的內容,而內容有可能是檔案也有可能是資料夾。

kobjkset沒有嚴格的層級關係,也並不是完全的父子關係。如何理解這句話?

  • kobject之間可以組成一個樹形結構:Kobj.parent = kobj'

  • kset之間也可以組成一個樹形結構,但這是基於kobject實現的:Kset.kobj.parent = Kset'.kobj

正因為kobj和kset並不是完全的父子關係,因此在註冊kobj時,將同時對parent及其所屬的kset增加引用計數。

若parent和kset為同一物件,則會對kset增加兩次引用計數。

kset算是kobj的“接盤俠”:

  • 當kobj沒有所屬的parent時,才讓kset來接盤當parent;
  • 如果連kset也沒有,那該kobj屬於頂層物件,其sysfs目錄將位於/sys/下。

待會我們看kobject_add_internal中是如何做的。

kset內部本身也包含一個kobj物件,在sysfs中也表現為目錄;所不同的是,kset要承擔kobj狀態變動訊息的傳送任務。因此,首先kset會將所屬的kobj組織在kset.list下,同時,通過uevent_ops在合適時候傳送訊息。

對於kobject_add()來說,它的輸入資訊是:kobj-parentkobj-namekobject_add()優先使用傳入的parent作為kobj->parent;其次,使用kset作為kobj->parent

kobj狀態變動後,必須依靠所關聯的kset來向用戶空間傳送訊息;若無關聯kset(kobj向上組成的樹中,任何成員都無所屬的kset),則kobj無法傳送使用者訊息。

kobject 結構可能的層次結構如圖:

舉例:通過kobject建立bus目錄

分析如何通過kobject/sys建立bus目錄。

// drivers/base/bus.c
static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
    struct kobj_type *ktype = get_ktype(kobj);

    if (ktype == &bus_ktype)
        return 1;
    return 0;
}

static const struct kset_uevent_ops bus_uevent_ops = {
    .filter = bus_uevent_filter,
};

int __init buses_init(void)
{
    // 直接呼叫kset_create_and_add,第一個引數為要建立的目錄的名字,而第三個引數指定父物件。
    bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);

    // ...

    return 0;
}

kset_create_and_add

kset_create_and_addkobject_createkobject_add的組合

// lib/kobject.c
/**
 * kset_create_and_add - create a struct kset dynamically and add it to sysfs
 *
 * @name: the name for the kset
 * @uevent_ops: a struct kset_uevent_ops for the kset
 * @parent_kobj: the parent kobject of this kset, if any.
 *
 * This function creates a kset structure dynamically and registers it
 * with sysfs.  When you are finished with this structure, call
 * kset_unregister() and the structure will be dynamically freed when it
 * is no longer being used.
 *
 * If the kset was not able to be created, NULL will be returned.
 */
struct kset *kset_create_and_add(const char *name,
                 const struct kset_uevent_ops *uevent_ops,
                 struct kobject *parent_kobj)
{
    struct kset *kset;
    int error;
    /*建立kset例項,設定某些欄位*/
    kset = kset_create(name, uevent_ops, parent_kobj);
    if (!kset)
        return NULL;
    /*新增kset到sysfs*/
    error = kset_register(kset);
    if (error) {
        kfree(kset);
        return NULL;
    }
    return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);


/**
 * kobject_add - the main kobject add function
 * @kobj: the kobject to add
 * @parent: pointer to the parent of the kobject.
 * @fmt: format to name the kobject with.
 *
 * The kobject name is set and added to the kobject hierarchy in this
 * function.
 *
 * If @parent is set, then the parent of the @kobj will be set to it.
 * If @parent is NULL, then the parent of the @kobj will be set to the
 * kobject associated with the kset assigned to this kobject.  If no kset
 * is assigned to the kobject, then the kobject will be located in the
 * root of the sysfs tree.
 *
 * If this function returns an error, kobject_put() must be called to
 * properly clean up the memory associated with the object.
 * Under no instance should the kobject that is passed to this function
 * be directly freed with a call to kfree(), that can leak memory.
 *
 * Note, no "add" uevent will be created with this call, the caller should set
 * up all of the necessary sysfs files for the object and then call
 * kobject_uevent() with the UEVENT_ADD parameter to ensure that
 * userspace is properly notified of this kobject's creation.
 */
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;
}
EXPORT_SYMBOL(kobject_add);

這裡主要呼叫了兩個函式,接下分別來看下。

kset_create

建立kset,設定名字與父物件。

路徑:lib/kobject.c

/**
 * kset_create - create a struct kset dynamically
 *
 * @name: the name for the kset
 * @uevent_ops: a struct kset_uevent_ops for the kset
 * @parent_kobj: the parent kobject of this kset, if any.
 *
 * This function creates a kset structure dynamically.  This structure can
 * then be registered with the system and show up in sysfs with a call to
 * kset_register().  When you are finished with this structure, if
 * kset_register() has been called, call kset_unregister() and the
 * structure will be dynamically freed when it is no longer being used.
 *
 * If the kset was not able to be created, NULL will be returned.
 */
static struct kset *kset_create(const char *name,
                const struct kset_uevent_ops *uevent_ops,
                struct kobject *parent_kobj)
{
    struct kset *kset;
    int retval;

    /*建立 kset 例項*/
    kset = kzalloc(sizeof(*kset), GFP_KERNEL);

    /*設定kobj->name*/
    retval = kobject_set_name(&kset->kobj, "%s", name);

    kset->uevent_ops = uevent_ops;
    /*設定父物件*/
    kset->kobj.parent = parent_kobj;

    /*
     * The kobject of this kset will have a type of kset_ktype and belong to
     * no kset itself.  That way we can properly free it when it is
     * finished being used.
     */
    kset->kobj.ktype = &kset_ktype;
    /*本keset不屬於任何kset*/
    kset->kobj.kset = NULL;

    return kset;
}

Issues

讀原始碼時有個疑問,當呼叫kset_create()時,為何kset->kobj.kset=NULL;
原始碼中的解釋如下:

/* 
 * The kobject of this kset will have a type of kset_ktype and belong to 
 * no kset itself. That way we can properly free it when it is 
 * finished being used. 
*/

但是當第三個引數parent_kobj有值時,既然kset->kobj.parent=parent_kobj; 那麼說明kset->kobj是有上層kset的,而這個上層kset應該可以用container_of由parent_kobj得出。

但為何在原始碼中直接就將kset->kobj.kset置為NULL了呢。是因為kset不會從屬於其它kset麼。謝謝

wowo

@Issues:

首先,kset是一個特殊的kobject,它是kobject的容器,用於將多個kobject收集在一起。那麼kset是否還會從屬於另一個kset?即容器是否會巢狀?從程式碼註釋上可以看出,答案是否定的。從實際邏輯上考慮,這也是合理的。這就是kset->kobj.kset為NULL的原因。
其次,雖然kset->kobj.kset為NULL,但為了kset的自動free,它還是需要一個ktype的,這就是為什麼kset->kobj.ktype不為NULL。
最後,您提到的kset->kobj.parent=parent_kobj,也就是說該kset可能會有一個上層的parent(也是一個kset),這就是另外一個含義了,kset之間可以組成一個樹形結構,這不是kset要描述的事情,而是kobject要描述的。

loren
2017-05-10 20:09

@wowo:“kset->kobj.parent=parent_kobj,能說明該kset可能會有一個上層的parent(也是一個kset)”
為什麼這個上層的parent必須是一個kset呢?不能這個parent不屬於任何一個kset嗎?

wowo
2017-05-11 08:56

@loren:是的,你說的對,kernel沒有規定這個parent kobj必須是什麼,它只是為了讓kset有個樹形結構,因而可以是另一個kset,也有可能是一個普通的資料結構。

不過看kernel程式碼,大部分時候,都是一個kset。因為kset的上級是kset,結構上比較清晰。

這個函式中,動態分配了kset例項,呼叫kobject_set_name設定kset->kobj->name為bus,也就是我們要建立的目錄bus

同時這裡kset->kobj.parentNULL,也就是沒有父物件。

因為要建立的bus目錄是在sysfs所在的根目錄建立的,自然沒有父物件。

隨後簡要看下由kobject_set_name函式呼叫引發的一系列呼叫。

// lib/kobject.c
/**
 * kobject_set_name_vargs - Set the name of an kobject
 * @kobj: struct kobject to set the name of
 * @fmt: format string used to build the name
 * @vargs: vargs to format the string.
 */
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
                  va_list vargs)
{
    const char *old_name = kobj->name;
    char *s;

    if (kobj->name && !fmt)
        return 0;

    kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
    if (!kobj->name) {
        kobj->name = old_name;
        return -ENOMEM;
    }

    /* ewww... some of these buggers have '/' in the name ... */
    while ((s = strchr(kobj->name, '/')))
        s[0] = '!';

    kfree(old_name);
    return 0;
}

// lib/kasprintf.c
/* Simplified asprintf. */
char *kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
{
    unsigned int len;
    char *p;
    va_list aq;

    va_copy(aq, ap);
    len = vsnprintf(NULL, 0, fmt, aq);
    va_end(aq);

    p = kmalloc_track_caller(len+1, gfp);
    if (!p)
        return NULL;

    vsnprintf(p, len+1, fmt, ap);

    return p;
}
EXPORT_SYMBOL(kvasprintf);

kset_register

// lib/kobject.c
/**
 * kset_register - initialize and add a kset.
 * @k: kset.
 */
int kset_register(struct kset *k)
{
    int err;

    if (!k)
        return -EINVAL;

    /*初始化kset*/
    kset_init(k);
     /*在sysfs中建立目錄*/
    err = kobject_add_internal(&k->kobj);
    if (err)
        return err;
    
    /* 向用戶空間傳送 KOBJ_ADD事件。 */
    kobject_uevent(&k->kobj, KOBJ_ADD);
    return 0;
}

這裡面呼叫了3個函式。這裡先介紹前兩個函式。kobject_uevent實現的是使用者空間事件傳遞,留到以後再說。

kset_init

該函式用於初始化kset。

/**
 * kset_init - initialize a kset for use
 * @k: kset
 */
void kset_init(struct kset *k)
{
    /*初始化kobject的某些欄位*/
    kobject_init_internal(&k->kobj);
    /*初始化連結串列頭*/
    INIT_LIST_HEAD(&k->list);
    /*初始化自旋鎖*/
    spin_lock_init(&k->list_lock);
}

static void kobject_init_internal(struct kobject *kobj)
{
    if (!kobj)
        return;
    /*初始化引用基計數*/
    kref_init(&kobj->kref);
    /*初始化連結串列頭*/
    INIT_LIST_HEAD(&kobj->entry);
    kobj->state_in_sysfs = 0;
    kobj->state_add_uevent_sent = 0;
    kobj->state_remove_uevent_sent = 0;
    kobj->state_initialized = 1;
}
kobject_add_internal

該函式將在sysfs中建立目錄。

kobject_add_internal()會根據kobj.parentkobj.kset來綜合決定父物件(相當於/sysfs中的父級目錄):

  • 如果有parent則預設parent為父物件;
  • 如果沒有parent作為父物件,但是有當前的kobj位於kset中,則使用kset中的kobj作為父物件
  • 如果沒有parent也沒有kset作為父物件,那該kobj屬於頂層物件:在sysfs中,我們可以看到kobj位於/sys/下。
static int kobject_add_internal(struct kobject *kobj)
{
    int error = 0;
    struct kobject *parent;

    if (!kobj)
        return -ENOENT;

    /*檢查name欄位是否存在*/
    if (!kobj->name || !kobj->name[0]) {
        return -EINVAL;
    }

    /*有父物件則增加父物件引用計數*/
    parent = kobject_get(kobj->parent);

    /* join kset if set, use it as parent if we do not already have one */
    /*
        由於在kset_create中有kset->kobj.kset = NULL,
        因此if (kobj->kset)條件不滿足。
    */
    if (kobj->kset) {
        if (!parent)
            /*kobj屬於某個kset,但是該kobj沒有父物件,則以kset的kobj作為父物件*/
            parent = kobject_get(&kobj->kset->kobj);
        /*將kojbect新增到kset結構中的連結串列當中*/
        kobj_kset_join(kobj);
        /* 根據kobj.parent和kobj.kset來綜合決定父物件*/
        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>");

    /*根據kobj->name在sys中建立目錄*/
    error = create_dir(kobj);
    // 如果出錯時,回收資源
    if (error) {
        /* 把kobj從kobj->kset的連結串列中去除 */
        // 對應於 kobj_kset_join
        kobj_kset_leave(kobj);
        kobject_put(parent);
        kobj->parent = NULL;

        /* be noisy on error issues */
        if (error == -EEXIST)
            WARN(1, "%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(1, "%s failed for %s (error: %d parent: %s)\n",
                 __func__, kobject_name(kobj), error,
                 parent ? kobject_name(parent) : "'none'");
    } else
        kobj->state_in_sysfs = 1;

    return error;
}

因此在這個函式中,對name進行了必要的檢查之後,呼叫了create_dir在sysfs中建立目錄。

create_dir執行完成以後會在sysfs的根目錄(/sys/)建立資料夾bus。該函式的詳細分析將在後面給出。

至此,對bus目錄的建立有了簡單而直觀的瞭解。

我們可以看出kset其實就是表示一個資料夾,而kset本身也含有一個kobject,而該kobject的name欄位即為該目錄的名字,本例中為bus。

kobj/kset功能特性

我們先大概提一下這些功能特性,在後面的文章中會詳細說明:物件生命週期管理以及使用者空間事件投遞

物件生命週期管理

在建立一個kobj物件時,kobj中的引用計數管理成員kref被初始化為1;從此kobj可以使用下面的API函式來進行生命週期管理:

// lib/kobject.c
/**
 * 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 - decrement refcount for object.
 * @kobj: object.
 *
 * Decrement the refcount, and if 0, call kobject_cleanup().
 */
void kobject_put(struct kobject *kobj)
{
    if (kobj) {
        kref_put(&kobj->kref, kobject_release);
    }
}

static void kobject_release(struct kref *kref)
{
    struct kobject *kobj = container_of(kref, struct kobject, kref);

    kobject_cleanup(kobj);
}

對於kobject_get(),它就是直接使用kref_get()介面來對引用計數進行加1操作;

而對於kobject_put(),它不僅要使用kref_put()介面來對引用計數進行減1操作,還要對生命終結的物件執行release()操作:當引用計數減為0時,回收該物件的資源。

然而kobject是高度抽象的實體,導致kobject不會單獨使用,而是嵌在具體物件中。反過來也可以這樣理解:凡是需要做物件生命週期管理的物件,都可以通過內嵌kobject來實現需求。

下列的東西寫入 bus 驅動中

回到`kobject_put()`,它通常被具體物件做一個簡單包裝,如bus_put(),它直接呼叫kset_put(),然後呼叫到kobject_put()。那對於這個bus_type物件而言,僅僅通過kobject_put(),如何來達到釋放整個bus_type的目的呢?這裡就需要kobject另一個成員`struct kobj_type * ktype`來完成。 

回到kobject_put()的release()操作。當引用計數為0時,kobject核心會呼叫kobject_release(),最後會呼叫`kobj_type->release(kobj)`來完成物件的釋放。可是具體物件的釋放,最後卻通過`kobj->kobj_type->release()`來釋放,那這個`release()`函式,就必須得由具體的物件來指定。

還是拿bus_type舉例:

在通過`bus_register(struct bus_type *bus)`進行匯流排註冊時,該API內部會執行`priv->subsys.kobj.ktype = &bus_ktype`操作;

有了該操作,那麼前面的`bus_put()`在執行`bus_type->p-> subsys.kobj->ktype->release()`時,就會執行上面註冊的`bus_ktype.release = bus_releas`e函式,由於bus_release()函式由具體的bus子系統提供,它必定知道如何釋放包括kobj在內的bus_type物件。 

sysfs檔案系統的層次組織

sysfs向用戶空間展示了驅動裝置的層次結構。這一個功能比較簡單,先完全貼出來。

我們都知道裝置和對應的驅動都是由核心管理的,這些對於使用者空間是不可見的。現在通過sysfs,可以在使用者空間直觀的瞭解裝置驅動的層次結構。

實際上,sysfs檔案系統的組織功能是基於kobject實現的:該功能依靠kobj的parent、kset、sd等成員來完成。sysfs檔案系統能夠以友好的介面,將kobj所刻畫的物件層次、所屬關係、屬性值,展現在使用者空間。

我們來看看sysfs的檔案結構:

$ uname -r
4.15.0-142-generic

$ ls /sys
block  bus  class  dev  devices  firmware  fs  hypervisor  kernel  module  power
目錄 意義
block 塊裝置
bus 系統中的匯流排
class 裝置型別,比如輸入裝置
dev 系統中已註冊的裝置節點的檢視,有兩個子目錄char和block。
devices 系統中所有裝置拓撲結構檢視
fireware 韌體
fs 檔案系統
kernel 核心配置選項和狀態資訊
module 模組
power 系統的電源管理資料

使用者空間事件投遞

這方面的內容可參考《http://www.wowotech.net/device_model/uevent.html》,該博文已經詳細的說明了使用者空間事件投遞。

具體物件有事件發生時,相應的操作函式中(如device_add()),會呼叫事件訊息介面kobject_uevent(),在該介面中,首先會新增一些共性的訊息(路徑、子系統名等),然後會找尋合適的kset->uevent_ops,並呼叫該ops->uevent(kset, kobj, env)來新增該物件特有的事件訊息(如device物件的裝置號、裝置名、驅動名、DT資訊);

一切準備完畢,就會通過兩種可能的方法向用戶空間傳送訊息:1.netlink廣播;2. uevent_helper程式。 

因此,上面具體物件的事件訊息填充函式,應該由特定物件來填充;對於device物件來說,在初始化的時候,通過下面這行程式碼: `devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);`

來完成kset->uevent_ops的指定,今後所有設備註冊時,呼叫device_register()-->device_initialize()後,都將導致: 

dev->kobj.kset = devices_kset;

因此通過device_register()註冊的裝置,在呼叫kobject_uevent()介面傳送事件訊息時,就自動會呼叫devices_kset的device_uevent_ops。該ops的uevent()方法定義如下: 

> static const struct kset_uevent_ops device_uevent_ops = { 
>
> ​     .filter =   dev_uevent_filter, 
>
> ​     .name =       dev_uevent_name, 
>
> ​     .uevent = dev_uevent, 
>
> };        || 
>
> ​         \/ 
>
> dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env) 
>
> ​     add_uevent_var(env, "xxxxxxx", ....) 
>
> ​     add_uevent_var(env, "xxxxxxx", ....) 
>
> ​     add_uevent_var(env, "xxxxxxx", ....) 
>
> ​     .... 
>
> ​     if (dev->bus && dev->bus->uevent) 
>
> ​          dev->bus->uevent(dev, env)    //通過匯流排的uevent()方法,傳送裝置狀態改變的事件訊息 
>
> ​     if (dev->class && dev->class->dev_uevent) 
>
> ​          dev->class->dev_uevent(dev, env) 
>
> ​     if (dev->type && dev->type->uevent) 
>
> ​          dev->type->uevent(dev, env) 

在該ops的uevent()方法中,會分別呼叫bus、class、device type的uevent方法來生成事件訊息。 

總結

kobject可以看成是一個基類,這個基類通過ktype屬性實現了資源回收的功能,至於kset,負責記錄一組kobject,我們將其看成是一個集合,負責事件管理。

從此,其他物件就可以通過包含kobject來實現上層的功能。

kobject_create_and_add解析

// lib/kobject.c
/**
 * kobject_create_and_add - 動態建立一個kobject結構並註冊到sysfs
 *
 * @name: kobject的名稱
 * @parent: kobject的parent kobject of this kobject, 如果有的話
 *
 * 該方法動態建立一個kobject結構並註冊到sysfs。當你完成該結構
 * 之後. 呼叫kobject_put(),這樣該結構在不再使用時將會動態的釋放。
 *
 * 如果該kobject無法被建立,將會返回NULL。
 */
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
    struct kobject *kobj;
    int retval;

    kobj = kobject_create();
    if (!kobj)
        return NULL;

    retval = kobject_add(kobj, parent, "%s", name);
    if (retval) {
        printk(KERN_WARNING "%s: kobject_add error: %d\n",  __func__, retval);
        kobject_put(kobj);
        kobj = NULL;
    }
    return kobj;
}

kobject_create

/**
 * kobject_create - 動態建立一個kobject結構
 *
 * 動態建立一個kobject結構,並將其設定為一個帶預設釋放方法的
 * 動態的kobject。
 *
 * 如果無法建立kobject,將會返回NULL。從這裡返回的kobject 結構釋放
 * 必須呼叫kobject_put() 方法而不是kfree(),因為kobject_init()已經被呼叫
 * 過了。
 */
struct kobject *kobject_create(void)
{
    struct kobject *kobj;

    kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
    if (!kobj)
        return NULL;

    kobject_init(kobj, &dynamic_kobj_ktype);
    return kobj;
}

kobject_init

/**
 * kobject_init - 初始化一個kobject結構
 * @kobj: 指向要初始化的kobject的指標
 * @ktype: 指向該kobject 的ktype的指標
 *
 * 該方法會正確的初始化一個kobject來保證它可以被傳遞給kobject_add()
 * 呼叫.
 *
 * 該功能被呼叫後,kobject必須通過呼叫kobject_put()來清理,而不是直接
 * 呼叫kfree,來保證所有的記憶體都可以被正確的清理。
 */
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

/* 設定初始化狀態 */
static void kobject_init_internal(struct kobject *kobj)
{
    if (!kobj)
        return;
    kref_init(&kobj->kref);
    INIT_LIST_HEAD(&kobj->entry);
    // 記錄kobj是否註冊到sysfs,在kobject_add_internal()中置位。
    kobj->state_in_sysfs = 0;
    // 當傳送KOBJ_ADD訊息時,置位。提示已經向用戶空間傳送ADD訊息。
    kobj->state_add_uevent_sent = 0;
    // 當傳送KOBJ_REMOVE訊息時,置位。提示已經向用戶空間傳送REMOVE訊息。
    kobj->state_remove_uevent_sent = 0;
    // 標記已經初始化了。
    kobj->state_initialized = 1;
}


/**
 * kobject_add - kobject新增主方法
 * @kobj: 要新增的kobject
 * @parent: 指向父kobject的指標
 * @fmt: kobject帶的名稱格式
 *
 * 本方法中會設定kobject名稱並新增到kobject階層。
 *
 * 如果設定了@parent, 那麼@kobj的parent將會被設定為它.
 * 如果 @parent為空, 那麼該@kobj的 parent會被設定為與該kobject
 * 相關聯的.  如果沒有kset分配給這個kobject,那麼該kobject會放在
 * sysfs的根目錄。
 *
 * 如果該方法返回錯誤,必須呼叫 kobject_put()來正確的清理該object
 * 相關聯的記憶體。
 * 在任何情況下都不要直接通過呼叫to kfree()來直接釋放傳遞給該方法
 * 的kobject,那樣會導致記憶體洩露。
 *
 * 注意,該呼叫不會建立"add" uevent, 呼叫方需要為該object設定好所有
 * 必要的sysfs檔案,然後再呼叫帶UEVENT_ADD 引數的kobject_uevent() 
 * 來保證使用者控制元件可以正確的收到該kobject建立的通知。
 */
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_set_name_vargs - 設定一個kobject的名稱
 * @kobj: 要設定名稱的kobject
 * @fmt: 用來構建名稱的字串格式
 * @vargs: 構建字串的引數
 */
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
                           va_list vargs)
{
    const char *old_name = kobj->name;
    char *s;

    if (kobj->name && !fmt)
        return 0;

    kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
    if (!kobj->name) {
        kobj->name = old_name;
        return -ENOMEM;
    }


    /* ewww... some of these buggers have '/' in the name ... */
    while ((s = strchr(kobj->name, '/')))
        s[0] = '!';

    kfree(old_name);
    return 0;
}

API與使用

由於我們現在關心的是裝置驅動模型,因此暫不瞭解。等到具體的程式碼中,我們再進行解析。

例子:https://blog.csdn.net/yldfree/article/details/84393321

https://www.cnblogs.com/helloahui/p/3674933.html

## Kobject的使用

### 使用流程

kobject是高度抽象的實體,導致**在大多數情況下**,kobject基本上不會單獨使用,而是嵌在具體物件中。

其使用流程如下:

1、定義一個`struct kset`型別的指標,並在初始化時為它分配空間,新增到核心中

2、根據實際情況,定義自己所需的資料結構原型,該資料結構中包含有`Kobject`

3、定義一個適合自己的`ktype`,並實現其中回撥函式

4、在需要使用到包含`Kobject`的資料結構時,動態分配該資料結構,並分配`Kobject`空間,新增到核心中

5、每一次引用資料結構時,呼叫`kobject_get`介面增加引用計數;引用結束時,呼叫`kobject_put`介面,減少引用計數

6、當引用計數減少為0時,`Kobject`模組呼叫`ktype`所提供的`release`介面,釋放上層資料結構以及`Kobject`的記憶體空間

上面有提過,有一種例外,Kobject不再嵌在其它資料結構中,可以單獨使用,這個例外就是:開發者只需要在sysfs中建立一個目錄,而不需要其它的kset、ktype的操作。這時可以直接呼叫kobject_create_and_add介面,分配一個kobject結構並把它新增到kernel中。

### kobject初始化

建立kobject當然必須初始化kobject物件,kobject的一些內部成員需要(強制)通過kobject_init()初始化:

\```c
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
\```

在呼叫kobject_init將kobject註冊到sysfs之後,必須呼叫kobject_add()新增:

\```c
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
\```

它正確設定了kobject的名稱和父節點,如果這個kobject和一個特定的kset關聯,則kobj->kset必須在呼叫`kobject_add`之前被指定。

若kobject已經跟一個kset關聯,在呼叫kobject_add時可以將這個kobject的父節點設定為NULL,這樣它的父節點就會被設定為這個kset本身。

在將kobject新增到kernel時,它的名稱就已經被設定好了,程式碼中不應該直接操作kobject的名稱。

如果你必須為kobject改名,則呼叫kobject_rename()函式實現:

\```c
int kobject_rename(struct kobject *kobj, const char *new_name);
\```

kobject_rename內部並不執行任何鎖定操作,也沒用何時名稱是有效的概念。因此呼叫者必須自己實現完整性檢查和保證序列性。

> 有一個函式叫kobject_set_name(),但是它遺留了一些缺陷,目前正在被移除。如果你的程式碼需要呼叫這個函式,它是不正確的,需要被修正。

可以通過kobject_name()函式來正確獲取kobject的名稱:

\```c
const char *kobject_name(const struct kobject * kobj);
\```

輔助函式`kobject_init_and_add`用來在同時初始化和新增kobject,它的引數和前面介紹的單獨使用kobject_init()、kobject_add()這兩個函式時一樣

\```c
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
                         struct kobject *parent, const char *fmt, ...);
\```

**建立簡單的kobject**

有些時候,開發者希望的只是在sysfs中建立一個目錄,並且不會被kset、show、store函式等細節的複雜概念所迷惑。

這裡有一個可以單獨建立kobject的例外,為了建立這樣一個入口,可以通過如下的函式來實現:

\```c
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
\```

這個函式會建立一個kobject,並把它的目錄放到指定父節點在sysfs中目錄裡面。

可以通過如下的函式簡單建立kobject相關聯的屬性:

\```c
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
\```

或者

\```c
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
\```

這兩種方法都包含了屬性型別,和通過`kobject_create_and_add()`建立的kobject型別;屬性型別可以直接使用kobj_attribute型別,所以不需要特別建立自定義屬性。

### 分配和釋放