Linux 內核:設備驅動模型(1)sysfs與kobject基類


Linux 內核:設備驅動模型(1)sysfs與kobject基類

背景

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

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

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

系列:Linux 內核:設備驅動模型 學習總結

參考:

內核版本:3.14

kobject, kset和ktype

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

sysfs與kobject密不可分。

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,所以千萬不要這么做。

實際上,kobject對自身實現什么功能並不感興趣,它存在的意義在於把高級的對象鏈接到設備模型上。因此內核代碼很少去創建一個單獨的kobject對象,相反,kobject用於控制對大型域相關對象的訪問(通過container_of)。

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.
 */
/**
 * kobject_create_and_add - 動態創建一個kobject結構並注冊到sysfs
 *
 * @name: kobject的名稱
 * @parent: kobject的parent kobject of this kobject, 如果有的話
 *
 * 該方法動態創建一個kobject結構並注冊到sysfs。當你完成該結構
 * 之后. kobject_del(),這樣該結構在不再使用時將會動態的釋放。
 *
 * 如果該kobject無法被創建,將會返回NULL。
 */
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;
}

這個函數中,動態分配了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是否注冊到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_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來實現需求。

kobject在kobject_put的資源回收是如何實現的?

實際上,kobject_put()通常被具體對象做一個簡單包裝,如:bus_put(),它直接調用kset_put(),然后調用到kobject_put()。

那對於這個bus_type對象而言,僅僅通過kobject_put(),如何來達到釋放整個bus_type的目的呢?這里就需要kobject另一個成員struct kobj_type * ktype來完成。

當引用計數為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操作;

// driver/base/bus.c
int bus_register(struct bus_type *bus)
{
    // ...
    
    priv->subsys.kobj.ktype = &bus_ktype;
    
    // ...
}

有了該操作,那么前面的bus_put()在執行bus_type->p-> subsys.kobj->ktype->release()時,就會執行上面注冊的bus_ktype.release = bus_release函數;

// driver/base/bus.c
static struct kobj_type bus_ktype = {
    .sysfs_ops  = &bus_sysfs_ops,
    .release    = bus_release,
};

static void bus_release(struct kobject *kobj)
{
    // 獲取整個 具體的 bus子系統 對象
    struct subsys_private *priv =
        container_of(kobj, typeof(*priv), subsys.kobj);
    struct bus_type *bus = priv->bus;

    // 釋放資源
    kfree(priv);
    bus->p = NULL;
}

由於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 系統的電源管理數據

用戶空間事件投遞

請參考:uevent與熱插拔

實際上,當具體對象有事件發生時,相應的操作函數中(如device_add()),會調用事件消息接口kobject_uevent()

在該接口中,首先會添加一些共性的消息(路徑、子系統名等),然后會找尋合適的kset->uevent_ops,並調用該ops->uevent(kset, kobj, env)來添加該對象特有的事件消息(如device對象的設備號、設備名、驅動名、DT信息);

kobject總結

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

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

sysfs

對象模型體系

sysfs(system filesystem)是一個處於內存中的虛擬文件系統,為我們提供了kobject對象層次結構的視圖。以一個簡單的文件系統的方式來觀察系統中各種設備的拓撲結構。

sysfs是用於表現設備驅動模型的文件系統,它基於ramfs。要學習linux的設備驅動模型,就要先做好底層工作,總結sysfs提供給外界的API就是其中之一。

sysfs文件系統中提供了四類文件的創建與管理,分別是目錄、普通文件、軟鏈接文件、二進制文件。

  • 目錄層次往往代表着設備驅動模型的結構
  • 軟鏈接文件則代表着不同部分間的關系。比如某個設備的目錄只出現在/sys/devices下,其它地方涉及到它時只好用軟鏈接文件鏈接過去,保持了設備唯一的實例。
  • 普通文件和二進制文件往往代表了設備的屬性,讀寫這些文件需要調用相應的屬性讀寫。

