天天看點

Linux裝置模型剖析系列文章之七(kobj、kset)

十、kobj、kset分析

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

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.2 sysfs檔案系統的層次組織

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方法來生成事件消息。

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 總結

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無法發送使用者消息。

在進行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,通過不斷的添加子節點,導緻的引用計數變化情況。

Linux裝置模型剖析系列文章之七(kobj、kset)
  • 首先在(I)中建立了頂層對象A,其kref初始化為1;
  • 在第(II)步中,建立了A的子對象B,此時A對象的kref引用計數加1變為2;
  • 在第(III)步中,繼續在A下建立子對象C,此時A對象的kref引用計數加1變為3;
  • 在第(IV)步中,建立B對象的子對象D,此時,隻會對D的父對象B進行引用計數加1;而對更上層的父對象A,則不進行引用加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 對外接口的總結

​​[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-5nSPbGla-1667028309823)(http://www.wowotech.net/content/uploadfile/201801/dbc59bfffd0315275d9a4d7b5e89349120180110012115.gif “2”)]​​

示意圖如下圖所示:

​​[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-4kiagoI8-1667028309824)(http://www.wowotech.net/content/uploadfile/201801/c839cae3fea493c433a3619e8b3ff04420180110012116.gif “4”)]​​

6 小結

  • kobj對應一個目錄;
  • kobj實作對象的生命周期管理(計數為0即清除);
  • kset包含一個kobj,相當于也是一個目錄;
  • kset是一個容器,可以包容不同類型的kobj(甚至父子關系的兩個kobj都可以屬于一個kset);
  • 注冊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;
  • 注冊kset時,如果它的kobj->parent為空,則設定它所屬的kset(kset.kobj->kset.kobj)為parent,即優先使用kobj所屬的parent;然後再使用kset作為parent。(如platform_bus_type的注冊,如某些input裝置的注冊)
  • 注冊device時,dev->kobj.kset = devices_kset;然而kobj->parent的選取有優先次序:
  • 【參考資料】
  • ​​蝸窩科技​​

繼續閱讀