Everything you never wanted to know about kobjects, ksets, and ktypes

    要理解kobject抽象及其之上的設備驅動模型並不簡單,難點之一就在於,沒有一個明顯的起點。要處理好kobject,需要理解一些別的類型,而這些類型又是相互引用的。爲了讓事情簡單,我們採用多遍的過程,從模糊的概念出發,逐漸增加細節。爲此,這裏先對一些相關概念進行定義:

—kobject 是一個類型爲struct object 的對象。kobject有一個名稱和一個引用計數,還有一個指向父kobject的指針(允許kobject分層排布),一個特定的類別,通常還有一個在sysfs虛擬文件系統中的顯示。

    kobject本身並不是重點,但它經常嵌入在其它結構中,這些被嵌入的結構裏,往往有我們感興趣的內容。

    每個結構不能有超過一個kobject嵌入其中。如果超過了,對該結構的引用計數會混亂掉,代碼會有bug。所以不要這樣做。

—ktype是一類嵌入在kobject中的對象。每個使用kobject的結構都需要相應的ktype。ktype管理了在kobject創建和銷燬時會發生什麼。

—kset是一羣kobject。這些kobject可以使用同樣的ktype,也可以是屬於不同 的ktype。kset是容納多個kobject的基本容器類型。kset有自己的一個kobject,但你可以安全地忽略它,kset的代碼會自動管理這個內部的kobject。

    但你看到一個sysfs目錄,下面包含有子目錄,通常每個子目錄就對應一個處在同一kset下的kobject。

    我們下面看如何創建並管理所有這些類型。我們會使用自底向上的方法,所以我們回到kobject。

 

內嵌的kobject

    內核代碼很少創建一個單獨的kobject,但後面有一個例外情況。kobject主要用於控制更大的、應用指定的對象的訪問。所以,kobject經常內嵌到其它對象結構中。如果你習慣用面向對象的方式來思考,kobject可以看成一個頂層的抽象的類,其它的類從這裏繼承。kobject中集成了一系列對自己沒用的能力,但這些功能在繼承的類裏很有用。c語言不支持直觀地使用繼承,所以必須使用其它方式完成——例如結構內嵌。

    例如,UIO設備代碼定義了一個表示內存範圍的結構:

struct uio_mem
{
    struct kobject kobj;
    unsigned long addr;
    unsigned long size;
    int memtype;
    void __iomem *internal_addr;
};

如果你有了一個uio_mem類型的對象,要找到它裏面的kobject很簡單,只要使用相應的kobj結構成員就行了。但處理kobject的代碼往往需要完成相反的過程:給你一個kobject的指針,如何找到指向uio_mem的指針?你需要避免使用一些把戲(例如假設kobject就是uio_mem的第一個結構成員),相反,你應該使用container_of宏,它定義在<linux/kernel.h>中:

container_of(pointer, type, member)


這裏的pointer是指向內嵌kobject的指針,type是kobject所在地結構類型,member是結構類型中kobject成員的名字。container_of()的返回值就是kobject所在的結構類型。假設有一個內嵌在uio_mem中的kobject的指針"kp",可以很容易地把它轉化成指向uio_mem結構的指針:

struct uio_mem *u_mem = container_of(kp, struct uio_mem, kobj);

程序員往往會定義一個更簡單的宏來進行從kobject指針到結構指針的回溯。

 

初始化kobject

    創建kobject的代碼同樣要初始化kobject。可以調用kobject_init()完成kobject內部的初始化。

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

    要初始化kobject,需要相應的ktype,因爲kobject必須有一個對應的ktype。調用完kobject_init()之後,要調用kobject_add()把kobject註冊到sysfs中。