sysfs是表現設備驅動模型的文件系統,它的目錄層次實際反映的是對象的層次。為了配合這種目錄,linux專門提供了兩個結構作為sysfs的骨架,它們就是struct kobject和struct kset。

我們知道,sysfs是完全虛擬的,它的每個目錄其實都對應着一個kobject,要想知道這個目錄下有哪些子目錄,就要用到kset。從面向對象的角度來講,kset繼承了kobject的功能,既可以表示sysfs中的一個目錄,還可以包含下層目錄。

dentry結構體表示目錄項,通過連接kobject到指定的目錄項上,無疑方便的將kobject映射到該目錄上。

因此,kobjects其實已經形成了一棵樹了——就是對象模型體系。

由於kobject被映射到目錄項,同時對象層次結構也已經在內存中形成了一棵樹,因此sysfs就誕生了。

$ tree  /sys | head -n 300
/sys
├── block
    ...
├── bus
│   ├── cpu
│   │   ├── devices
│   │   │   └── cpu0 -> ../../../devices/system/cpu/cpu0
│   │   ├── drivers
│   │   │   └── processor
│   │   │       ├── bind
│   │   │       ├── cpu0 -> ../../../../devices/system/cpu/cpu0
│   │   │       ├── uevent
│   │   │       └── unbind
│   │   ├── drivers_autoprobe
│   │   ├── drivers_probe
│   │   └── uevent
...

$ ls /sys
block  bus  class  dev  devices  firmware  fs  kernel  module  power

sysfs的根目錄下包含了七個目錄:block、bus、class、devices、firmware、module和power。

  • block目錄下的每個子目錄都對應着系統的一個塊設備。
  • bus目錄提供了一個系統總線視圖。
  • class目錄包含了以高層功能邏輯組織起來的系統設備視圖。
  • devices目錄是系統中設備拓撲結構視圖,它直接映射除了內核中設備結構體的組織層次。
  • firmware目錄包含了一些諸如ACPI、EDD、EFI等低層子系統的特殊樹。
  • power目錄包含了系統范圍的電源管理數據。
  • 最重要的目錄是devices,該目錄將設備模型導出到用戶空間。目錄結構就是系統中實際的設備拓撲。

kernel 包括了一些內核的可調參數等信息,和devices目錄關聯性沒那么強。

注意,其他目錄中的很多數據都是將devices目錄下的數據加以轉換加工而得;

因此,在class章節中,我們會看到一些軟鏈接的創建,其實就是為了優雅地歸對設備進行歸類。

sysfs中添加和刪除kobject

導入kobject到sysfs

僅僅初始化kobjcet是不能自動將其導出到sysfs中的,想要把kobject導入sysfs,需要用到函數kobject_add():

需要注意的是,並不是說每一個kobject對象都需要在sysfs中表示,但是每一個被注冊到系統中的kset都會被添加到sysfs文件系統中。

一個kset對象就對應一個/sys中的一個目錄,kset中的每一個kobject成員,都對應sysfs中一個文件或者一個目錄。

#include <linux/kobject.h> 
#if 0 // 下面兩種操作等價
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj);
#else
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
// 再也沒有 kobject_register 這個函數了
#endif

kobject在sysfs中的位置取決於kobject在對象層次結構中的位置。如果kobject的父指針被設置,那么在sysfs中的kobject將被映射為其父目錄下的子目錄。如果parent沒有設置,那么kobject將被映射為kset->kobj中的子目錄。

如果給定的kobject中parent或kset字段都沒有設置,那么就認為kobject沒有父對象,所以就會被映射成sysfs下的根級目錄。但這往往不是你所需要的,所以在調用kobject_add()前parent或kset字段應該顯示的設置。

不管怎樣,sysfs中代表kobject的目錄名字是由kobj->k_name指定的。

不過不需要調用kobject_init()kobject_add(),因為系統提供了函數kobject_create_and_add()

該函數既初始化了給定的kobject對象,同時又將其加入到對象層次結構中。

從sysfs刪除kobject

void kobject_del(struct kobject *kobj);

