kobj/kset作為統一裝置模型的基礎,到底提供了哪些功能,在具體應用過程中,如device、bus甚至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所屬的kset。kset可以作為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:該型別kobj的sysfs操作介面。
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。
kobj:kset內部包含一個kobj物件。
uevent_ops:kset用於傳送訊息的操作函式集。需要指出的是,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檔案系統的組織功能。該功能依靠kobj的parent、kset、sd等成員來完成。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_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方法來生成事件訊息。
3.4kobj、kset關係總結
kobj和kset並不是完全的父子關係。kset算是kobj的“接盤俠”,當kobj沒有所屬的parent時,才讓kset來接盤當parent;如果連kset也沒有,那該kobj屬於頂層物件,其sysfs目錄將位於/sys/下。正因為kobj和kset並不是完全的父子關係,因此在註冊kobj時,將同時對parent及其所屬的kset增加取用計數。若parent和kset為同一物件,則會對kset增加兩次取用計數。
kset內部本身也包含一個kobj物件,在sysfs中也表現為目錄;所不同的是,kset要承擔kobj狀態變動訊息的傳送任務。因此,首先kset會將所屬的kobj組織在kset.list下,同時,透過uevent_ops在合適時候傳送訊息。
對於kobject_add()來說,它的輸入資訊是:kobj-parent、kobj-name,kobject_add()優先使用傳入的parent作為kobj->parent;其次,使用kset作為kobj->parent。
kobj狀態變動後,必須依靠所關聯的kset來向用戶空間傳送訊息;若無關聯kset(該kobj向上組成的樹中,任何成員都無所屬的kset),則kobj無法傳送使用者訊息。
4 kset和kobj的註冊總結
4.1 API
kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, …)
說明:(1)kobj->parent = parent;(2)kobject_add_internal(kobj)。其中,kobject_add_internal(kobj)會根據kobj.parent和kobj.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-parent、kobj-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增加取用計數。
(1)kobj/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操作。
(2)platform_bus_type的註冊示例
核心啟動時,會在初始化階段進行platform_bus_type匯流排的註冊:bus_register(&platform;_bus_type)。在該函式裡,會設定platform_bus_type->p->subsys.kobj.kset = bus_kset、platform_bus_type->p->subsys.kobj.ktype = &bus;_ktype,因此按照前面的總結,由於platform_bus_type有kset、無parent,導致該bus註冊進系統後,其kset取用計數將被增加兩次,同時,在bus_kset對應的目錄(/sys/bus/)下建立platform_bus_type對應的子目錄(/sys/bus/platform/)。同理,當該匯流排的取用計數為0時,將導致該物件生命的終結,會觸發release()操作,顯然該操作將導致bus_kset的取用計數減少兩次。
5 對外介面的總結
示意圖如下圖所示。
6 小結
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為空,則設定它所屬的kset(kset.kobj->kset.kobj)為parent,即優先使用kobj所屬的parent;然後再使用kset作為parent。(如platform_bus_type的註冊,如某些input裝置的註冊)
l 註冊device時,dev->kobj.kset = devices_kset;然而kobj->parent的選取有優先次序:
朋友會在“發現-看一看”看到你“在看”的內容