歡迎光臨
每天分享高質量文章

統一裝置模型:kobj、kset分析

kobj/kset作為統一裝置模型的基礎,到底提供了哪些功能,在具體應用過程中,如devicebus甚至platform_device等是如何使用kobj/kset的,這是本文的主要闡述內容。

作為閱讀wowo相關文章後的筆記,本文紕漏之處,歡迎各位大俠拍磚。

1 kobj實現

1.1 kobject

struct kobject {

         const char                  *name;

         struct list_head        entry;

         struct kobject           *parent;

         struct kset                  *kset;

         struct kobj_type       *ktype;

         struct kernfs_node  *sd;

         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;

         unsigned int uevent_suppress:1;

};

name:對應sysfs的目錄名。

entry:用於將kobj掛在kset->list中。

parent:指向kobj的父結構,形成層次結構,在sysfs中表現為父子目錄的關係。

kset:表徵該kobj所屬的ksetkset可以作為parent候補:當註冊時,傳入的parent為空時,可以讓kset來擔當。

ktype:該kobj對應的kobj_type。每個kobj或其嵌入的結構物件應該都對應一個kobj_type

sd:對應sysfs物件。在3.14以後的核心中,sysfs基於kernfs來實現。

kref:取用計數物件,支撐kobj的取用計數功能。

state_initialized:1——————-記錄初始化與否。呼叫kobject_init()後,會置位。

state_in_sysfs:1———————記錄kobj是否註冊到sysfs,在kobject_add_internal()中置位。

state_add_uevent_sent:1——–當傳送KOBJ_ADD訊息時,置位。提示已經向用戶空間傳送ADD訊息。

state_remove_uevent_sent:1—當傳送KOBJ_REMOVE訊息時,置位。提示已經向用戶空間傳送REMOVE訊息。

uevent_suppress:1—————–如果該欄位為1,則表示忽略所有上報的uevent事件。

1.2 kobj_type

struct kobj_type {

         void (*release)(struct kobject *kobj);

         const struct sysfs_ops *sysfs_ops;

         struct attribute **default_attrs;

         const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);

         const void *(*namespace)(struct kobject *kobj);

};

release:處理物件終結的回呼函式。該介面應該由具體物件負責填充。

sysfs_ops:該型別kobjsysfs操作介面。

default_attrs:該型別kobj自帶的預設屬性(檔案),這些屬性檔案在註冊kobj時,直接pop為該目錄下的檔案。

child_ns_type/namespace:檔案系統名稱空間相關,不分析。

2 kset實現

struct kset {

         struct list_head list;

         spinlock_t list_lock;

         struct kobject kobj;

         const struct kset_uevent_ops *uevent_ops;

};

head_list:與kobj->entry對應,用來組織本kset管理的kobj

kobjkset內部包含一個kobj物件。

uevent_opskset用於傳送訊息的操作函式集。需要指出的是,kset能夠傳送它所包含的各種子kobj、孫kobj的訊息,即kobj或其父輩、爺爺輩,都可以傳送訊息;優先父輩,然後是爺爺輩,以此類推。

3 kobj/kset功能特性

3.1物件生命週期管理

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

struct kobject *kobject_get(struct kobject *kobj)

void kobject_put(struct kobject *kobj)

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

而對於kobject_put(),它的實現複雜。其不僅要使用kref_put()介面來對取用計數進行減1操作,還要對生命終結的物件執行release()操作。然而kobject是高度抽象的物體,導致kobject不會單獨使用,而是嵌在具體物件中。反過來也可以這樣理解:凡是需要做物件生命週期管理的物件,都可以透過內嵌kobject來實現需求

回到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_release函式,由於bus_release()函式由具體的bus子系統提供,它必定知道如何釋放包括kobj在內的bus_type物件。

3.2sysfs檔案系統的層次組織

kobject的另一個功能就是完成sysfs檔案系統的組織功能。該功能依靠kobjparentksetsd等成員來完成。sysfs檔案系統能夠以友好的介面,將kobj所刻畫的物件層次、所屬關係、屬性值,展現在使用者空間。

3.3使用者空間事件投遞

這方面的內容可參考《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_ksetdevice_uevent_ops。該opsuevent()方法定義如下:

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)

在該opsuevent()方法中,會分別呼叫busclassdevice typeuevent方法來生成事件訊息。

3.4kobjkset關係總結