從sysfs中刪除一個kobject對應文件目錄。需使用函數kobject_del(),內部會自動使用put來讓引用減一。

以前的版本需要再調用一次kobject_put;但現在不需要了。

向sysfs中添加文件

kobject已經被映射為文件目錄。所有的對象層次一個不少的映射成sys下的目標結構。

sysfs僅僅是一個漂亮的樹,但是沒有提供實際數據的文件。

因此,需要kobj_type來進行支持,有關sysfs的成員如下:

// include/linux/kobject.h
struct kobj_type {
    // ...
    /* 該類型kobj的sysfs操作接口。 */
    const struct sysfs_ops *sysfs_ops;
    /* 該類型kobj自帶的缺省屬性(文件),這些屬性文件在注冊kobj時,直接pop為該目錄下的文件。 */
    struct attribute **default_attrs;
};

默認屬性attribute

默認的文件集合是通過Kobject和kset的ktype字段提供的。因此所有具有相同類型的kobject在它們對應的sysfs目錄下都擁有相同的默認文件集合。

kobj_type字段含有一個字段——default_attrs,它是一個attribute結構體數組。這些屬性負責將內核數據映射成sysfs中的文件。

attribute原型
// include/linux/sysfs.h
#include <linux/sysfs.h>
struct attribute {
    /* 屬性名稱 */
    const char      *name;
    /* 訪問權限 */
    umode_t         mode;
    // ...
};

struct attribute_group {
    const char      *name;
    umode_t         (*is_visible)(struct kobject *,
                          struct attribute *, int);
    struct attribute    **attrs;
    struct bin_attribute    **bin_attrs;
};

struct bin_attribute {
    struct attribute    attr;
    size_t          size;
    void            *private;
    ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
            char *, loff_t, size_t);
    ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
             char *, loff_t, size_t);
    int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
            struct vm_area_struct *vma);
};

其中,name:提供了該屬性的名稱,最終出現在sysfs中的文件名就是它。

owner(todo):在存在所屬模塊的情況下指向其所屬module結構體。如果一個模塊沒有該屬性,那么該字段為NULL。

mode字段類型為mode_t,它表示了sysfs中該文件的權限。

  • 對於只讀屬性而言,如果是所有人都可讀它,那么該字段設為S_IRUGO;
  • 如果只限於所有者可讀,則該字段設置為S_IRUSR。
  • 同樣對於可寫屬性,可能會設置該字段為S_IRUGO|S_IWUSR。
  • sysfs中的所有文件和目錄的uid與gid標志均為零。

如何理解屬性?

$ ls /sys/class/leds/input1::capslock
brightness  device  max_brightness  power  subsystem  trigger  uevent

其中,brightnesstrigger這些文件就是屬性,以文件的形式提供。

屬性讀寫方法sysfs_ops

雖然default_attrs列出了默認的屬性,sysfs_ops則描述了如何使用它們。

