[翻譯]你不會想知道的kobject,kset,和ktypes

-------------------------------------------------------------------------------------------------------------------------------linux

阿輝翻譯自Linux內核文檔:\linux-3.4.75\Documentation\kobject.txt程序員

原創翻譯,歡迎轉載,轉載請註明出處安全

-------------------------------------------------------------------------------------------------------------------------------異步

  理解kobject抽象和以此來創建的設備驅動模型的部分困難在於沒有一個明顯的切入點。函數

  而正確使用kobjects須要瞭解一些不一樣的類型,而這些類型是相互引用。爲了更容易理解,咱們將從一些模糊的概念開始,並逐步增長細節。爲此,先來介紹幾個咱們會使用到術語的快速定義。學習

一、kobject是struct kobject類型的對象,它包含一個名字和引用計數,而且擁有一個父指針(這可讓對象組織成層次結構),而且kobject一般是sysfs虛擬文件系統中的表述。ui

二、 通常不會對kobject自己感興趣,相反,kobject結構一般是被嵌入到其餘結構中,而這些結構才包含了真正感興趣的數據。this

三、沒有一個結構會嵌入多於一個kobject結構,若是這麼作了,關於這個對象的引用計數確定會一團糟,你的code也會充滿bug,因此千萬不要這麼作。spa

四、ktype是內嵌kobject結構的對象的類型,每一個內嵌kboject的結構都須要一個特定的ktype結構,ktype決定了kobject被建立和銷燬時所採起的動做。翻譯

五、kset包含了一組kobject結構,這些kobject能夠有相同或者不一樣的ktype。kset是一個基礎的容器類型,它包含了kobject的集合。kset自身也內嵌了一個kobject結構,不過你能夠不用關注這個實現的細節,由於kernel中的kset核心程序已經作好這些事情了。

六、當你看到某個sysfs目錄下有不少其餘目錄,這些目錄都對應着一樣kset裏包含的具體kobject

 

  咱們會以自下到上的角度開始研究如何建立和操做這些類型;所以,咱們將會從kobject開始

 

嵌入的kobject

   極少會須要kernel代碼區建立一個單獨的kboject結構,除了後面所述的一個顯著例外。相反,kobject被用來控制訪問一個更大的,具備特定做用域的對象,爲了達到這個做用,kobject將會被嵌入到其餘結構體中。若是你用面向對象的角度來看,kobject結構能夠被看作頂層的抽象類,其餘類都是從這個類派生出來的。 

  kobject實現了一系列功能,這些功能一般不會用於kobject自身,但對其餘對象來講倒是很是好的功能。C語言不支持直接表達繼承的語法,所以就必須使用相似於結構體內嵌的其餘技術(做爲旁白,對於那些對內核鏈表實現熟悉的人來講,這有點相似於「list_head」結構不多會做用於自身,但老是能夠發現它嵌入在在更大的結構體中)。舉個例子,實現於drivers/uio/uio.c的UIO代碼包含了一個以下定義的結構體,定義了uio設備對應的內存空間

    struct uio_map {

         struct kobject kobj;

         struct uio_mem *mem;

};

若是你有一個struct uio_map結構體,經過指向kobj成員就能夠找到嵌入的kobject結構。然而,包含kobject的代碼一般會有相反的困擾,如何根據kobject結構指針找到包含它的指針呢?不要特地取巧(好比假設kobject在該結構的開頭),而應該使用<linux/kernel.h>中的contain_of()宏:

    container_of(pointer, type, member)

對應參數的含義以下:

pointer:指向內嵌kobject的指針

type:包含kobject結構的結構體的指針

member:」pointer」指針的名字(好比前面uio_map結構中的kobj)

 

  container_of()宏的返回值是該容器類型的指針。舉個例子,指針kp是kobject結構指針,內嵌於uio_map結構體,能夠經過以下的代碼獲得獲得uio_map結構體指針:

  struct uio_map *u_map = container_of(kp, struct uio_map, kobj);

 

  爲了方便,程序員一般定義一個簡單的宏,經過kobject指針逆向獲得容器類指針。在drivers/uio/uio.c更早的代碼中就是這樣實現的,就像你看到的以下代碼:

    struct uio_map {

        struct kobject kobj;

        struct uio_mem *mem;

    };

#define to_map(map) container_of(map, struct uio_map, kobj)

這裏,宏的參數」map」是指向kobject結構的指針,隨後宏就變成以下形式的調用:

struct uio_map *map = to_map(kobj);

kobject初始化

   建立kobject固然必須初始化kobject對象,kobject的一些內部成員須要(強制)經過kobject_init()初始化:

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

   ktyp對於kobject的正確建立是必須的,由於每一個kobject必須關聯一個kobj_type。

  在調用kobject_init將kobject註冊到sysfs以後,必須調用kobject_add()添加:

  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()函數實現:

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

  kobject_rename內部並不執行任何鎖定操做,也沒用什麼時候名稱是有效的概念。所以調用者必須本身實現完整性檢查和保證序列性。有一個函數叫kobject_set_name(),可是它遺留了一些缺陷,目前正在被移除。若是你的代碼須要調用這個函數,它是不正確的,須要被修正。能夠經過kobject_name()函數來正確獲取kobject的名稱:

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

  有一個輔助函數用來在同一時間初始化和添加kobject,它被稱做使人驚訝的是kobject_init_and_add():

  int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,

                             struct kobject *parent, const char *fmt, ...);

 

  它的參數和前面介紹的單獨使用kobject_init()、kobject_add()這兩個函數時同樣

 