kobjkset並不是完全的父子關係kset算是kobj的“接盤俠”,當kobj沒有所屬的parent時,才讓kset來接盤當parent;如果連kset也沒有,那該kobj屬於頂層物件,其sysfs目錄將位於/sys/下。正因為kobjkset並不是完全的父子關係,因此在註冊kobj時,將同時對parent及其所屬的kset增加取用計數。若parentkset為同一物件,則會對kset增加兩次取用計數。

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來向用戶空間傳送訊息;若無關聯ksetkobj向上組成的樹中,任何成員都無所屬的kset),則kobj無法傳送使用者訊息。

4 ksetkobj的註冊總結

4.1 API

kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, …) 

說明:(1kobj->parent = parent;(2kobject_add_internal(kobj)。其中,kobject_add_internal(kobj)會根據kobj.parentkobj.kset來綜合決定父目錄。詳見下麵總結。

kset_register(struct kset *k) 

說明:直接呼叫kobject_add_internal()進行註冊,然後用kobject_uevent(&k-;>kobj, KOBJ_ADD)傳送KOBJ_ADD事件。

kobject_uevent(struct kobject *kobj, enum kobject_action action) 

說明:向用戶空間傳送事件訊息。

4.2總結

關於kobject_add()的總結:

在進行kobj註冊時,呼叫kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, …)

輸入資訊kobj物件、kobj-parentkobj-name

當kobj,無parent、無kset時,將在sysfs的根目錄(即/sys/)下建立目錄;

當kobj,無parent、有kset時,kobject_add()會設定kobj->parent為kset->kobj;因此會在該kset下建立目錄;該kobj會加入kset.list;同時,會對kobj->kset->kobj增加兩次取用:1.增加對kobj->parent的取用;2.增加對kobj->kset的取用。

當kobj,有parent、無kset時,kobject_add()會在該parent下建立目錄;同時,會對parent增加取用。

當kobj,有parent、有kset時,優先在parent下建立目錄;該kobj會加入kset.list;同時,會分別對parent、kset增加取用計數。

1kobj/kset的取用計數示例

下圖展示了一個頂層kobj/kset,透過不斷的新增子節點,導致的取用計數變化情況。

首先在(I)中建立了頂層物件A,其kref初始化為1;在第(II)步中,建立了A的子物件B,此時A物件的kref取用計數加1變為2;在第(III)步中,繼續在A下建立子物件C,此時A物件的kref取用計數加1變為3;在第(IV)步中,建立B物件的子物件D,此時,只會對D的父物件進行取用計數加1;而對更上層的父物件,則不進行取用加1操作。

2platform_bus_type的註冊示例

核心啟動時,會在初始化階段進行platform_bus_type匯流排的註冊:bus_register(&platform;_bus_type)。在該函式裡,會設定platform_bus_type->p->subsys.kobj.kset = bus_ksetplatform_bus_type->p->subsys.kobj.ktype = &bus;_ktype,因此按照前面的總結,由於platform_bus_typekset、無parent,導致該bus註冊進系統後,其kset取用計數將被增加兩次,同時,在bus_kset對應的目錄(/sys/bus/)下建立platform_bus_type對應的子目錄(/sys/bus/platform/)。同理,當該匯流排的取用計數為0時,將導致該物件生命的終結,會觸發release()操作,顯然該操作將導致bus_kset的取用計數減少兩次。

對外介面的總結

  示意圖如下圖所示。

  小結

l  kobj對應一個目錄;

l  kobj實現物件的生命週期管理(計數為0即清除);

l  kset包含一個kobj,相當於也是一個目錄;

l  kset是一個容器,可以包容不同型別的kobj(甚至父子關係的兩個kobj都可以屬於一個kset);

l  註冊kobj(如kobj_add()函式)時,優先以kobj->parent作為自身的parent目錄;

其次,以kset作為parent目錄;若都沒有,則是sysfs的頂層目錄;

同時,若設定了kset,還會將kobj加入kset.list。舉例:

1、無parent、無kset,則將在sysfs的根目錄(即/sys/)下建立目錄;

2、無parent、有kset,則將在kset下建立目錄;並將kobj加入kset.list

3、有parent、無kset,則將在parent下建立目錄;

4、有parent、有kset,則將在parent下建立目錄,並將kobj加入kset.list

l  註冊kset時,如果它的kobj->parent為空,則設定它所屬的ksetkset.kobj->kset.kobj)為parent,即優先使用kobj所屬的parent;然後再使用kset作為parent。(如platform_bus_type的註冊,如某些input裝置的註冊)

l  註冊device時,dev->kobj.kset = devices_kset;然而kobj->parent的選取有優先次序:

已同步到看一看
贊(0)

分享創造快樂