#include <linux/sysfs.h>
struct sysfs_ops {
    /* 讀取該 sysfs 文件時該方法被調用 */
    ssize_t (*show)(struct kobject *, struct attribute *, char *);
    /* 寫入該 sysfs 文件時該方法被調用 */
    ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

struct sysfs_ops中包含show和store兩個函數指針,它們分別在sysfs文件讀和文件寫時調用。

創建/刪除新屬性

一些特別情況下會碰到特殊的kobject實例,它希望(甚至必須)有自己的屬性——也許是通用屬性沒包含那些需要的數據或者函數。

因此使用sysfs_create_file()接口在默認集合上添加新屬性:

#include <linux/sysfs.h>
int sysfs_create_file(struct kobject *kobj,
                      const struct attribute *attr);
int sysfs_create_files(struct kobject *kobj,
                       const struct attribute **attr);

void sysfs_remove_file(struct kobject *kobj,
                       const struct attribute *attr)

這個接口通過attr參數指向相應的attribute結構體,而參數kobj則指定了屬性所在的kobject對象。

在該函數被調用之前,給定的屬性將被賦值,如果成功,該函數返回零。否則返回負的錯誤碼。

kobject中ktype.sysfs_ops操作將負責處理新屬性。現有的show()store()方法必須能夠處理新屬性。

刪除一個屬性通過函數sysfs_remove_file()完成:一旦調用sysfs_remove_file,給定的屬性將不復存在於給定的kobject目錄中。

創建/刪除軟鏈接

除了添加文件外,可能還要創建符號連接。在sysfs中創建一個符號連接方式。

int  sysfs_create_link(struct kobject *kobj, struct kobject *target,
                       const char *name);
int sysfs_create_link_nowarn(struct kobject *kobj,
                             struct kobject *target,
                             const char *name);

該函數創建的符號連接名由name指定,連接則由kobj對應的目錄映射到target指定的目錄。如果成功,則返回零,如果失敗,返回負的錯誤碼。

而由sysfs_create_link()創建的符號連接可通過函數sysfs_remove_link()刪除:

void sysfs_remove_link(struct kobject *kobj, const char *name);
// 一旦調用返回,給定的連接將不復存在於給定的kobject目錄中。

sysfs約定

首先sysfs屬性應該保證每個屬性文件只導出一個值,該值應該是文本形式而且被映射為簡單C類型。其次,在sysfs中要以一個清晰的層次組織數據。最后,記住sysfs提供內核到用戶空間的服務,這多少有些用戶空間的ABI(應用程序二進制接口)的作用。

附錄:kobject創建

簡單創建kobj以及添加

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kobject.h>

struct kobject *g_obj = NULL;
struct kobject *g_parobj = NULL;

static int __init  test_init(void)
{    
    g_parobj  = kobject_create_and_add("dog",NULL);
    if(NULL == g_parobj)
    {
        printk("create kobj error\n");
        return -1;
    }

    g_obj  = kobject_create_and_add("whitedog",g_parobj);
    if(NULL == g_obj)
    {
        if(g_parobj)
        {     
            kobject_del(g_parobj); 
            kobject_put(g_parobj);
        }
    }
    return 0;
}

static void __exit test_exit(void)
{
    if(g_obj)
    {
        kobject_del(g_obj); 
        kobject_put(g_obj);
    }
    if(g_parobj)
    {
        kobject_del(g_parobj); 
        kobject_put(g_parobj);
    }

    return;
}

module_init(test_init);
module_exit(test_exit); 
MODULE_LICENSE("GPL");

內核kobj例子

kobject-example.c

代碼來自:samples/kobject/kobject-example.c

可能會遇到error: negative width in bit-field ‘<anonymous>’這個錯誤,需要將__ATTR中的666權限改為664

/*
 * Sample kobject implementation
 *
 * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
 * Copyright (C) 2007 Novell Inc.
 *
 * Released under the GPL version 2 only.
 *
 */
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>

/*
 * This module shows how to create a simple subdirectory in sysfs called
 * /sys/kernel/kobject-example  In that directory, 3 files are created:
 * "foo", "baz", and "bar".  If an integer is written to these files, it can be
 * later read out of it.
 */

static int foo;
static int baz;
static int bar;

/*
 * The "foo" file where a static variable is read from and written to.
 */
static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
			char *buf)
{
	return sprintf(buf, "%d\n", foo);
}

static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
			 const char *buf, size_t count)
{
	sscanf(buf, "%du", &foo);
	return count;
}

static struct kobj_attribute foo_attribute =
	__ATTR(foo, 0664, foo_show, foo_store);

/*
 * More complex function where we determine which variable is being accessed by
 * looking at the attribute for the "baz" and "bar" files.
 */
static ssize_t b_show(struct kobject *kobj, struct kobj_attribute *attr,
		      char *buf)
{
	int var;

	if (strcmp(attr->attr.name, "baz") == 0)
		var = baz;
	else
		var = bar;
	return sprintf(buf, "%d\n", var);
}

static ssize_t b_store(struct kobject *kobj, struct kobj_attribute *attr,
		       const char *buf, size_t count)
{
	int var;

	sscanf(buf, "%du", &var);
	if (strcmp(attr->attr.name, "baz") == 0)
		baz = var;
	else
		bar = var;
	return count;
}