int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);

   kobject_add設定了kobject的父節點,kobject的名字。如果kobject需要和一個特定的kset關聯,在調用kobject_add()之前要先對kobj->kset賦值。如果一個kobject與一個kset關聯,那它的父節點可以設爲NULL,這樣kobject的父節點會在調用kobject_add()時自動設爲kset內部的kobject。

   因爲kobject的名字是在它調用kobject_add()時設定的,會在sysfs中顯示,所以不要手動修改kobject名字,而是調用kobject_rename()。

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

    kobject_rename 不會加鎖,不會檢查name是否有效,所以調用者需要提供名稱檢查和串行化管理。

    有一個叫做kobject_set_name()的汗水,但已經過時了,準備移除。所以不要使用kobject_set_name。

    爲了更好地獲取kobject的名字,使用kobject_name()。

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

    有一個幫助函數,既可以初始化kobject,有可以將其註冊到sysfs中,它叫做kobject_init_and_add。

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

 

uevent

    一個kobject被註冊到sysfs中後,需要向世界宣佈它的創建。這需要調用kobject_uevent()。

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

    在kobject加入內核時,先使用KOBJ_ADD action。這一調用需要等kobject的屬性和子kobject初始化好之後才行,因爲調用之後用戶空間會立刻檢查kobject的屬性和子kobject。

    當kobject從內核中刪除時,KOBJ_REMOVE action會被自動發出,所以調用者不需要手動發出KOBJ_REMOVE。

 

引用計數

    kobject的一個重要功能就是充當所嵌入結構的引用計數。只要對象的引用還存在,對象就要存在。管理一個kobject引用計數的低層函數如下:

struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);

    調用kobject_get()會增加kobject的引用計數,並返回kobject指針。

    當一個引用要被釋放,需要調用kobject_put(),並可能要銷燬kobject。注意kobject_init()設置計數值爲1,所以創建kobject的代碼需要調用kobject_put來釋放創建時生成的引用。

    因爲kobject是動態的,他們不應該被靜態聲明或者放在堆棧上,而應該動態創建。內核的更高版本會進行運行時檢查,對靜態創建的kobject予以警告。

    如果你只想用kobject爲你的結構提供一個引用計數器,可以用kref替換,kobject太浪費了。

 

創建簡單的kobject

    有時開發者只想在sysfs中創建一個簡單的目錄,而且不想考慮與kset、show函數、store函數,等一系列細節問題。這是就可以創建一個單獨的kobject,用下面的函數創建。

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

    這個函數會創建一個kobject,並把它放在sysfs中parent的子目錄中。爲了創建這個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。
    具體使用簡單kobject的例子可以參見samples/kobject/kobject-example.c。

 

ktype和release函數

    我們還沒有討論當kobject引用計數降爲零時會發生什麼。創建kobject的代碼通常不知道引用計數何時會降爲零,如果它知道,那使用kobject就沒什麼意義了。即使是可以預測的對象生命週期,也會在註冊到sysfs文件系統後變得更復雜。因爲系統的其它部分也可以獲得註冊到系統中的kobject的引用。

    我們的目的是在kobject的引用計數降爲零之前,決不能釋放kobject。這個引用計數並非由創建kobject的代碼直接控制,所以要在kobject引用計數降爲零時異步地通知創建它的代碼。一旦你通過kobject_add()把一個kobject註冊到系統中,就決不能直接用kfree()釋放它。唯一安全的做法是使用kobject_put()。

    這種釋放時對創建代碼的異步通知是通過kobject的release函數完成的。通常release函數有如下的形式。

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一定要有一個release函數,並且這個kobject在調用release函數前一定要保持存在狀態。否則代碼就是有缺陷的。注意,如果代碼沒有提供一個release函數,內核會提醒你的。不要試圖用空的release函數來消除警告;如果你那樣做,你最終會被kobject的維護者無情地嘲弄。【作者爲了提醒這點已經開始不擇手段了】

    注意,在release函數中,kobject的名字仍然是有效的,只是絕不能被更改。不然在kobject中會出現內存泄露。
    有趣的是,release函數指針並不存放在kobject中,而是在ktype中。所以我們下面介紹ktype。