Uevents

  在註冊完kobject結構以後,你須要向世界宣佈,kobject已經被建立。這能夠經過kobject_uevent()函數來實現:

  int kobject_uevent(struct kobject *kobj, enum kobject_action action);

 

  若這個kobject是第一次被加到kernel裏面,action參數應該設置爲KOBJ_ADD。

  kobject_uevent只能在kobject的全部屬性或者子節點正確初始化完成以後調用,由於用戶空間會在這個函數調用時立刻區查找它們。從kernel中移除kobject結構時(詳細操做會在後面介紹),kobject 核心會自動建立KOBJ_REMOVE這個uevent,所以調用者不須要擔憂須要手動去調用廣播移除事件。

 

引用計數

  kobject結構的一個關鍵做用是做爲它所嵌入對象的引用計數器,只要這個對象的引用還在,該對象(和支持它的代碼)就必須存在。用來操做kobject引用計數的底層函數是:

    struct kobject *kobject_get(struct kobject *kobj);

    void kobject_put(struct kobject *kobj);

  成功調用kobject_get()函數會增長kobject的引用計數,而且返回kobject的指針。當一個對象的引用被釋放時時,將會調用kobject_put函數來減少引用計數,而且有可能會釋放這個對象。

   記住,kobject_init將會將引用計數設置爲1,所以當你建立一個kobject時,你應當確保在釋放引用的時候調用kobject_put來講釋放這個引用。kobject是一個動態的概念,它們不能靜態的聲明或者保存在堆棧上;相反,必須動態的分配。將來的內核版本將會包含對靜態建立的kobject的運行時檢查,而且會警告開發者這個用法不合適。若是你使用kobject只是但願爲你的結構提供一個引用計數,那麼請使用kref來代替,kobject有點大材小用了。對於如何使用kref結構的信息,請參考Linux內核源碼樹中的Documentation/kref.txt這個文件。

 

建立簡單的kobject

  有些時候,開發者但願的只是在sysfs中建立一個目錄,而且不會被kset、show、store函數等細節的複雜概念所迷惑。這裏有一個能夠單首創建kobject的例外,爲了建立這樣一個入口,能夠經過以下的函數來實現:

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

 

  這個函數會建立一個kobject,並把它的目錄放到指定父節點在sysfs中目錄裏面。能夠經過以下的函數簡單建立kobject相關聯的屬性:

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

或者

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

 

  這兩種方法都包含了屬性類型,和經過kobject_create_and_add()建立的kobject類型;屬性類型能夠直接使用kobj_attribute類型,因此不須要特別建立自定義屬性。讀者能夠經過samples/kobject/kobject-example.c中的模塊例程,學習kobject和屬性的實現。

 

kytpes和釋放方法

   前面的討論中仍然缺失的一個重要部分是當一個kobject的引用計數爲0時會發生什麼。建立kobject的代碼一般不知道何時會發生這件事,若是它知道,那在第一次使用kobject時使用引用計數就沒有意義了。甚至,連生命週期可預測的對象也在sysfs被引入時變得更加複雜,由於內核的其餘部分可能會引用任何在系統中已註冊的kobject。

   結論就是:kobject所保護的結構體在它的引用計數沒到0以前不能夠被釋放。引用計數並不禁建立kobject的代碼直接控制,所以這些代碼必須在kobject的最後一個引用被釋放時被異步的通知到。千萬不能經過kfree直接去釋放你經過kobject_add註冊的kobject結構體,kobject_put纔是惟一安全的方法。在kobject_init調用以後使用kobject_put來避免錯誤發生老是一個好的方法。

  通知的機制經過kobject的釋放函數法實現。一般這種函數有以下的實現形式:

    void my_object_release(struct kobject *kobj)

    {

                 struct my_object *mine = container_of(kobj, struct my_object, kobj);

 

             /* Perform any additional cleanup on this object, then... */

             kfree(mine);

    }

  一個重要的概念:每一個kobject必須有一個釋放函數,而且這個kobject必須保持直到這個釋放函數被調用到。若是這個條件不能被知足,則這個代碼是有缺陷的。注意,假如你忘了提供釋放函數,內核會提出警告的;不要嘗試提供一個空的釋放函數來消除這個警告,你會收到kobject維護者的無情嘲笑。

 

  注意,kobject的名稱在釋放函數裏仍是有效的,可是千萬不要在釋放函數裏面修改kobject的名稱,不然會致使kobject核心的內存泄漏,這會讓人很是不高興的。