static struct kobj_attribute baz_attribute =
	__ATTR(baz, 0664, b_show, b_store);
static struct kobj_attribute bar_attribute =
	__ATTR(bar, 0664, b_show, b_store);


/*
 * Create a group of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *attrs[] = {
	&foo_attribute.attr,
	&baz_attribute.attr,
	&bar_attribute.attr,
	NULL,	/* need to NULL terminate the list of attributes */
};

/*
 * An unnamed attribute group will put all of the attributes directly in
 * the kobject directory.  If we specify a name, a subdirectory will be
 * created for the attributes with the directory being the name of the
 * attribute group.
 */
static struct attribute_group attr_group = {
	.attrs = attrs,
};

static struct kobject *example_kobj;

static int __init example_init(void)
{
	int retval;

	/*
	 * Create a simple kobject with the name of "kobject_example",
	 * located under /sys/kernel/
	 *
	 * As this is a simple directory, no uevent will be sent to
	 * userspace.  That is why this function should not be used for
	 * any type of dynamic kobjects, where the name and number are
	 * not known ahead of time.
	 */
	example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
	if (!example_kobj)
		return -ENOMEM;

	/* Create the files associated with this kobject */
	retval = sysfs_create_group(example_kobj, &attr_group);
	if (retval)
		kobject_put(example_kobj);

	return retval;
}

static void __exit example_exit(void)
{
	kobject_put(example_kobj);
}

module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");

對應的Makefile

EXTRA_CFLAGS += $(DEBFLAGS)
#EXTRA_CFLAGS += -I$(INCDIR)

########## change your module name here
MODULE   = myKobj

########## change your obj file(s) here
$(MODULE)-objs:= kobject-example.o
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#ARCH 		  ?= arm

ifneq ($(KERNELRELEASE), )
	obj-m := $(MODULE).o

else
	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
	PWD := $(shell pwd)

all:
	$(MAKE_BEGIN)
	@echo
	@if \
	$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \
	then $(MAKE_DONE);\
	else \
	$(MAKE_ERR);\
	exit 1; \
	fi

endif

show:
	@echo "ARCH     :    ${ARCH}"
	@echo "CC       :    ${CROSS_COMPILE}gcc"
	@echo "KDIR     :    ${KERNELDIR}"
	@echo "$(MODULE):    $(ALLOBJS)"
clean:
	$(CLEAN_BEGIN)
	rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
	$(CLEAN_END)