struct kobj_type {
    void (*release)(struct kobject *);
    struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
};   

    ktype用來表示一類kobject的公共內容。每個kobject都要有一個對應的kobj_type結構,在kobject_init()或者kobject_init_and_add()調用時給出。

    kobj_type中的release函數指針,當然就是指向這類kobject使用的release函數。另外兩個部分(sysfs_ops和default_attrs)管理這類kobject在sysfs中如何顯示,對它們的討論超出了本文檔的範圍。

    default_attrs指針是一個默認屬性的鏈表,這些屬性會在這類kobject創建時自動創建。

 

kset

    一個kset是一些kobject的集合,這些kobject通過kset關聯在一起。一個kset中的kobject並不要求非得使用同樣的ktype,但如果不是,必須加以注意。

    一個kset有如下功能: 

         1) 它作爲一個容器,包含一組對象。它可以被內核用來跟蹤所有的塊設備或者所有的PCI設備驅動。

         2) 一個kset在sysfs中變現爲一個分層的目錄。每個kset都有一個內部kobject,作爲其它kobject的父目錄,其餘各個kobject都是它的子目錄。sysfs的頂層目錄結構也是這樣組成的。

        3) kset可以支持kobject的熱插拔,並且影響uevent事件如何被髮布到用戶空間。

    從面向對象的角度來講,kset是頂層類容器。kset也有自己內部的kobject,但這個內部kobject是由kset代碼管理的,不允許其它用戶直接訪問。

    kset用一個標準的鏈表鏈接它的子kobject。而子kobject又反過來通過結構中的kset域指向kset。在絕大部分情況下,kobject都屬於自己所在的kset,把自己的parent域設爲所在kset的內部kobject。

    因爲kset包括一個內部kobject,kset必須動態創建,不能靜態聲明。可以用如下函數創建一個新的kset。

struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *u, struct kobject *parent);

    當你不再使用一個kset,可以用kset_unregister()釋放它。

void kset_unregister(struct kset *kset);

    在samples/kobject/kset-example.c中有使用kset的例子。

    如果一個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,則不會發送。

    name函數是用來覆蓋uevent發送到用戶控件的kset名字。默認情況下,這個名字和kset的名字一樣,但如果提供了name函數,這個名字會被覆蓋。

    uevent函數在uevent將被髮送到用戶空間時調用,可以在uevent中添加更多的環境變量。

 

    大家或許會懷疑,一個kobject究竟是如何被加入一個kset中的,沒有一個函數進行此項操作。其實這項任務是由kobject_add()完成。當一個kobject參數傳給kobject_add()時,它的kset域應該指向所屬的kset,其餘的工作會由kobject_add()完成。

    如果屬於一個kset的kobject沒有設置parent域,它會被放在kset目錄下。但如果一個kobject設置了parent kobject,雖然它仍屬於這個kset,但卻放在parent kobject的目錄下。

 

  
刪除kobject

    如果一個kobject成功地註冊到內核中,在代碼結束對其操作時必須清除該kobject。可以調用kobject_put()。通過調用kobject_put(),系統會自動釋放與該kobject有關的所有內存。如果該kobject發送過一個KOBJ_ADD消息,結束時會對應地發一個KOBJ_REMOVE消息,其它的sysfs管理也會被相應地做好。

    如果你需要一個兩段式的kobject刪除(例如你不希望在刪除kobject時進入睡眠),可以調用kobject_del()將kobject從sysfs中註銷。這會使kobject不可見,但還沒有被清除,對它的引用計數也不變。之後某個時間需要調用kobject_put()結束該kobject相關的內存清理工作。

    kobject_del()可以用來減少對parent object的引用,特別是在循環引用的時候。父對象引用子對象在某些情況下是合法的。爲了打破環狀的引用,必須顯式地調用kobject_del(),這樣才能將計數降爲零,調用release函數,清除環中的kobject。

    sample/kobject/kset-example.c是一個使用kobject和kset的簡單例子。



  


 

 

 

 




 

發佈了49 篇原創文章 · 獲贊 32 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章