有趣的是,釋放函數並無保存在kobject結構自身裏面。相反, 它被關聯到ktype裏,讓咱們介紹下kobj_type這個結構體

    struct kobj_type {

             void (*release)(struct kobject *);

             const struct sysfs_ops *sysfs_ops;

             struct attribute        **default_attrs;

    };

  這個結構體用於描述特定類型的kobject(或者,更準確的說是容器對象)。每一個對象都必須關聯一個kobj_type結構體,當你調用kobject_init和kobject_init_and_add時必須傳入一個kobj_type的指針。

kobj_type結構裏的release域固然是針對這個kobject類型的釋放函數指針。其餘兩個成員(sysfs_ops和default_attrs)控制對象在sysfs中時如何表述的,這部份內容這篇文章不會介紹到。

當kobject使用ktype這個屬性類型註冊到系統時,default_attrs指針包含的默認屬性列表會自動爲該kobject建立出來。

 

kset

  kset僅僅是一系列相互關聯的kobject的集合。並無要求這些kobject有一樣的ktype,可是若是沒有一樣的ktype,仍是要很是當心才行

 

  kset的主要做用以下:

  做爲一個包含一組對象的容器,kset能夠被內核用來追蹤」全部塊設備」或者」全部PCI設備驅動」。

  kset也是sysfs中的子目錄,全部跟kset相關聯的kobject都會出如今這個目錄裏。每一個kset都內嵌了一個kobject結構,它能夠是其餘kobject的父節點,sysfs中的頂級目錄結構都是這麼組織的。

  kset能夠用於支持kobject的熱插拔,而且對uevent事件如何上報到用戶空間形成影響。

 

  若用面向對象思想來看,kset是頂級的容器類;kset包含一個自身的kobject,可是這個kobject由kset代碼來管理,不該該被其餘用戶操做到。

 

  kset經過標準的內核鏈表來管理它的全部子節點,kobject經過kset成員變量指向它的kset容器。大多數狀況下,kobject都是它所屬kset(或者嚴格點,kset內嵌的kobject)的子節點。

 

  由於kset內嵌了一個kobject結構,它應該動態的被建立,而永遠不要靜態或者在棧上聲明,能夠經過以下的函數來建立一個新的kset:

    struct kset *kset_create_and_add(const char *name,

                                        struct kset_uevent_ops *u,

                                        struct kobject *parent);

 

  當你結束kset的使用時,經過下面的函數來銷燬它:

  void kset_unregister(struct kset *kset);

  kset的一個例子能夠在內核源碼樹的samples/kobject/kset-example.c文件中找到。

 

         若是一個kset但願控制它所關聯kobject的uevent操做,它可使用kset_uevent_ops結構來處理:

struct kset_uevent_ops {

        int (*filter)(struct kset *kset, struct kobject *kobj);

        const char *(*name)(struct kset *kset, struct kobject *kobj);

        int (*uevent)(struct kset *kset, struct kobject *kobj,

                      struct kobj_uevent_env *env);

};

 

  filter函數用於讓kset決定一個特定kobject的uevent是否被傳遞到用戶空間,若該函數返回0,則該uevent將會被阻止。

  name函數用於替代發送uevent到用戶空間的kset的默認名字。默認狀況下,該名稱會和kset一致,但這個函數(name)存在的話,能夠覆蓋這個名字。

  當uevent要被髮送到用戶空間時,uevent函數會被調用到,該函數容許將更多環境變量添加到uevent中。

 

  你可能會問,一個kobject是如何添加到kset中去的,鑑於沒有看到一個確切實現該功能的函數,答案是經過kobject_add函數來實現。當調用kobject_add來添加一個kobject時,須要傳遞一個指向該kobject所屬的kset的kset參數,kobject_add會處理剩下的事情。

  若是kobject沒有設置父節點,它將會被添加到kset的目錄底下。並非kset的全部成員都會在kset目錄底下。若是在kobject添加以前就確認了父節點,在註冊到kset時會添加到父節點的目錄底下。

 

刪除kobject

 

  在kobject核心成功註冊以後,必須在代碼完成時清理掉這個kobject,能夠經過kobject_put來完成。經過這個函數,kobject核心會自動清除爲該kobject分配的全部內存。若已經爲該對象發送過KOBJ_ADD這個uevent,對應的KOBJ_REMOVE uevent也在清理時被髮送。另外,sysfs的其餘清理工做也會爲這個調用者啓動。

         若是你須要完成kobject的二級刪除(表示銷燬對象時不容許睡眠),能夠嘗試經過kobject_del從sysfs中註銷kobject。這個函數會讓kobject在sysfs中不可見,但此時它並無被清理掉,引用計數仍是不變。能夠在後續的代碼中調用kobject_put來完成清理kobject相關的內存。

         若建立了循環引用,能夠經過kobject_del函數來去掉對父節點的引用。這種方法在某些狀況下是合法的,好比父節點引用到了子節點。循環引用必須經過一個明確的kobject_del調用來去除,這樣釋放函數將會被調用到,而且以前的循環引用將會釋放掉。

kobject/kset例程

能夠經過samples/kobject/{kobject-example.c,kset-example.c}中的例程學習如何正確的使用kset和kobject。這些例程能夠經過選擇kernel的配置選項CONFIG_SAMPLE_KOBJECT來把它編譯成模塊實現。

相關文章
相關標籤/搜索