.PHONY:all clean show
#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### nothing
#OFFSET=\e[21G    # 21 col
COLOR1=\e[32m  # all --> bule
COLOR2=\e[33m  # clean --> brown
COLOR3=\e[31m  # error --> red
RESET=\e[0m

CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"

MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
### I do not forget "@", but it DOES NOT need "@"
MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"

測試

編譯以后,加載模塊:

$ sudo insmod  myKobj.ko

發現創建了/sys/kernel/kobject_example/目錄,而且目錄中有3個屬性文件。

$ ls /sys/kernel/kobject_example/
bar  baz  foo

讀寫這些屬性

$ cat /sys/kernel/kobject_example/bar
0

$ cat /sys/kernel/kobject_example/baz
0

$ cat /sys/kernel/kobject_example/foo
0
$ sudo su
# echo "2021" > /sys/kernel/kobject_example/foo
# cat  /sys/kernel/kobject_example/foo
2021

內核kset例子

kset-example.c

代碼來自:samples/kobject/kset-example.c

可能會遇到error: negative width in bit-field ‘<anonymous>’這個錯誤,需要將__ATTR中的666權限改為664

/*
 * Sample kset and ktype implementation
 *
 * Copyright (C) 2004-2007 Greg Kroah-Hartman <greg@kroah.com>
 * Copyright (C) 2007 Novell Inc.
 *
 * Released under the GPL version 2 only.
 *
 */
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h>

/*
 * This module shows how to create a kset in sysfs called
 * /sys/kernel/kset-example
 * Then tree kobjects are created and assigned to this kset, "foo", "baz",
 * and "bar".  In those kobjects, attributes of the same name are also
 * created and if an integer is written to these files, it can be later
 * read out of it.
 */


/*
 * This is our "object" that we will create a few of and register them with
 * sysfs.
 */
struct foo_obj {
	struct kobject kobj;
	int foo;
	int baz;
	int bar;
};
#define to_foo_obj(x) container_of(x, struct foo_obj, kobj)

/* a custom attribute that works just for a struct foo_obj. */
struct foo_attribute {
	struct attribute attr;
	ssize_t (*show)(struct foo_obj *foo, struct foo_attribute *attr, char *buf);
	ssize_t (*store)(struct foo_obj *foo, struct foo_attribute *attr, const char *buf, size_t count);
};
#define to_foo_attr(x) container_of(x, struct foo_attribute, attr)

/*
 * The default show function that must be passed to sysfs.  This will be
 * called by sysfs for whenever a show function is called by the user on a
 * sysfs file associated with the kobjects we have registered.  We need to
 * transpose back from a "default" kobject to our custom struct foo_obj and
 * then call the show function for that specific object.
 */
static ssize_t foo_attr_show(struct kobject *kobj,
			     struct attribute *attr,
			     char *buf)
{
	struct foo_attribute *attribute;
	struct foo_obj *foo;

	attribute = to_foo_attr(attr);
	foo = to_foo_obj(kobj);

	if (!attribute->show)
		return -EIO;

	return attribute->show(foo, attribute, buf);
}

/*
 * Just like the default show function above, but this one is for when the
 * sysfs "store" is requested (when a value is written to a file.)
 */
static ssize_t foo_attr_store(struct kobject *kobj,
			      struct attribute *attr,
			      const char *buf, size_t len)
{
	struct foo_attribute *attribute;
	struct foo_obj *foo;

	attribute = to_foo_attr(attr);
	foo = to_foo_obj(kobj);

	if (!attribute->store)
		return -EIO;

	return attribute->store(foo, attribute, buf, len);
}

/* Our custom sysfs_ops that we will associate with our ktype later on */
static const struct sysfs_ops foo_sysfs_ops = {
	.show = foo_attr_show,
	.store = foo_attr_store,
};

/*
 * The release function for our object.  This is REQUIRED by the kernel to
 * have.  We free the memory held in our object here.
 *
 * NEVER try to get away with just a "blank" release function to try to be
 * smarter than the kernel.  Turns out, no one ever is...
 */
static void foo_release(struct kobject *kobj)
{
	struct foo_obj *foo;

	foo = to_foo_obj(kobj);
	kfree(foo);
}

/*
 * The "foo" file where the .foo variable is read from and written to.
 */
static ssize_t foo_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
			char *buf)
{
	return sprintf(buf, "%d\n", foo_obj->foo);
}

static ssize_t foo_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
			 const char *buf, size_t count)
{
	sscanf(buf, "%du", &foo_obj->foo);
	return count;
}

static struct foo_attribute foo_attribute =
	__ATTR(foo, 0664, foo_show, foo_store);

/*
 * More complex function where we determine which variable is being accessed by
 * looking at the attribute for the "baz" and "bar" files.
 */
static ssize_t b_show(struct foo_obj *foo_obj, struct foo_attribute *attr,
		      char *buf)
{
	int var;

	if (strcmp(attr->attr.name, "baz") == 0)
		var = foo_obj->baz;
	else
		var = foo_obj->bar;
	return sprintf(buf, "%d\n", var);
}

static ssize_t b_store(struct foo_obj *foo_obj, struct foo_attribute *attr,
		       const char *buf, size_t count)
{
	int var;

	sscanf(buf, "%du", &var);
	if (strcmp(attr->attr.name, "baz") == 0)
		foo_obj->baz = var;
	else
		foo_obj->bar = var;
	return count;
}

static struct foo_attribute baz_attribute =
	__ATTR(baz, 0664, b_show, b_store);
static struct foo_attribute bar_attribute =
	__ATTR(bar, 0664, b_show, b_store);

/*
 * Create a group of attributes so that we can create and destroy them all
 * at once.
 */
static struct attribute *foo_default_attrs[] = {
	&foo_attribute.attr,
	&baz_attribute.attr,
	&bar_attribute.attr,
	NULL,	/* need to NULL terminate the list of attributes */
};

/*
 * Our own ktype for our kobjects.  Here we specify our sysfs ops, the
 * release function, and the set of default attributes we want created
 * whenever a kobject of this type is registered with the kernel.
 */
static struct kobj_type foo_ktype = {
	.sysfs_ops = &foo_sysfs_ops,
	.release = foo_release,
	.default_attrs = foo_default_attrs,
};

static struct kset *example_kset;
static struct foo_obj *foo_obj;
static struct foo_obj *bar_obj;
static struct foo_obj *baz_obj;

static struct foo_obj *create_foo_obj(const char *name)
{
	struct foo_obj *foo;
	int retval;

	/* allocate the memory for the whole object */
	foo = kzalloc(sizeof(*foo), GFP_KERNEL);
	if (!foo)
		return NULL;

	/*
	 * As we have a kset for this kobject, we need to set it before calling
	 * the kobject core.
	 */
	foo->kobj.kset = example_kset;

	/*
	 * Initialize and add the kobject to the kernel.  All the default files
	 * will be created here.  As we have already specified a kset for this
	 * kobject, we don't have to set a parent for the kobject, the kobject
	 * will be placed beneath that kset automatically.
	 */
	retval = kobject_init_and_add(&foo->kobj, &foo_ktype, NULL, "%s", name);
	if (retval) {
		kobject_put(&foo->kobj);
		return NULL;
	}

	/*
	 * We are always responsible for sending the uevent that the kobject
	 * was added to the system.
	 */
	kobject_uevent(&foo->kobj, KOBJ_ADD);

	return foo;
}

static void destroy_foo_obj(struct foo_obj *foo)
{
	kobject_put(&foo->kobj);
}

static int __init example_init(void)
{
	/*
	 * Create a kset with the name of "kset_example",
	 * located under /sys/kernel/
	 */
	example_kset = kset_create_and_add("kset_example", NULL, kernel_kobj);
	if (!example_kset)
		return -ENOMEM;

	/*
	 * Create three objects and register them with our kset
	 */
	foo_obj = create_foo_obj("foo");
	if (!foo_obj)
		goto foo_error;

	bar_obj = create_foo_obj("bar");
	if (!bar_obj)
		goto bar_error;

	baz_obj = create_foo_obj("baz");
	if (!baz_obj)
		goto baz_error;

	return 0;

baz_error:
	destroy_foo_obj(bar_obj);
bar_error:
	destroy_foo_obj(foo_obj);
foo_error:
	kset_unregister(example_kset);
	return -EINVAL;
}

static void __exit example_exit(void)
{
	destroy_foo_obj(baz_obj);
	destroy_foo_obj(bar_obj);
	destroy_foo_obj(foo_obj);
	kset_unregister(example_kset);
}

module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Greg Kroah-Hartman <greg@kroah.com>");

對應的Makefile

EXTRA_CFLAGS += $(DEBFLAGS)
#EXTRA_CFLAGS += -I$(INCDIR)

########## change your module name here
MODULE   = myKset

########## change your obj file(s) here
#$(MODULE)-objs:= kobject-example.o
$(MODULE)-objs:= kset-example.o
#CROSS_COMPILE ?= arm-linux-gnueabihf-
#ARCH 		  ?= arm

ifneq ($(KERNELRELEASE), )
	obj-m := $(MODULE).o

else
	KERNELDIR ?= /lib/modules/$(shell uname -r)/build
	PWD := $(shell pwd)

all:
	$(MAKE_BEGIN)
	@echo
	@if \
	$(MAKE) INCDIR=$(PWD)/configs -C $(KERNELDIR) M=$(PWD) modules; \
	then $(MAKE_DONE);\
	else \
	$(MAKE_ERR);\
	exit 1; \
	fi

endif

show:
	@echo "ARCH     :    ${ARCH}"
	@echo "CC       :    ${CROSS_COMPILE}gcc"
	@echo "KDIR     :    ${KERNELDIR}"
	@echo "$(MODULE):    $(ALLOBJS)"
clean:
	$(CLEAN_BEGIN)
	rm -rf *.cmd *.o *.ko *.mod.c *.symvers *.order *.markers .tmp_versions .*.cmd *~ .*.d
	$(CLEAN_END)

.PHONY:all clean show
#xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
### nothing
#OFFSET=\e[21G    # 21 col
COLOR1=\e[32m  # all --> bule
COLOR2=\e[33m  # clean --> brown
COLOR3=\e[31m  # error --> red
RESET=\e[0m

CLEAN_BEGIN=@echo -e "$(OFFSET)$(COLOR2)Cleaning up...$(RESET)"
CLEAN_END=@echo -e "$(OFFSET)$(COLOR2)Cleaned.$(RESET)"

MAKE_BEGIN=@echo -ne "$(OFFSET)$(COLOR1)Compiling...$(RESET)"
### I do not forget "@", but it DOES NOT need "@"
MAKE_DONE=echo -e "$(OFFSET)$(COLOR1)Compilied.$(RESET)"
MAKE_ERR=echo -e "$(OFFSET)$(COLOR3)[Oops! Error occurred]$(RESET)"

測試

編譯以后,加載模塊:

$ sudo insmod  myKset.ko

發現創建了/sys/kernel/kset_example/目錄,而且目錄中有3個子目錄,每一個子目錄中都有3個屬性文件。

$ tree /sys/kernel/kset_example/
/sys/kernel/kset_example/
├── bar
│   ├── bar
│   ├── baz
│   └── foo
├── baz
│   ├── bar
│   ├── baz
│   └── foo
└── foo
    ├── bar
    ├── baz
    └── foo

讀寫就不再啰嗦了,是一樣的。

附錄:一部分關於kobject的API

由於我們現在關心的是設備驅動模型,因此暫不了解。等到具體的代碼中,我們再進行解析。

動態創建

struct kobject *kobject_create(void);

動態創建一個kobject結構,並將其設置為一個帶默認釋放方法(dynamic_kobj_ktype)的動態的kobject。

如果無法創建kobject,將會返回NULL。從這里返回的kobject 結構釋放必須調用kobject_put() 方法而不是kfree(),因為kobject_init()已經被調用過了。

初始化

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

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

該方法會正確的初始化一個kobject來保證它可以被傳遞給kobject_add()

該功能被調用后,kobject必須通過調用kobject_put()來清理,而不是直接調用kfree,來保證所有的內存都可以被正確的清理。

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

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

添加到sysfs

在調用kobject_init將kobject注冊到sysfs之后,必須調用kobject_add()添加:

/**
 * 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, ...);

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

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

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

不過不需要調用kobject_init()kobject_add(),因為系統提供了函數kobject_create_and_add()

#include <linux/kobject.h> 
#if 0 // 下面兩種操作等價
struct kobject *kobject_create(void);
int kobject_add(struct kobject *kobj);
#else
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
// 再也沒有 kobject_register 這個函數了
#endif

創建簡單的kobject

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

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

struct kobject *kobject_create_and_add(char *name, struct kobject *parent);

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

重命名

如果你必須為kobject改名,則調用kobject_rename()函數實現:

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

// 如果 kobject 還沒被 add 進 sysfs,
// 那么還可以使用下列的函數,否則必須使用kobject_rename
int kobject_set_name(struct kobject *kobj, const char *fmt, ...);
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
                           va_list vargs);

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

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

const char *kobject_name(const struct kobject * kobj);

附錄:sysfs API

見:Linux 內核:sysfs 有關的API


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM