文章目錄
簡介
kobject 出現在2.5.45的開發版內核當中,最早想要簡單的用來實現對象的引用計數,但是後來遭遇了“mission creep”,成了sysfs和具體設備的膠水。對於驅動開發者而言,與kobjects直接交互的情況實際上很少,它們在高層次代碼中已經被抽象起來了,不過在中間層,還是有直接使用的kobject傾向的,所以kobject還是被大家知道了,瞭解他是什麼、怎麼工作還是有必要的。本文將覆蓋到kobject的類型定義和相關的主題,但不會提到太多kobjects與sysfs的關係。
爲了更好的理解kobject,這裏將採取多道次(multi-pass)的方法,從一些模糊的詞彙開始,然後再逐漸添加細節。以下介紹幾個重要的定義:
- kobject 是一個結構體。kobject有一個名字name和引用的數目reference count,有一個父指針parent pointer(如此可以建立一個kobjet的層次結構),有一個表示具體類型變量,或許還有一個sysfs的虛擬文件系統的表示。
kobject 本身其實並沒有趣,有趣的是嵌入kobject的其他代碼。 - ktype 是與kobject相關聯的類型,ktype將在kobject沒有引用之後對其進行處理,以及在sysfs系統的默認默認表示
- kset 是保存所有具有相同類型的kobject的集合,它是kobject的基礎集合。kset同樣可以保存kset,這表示一個kobject的parent經常會是一個包含它的kset,即使一般不會這樣。
sysfs中各種各樣的entries(目錄),這些目錄一般將在一個kset之下。 - subsystem是一個kset的集合,它將構成內核的主要子模塊。子系統一般對應着sysfs頂層的目錄。也就是sysfs下每一個目錄都是一個subsystem。
接下來將去查看如何創建和操作這些類型。這裏將採取一個自底向上的方式進行。所以現在回到kobject。
嵌入在其他結構中的kobjects(Embedding kobjects)
在內核代碼中幾乎不存在獨立存在的kobject創建需求,而是通過存在的kobjects去控制一個更大的、特定範圍的object。所以,kobject經常被發現嵌入在其它的結構裏。如果你熟悉面向對象思考的風格,那麼kobjects可以被視爲在最頂層,作爲抽象類被其他類繼承。kobject自己實現的功能並沒有多少作用,而是在被其他其他對象使用時纔有價值。C語言當然不支持繼承,所以其他技術被用來實現功能,其實就是通過結構嵌入(組合)來完成。
例如,在2.6.0-test6版本中的struct dev,以下這個數據結構表示一個字符設備:
struct cdev{
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
}
在已經有一個cdev結構的對象下,找到它內部嵌入的內容其實就是通過kobj指針完成。利用kobjects在代碼中將經常產生一些負面作用:比如有了一個kobject的指針,怎麼找到包含這個指針的結構?同時也需要避免一個誤區(假設kobject是結構的開始),可以通過使用一個container_of
這個宏,可以<linux/kernel.h>
中發現:
container_of(pointer, type, member)
該宏中pointer
指嵌入在其中的kobject
指針,type
是指包含這個kobject
的類型,member
是kobject
指針所指的數據結構中具體域的類型名字(如果指向的是kobject
,那這裏就是數據結構中kobject
類型的名字kobj
)。最後,該宏返回的結果就是指向type
的指針。
例如,有一個指針指向kobject
嵌入在cdev
中,叫做kp
,這裏可以通過container_of
獲得包含kp
的數據結構。
container_of(kp, struct cdev, kobj)
編程過程中將時常使用到這個簡單的宏完成向前轉型(back-casting)。
kobject的初始化
創建kobject必須對其進行初始化,需要調用kobject_init()
方法,當然方法內部有委託調用該方法。
void kobject_init(struct kobject *kobj);
除了別的方法之外,kobject_init()
方法將kobject的初始引用設置爲1。然而調用完kobject_init()
方法其實還是不夠的,Kobject 的使用者還得爲kobject至少設置一個名字,這個名字將會在sysfs當中被使用。如果大家在去深挖內核代碼會發現kobject將會有一個名字直接複製過來到name
域。作爲name
域修改方式:
int kobject_set_name(struct kobject *kobj, const char *format, ...);
這個函數接受printk風格的變量列表。此外Believe it or not,這個操作有可能會失敗,仔細的代碼需要檢查返回。
其他kobject
中的屬性要麼是直接要麼間接的被賦值,通過其創造者比如ktype
,kset
,和其parent
。
引用計數Reference counts
kobject的一個核心功能就是作爲其嵌入的對象的引用計數,只要引用存在,對象就一直存在。操縱引用計數底層的函數爲:
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
kobect_get()成功執行後,將kobect的引用計數加1並且返回kobject指針。然而如果kobject所在的進程被摧毀了,這個操作會失敗並返回空,所以該函數返回值必須檢查。
當一個引用被釋放後,kobject_put()會被調用並將引用計數減1,如果爲0了會釋放object。注意kobject_init()會將引用計數設爲1,所以初始化這個kobject的代碼最後需要kobejct_put()釋放kobject。
注意在很多情況下,kobject的引用計數可能不會避免的了競爭(也就是多線程不安全),kobject的存在可能正常。例如,當需要一個持續存在的內核模塊,這個模塊創建了kobject。不會卸載這個模塊,同時kobject又在到處傳遞。這就是爲什麼cdev結構包含了module的指針,cdev的引用計數是這樣實現的:
struct kobject *cdev_get(struct cdev *p)
{
struct module *owner = p->owner;
struct kobject *kobj;
if (owner && !try_module_get(owner))
return NULL;
kobj = kobject_get(&p->kobj);
if (!kobj)
module_put(owner);
return kobj;
}
創建一個cdev的結構需要創建一個到模塊的引用(這個模塊擁有cdev),所以cdev_get使用了try_module_get去嘗試增加模塊的使用計數,如果成功,kobject_get就回去增加kobject的引用計數,如果失敗,則檢查kobj的值並釋放模塊的引用。
與sysfs綁定
初始化的kobject將毫無問題地執行引用計數,但不會出現在sysfs中。 要創建sysfs條目,內核代碼必須將對象傳遞給kobject_add():
int kobject_add(struct kobject * kobj);
此操作也可能會失敗,需要檢查。
void kobject_del(struct kobject * kobj);
顧名思義,將從sysfs中刪除kobject。
有一個kobject_register函數,它實際上只是對kobject_init和kobject_add的調用的組合。 同樣,kobject_unregister將調用kobject_del,然後調用kobject_put釋放使用kobject_register或kobject_init創建的kobject。
ktypes和release方法
討論中仍然遺漏的重要一件事是,當kobject的引用計數達到零時會發生什麼。創建kobject的代碼通常不知道何時會發生。如果這樣做的話,首先使用kobject毫無意義。當引入sysfs時,甚至可預測的對象生命週期也變得更加複雜。用戶空間程序可以在任意時間段內保持對kobject的引用(通過使其關聯的sysfs文件之一保持打開狀態)。
最終結果是,受kobject保護的結構在其引用計數變爲零之前無法釋放。引用計數不受創建kobject的代碼的直接控制。因此,只要最後一個對其kobject的引用消失,該代碼就必須異步通知。
該通知是通過kobject的release()方法完成的。通常,這種方法的形式如下:
void my_object_releasestruct(kobject * kobj)
{
struct my_object * mine = container_of(kobj,struct my_object,kobj);
/ *對這個對象執行任何其他清理,然後... * /
kfree(mine);
}
有一點非常重要:每個kobject必須具有release()方法,並且kobject必須持久(處於一致狀態),直到調用該方法爲止。如果不滿足這些約束,則代碼有缺陷。
有趣的是,release方法沒有存儲在kobject本身中;相反,它與ktype關聯。因此,讓我們介紹struct kobj_type:
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
此結構用於描述特定類型的kobject(或更正確地說,是包含對象)。每個kobject需要具有關聯的kobj_type結構;可以在初始化時將指向該結構的指針放置在kobject的ktype字段中,或者(更有可能)可以由kobject的包含kset定義它。
當然,struct kobj_type中的release字段是指向此類kobject的release()方法的指針。其他兩個字段(sysfs_ops和default_attrs)控制在sysfs中如何表示這種類型的對象。它們超出了本文檔的範圍。
ksets
在許多方面,kset看起來像是kobj_type結構的擴展;但是,雖然struct kobj_type本身與對象的類型有關,但struct kset與集合有關。這兩個概念已經分開,因此相同類型的對象可以出現在不同的集合中。
kset具有以下功能:
- 它用作容納一組相同(類似)kobject的袋子。內核可以使用kset來存儲“所有塊設備”或“所有PCI設備驅動程序”。
- kset是將設備模型(和sysfs)保持在一起的目錄級粘合。每個kset都包含一個kobject,可以將其設置爲其他kobject的父對象。以這種方式構造了設備模型層次結構。
- Ksets可以支持kobject的“熱插拔”,並影響將熱插拔事件報告給用戶空間的方式。
用面向對象的術語來說,“ kset”是頂級容器類。 kset繼承自己的kobject,也可以視爲kobject。
kset將其孩子(kobject)保留在標準內核鏈接列表中。 Kobject通過其kset字段指向包含它的kset。在幾乎所有情況下,所包含的kobject在其parent字段中還具有指向kset(或嚴格來說是其嵌入的kobject)的指針。因此,通常,一個kset及其kobject看起來類似於您在下圖中所看到的。
爲消除歧義,上圖要注意的兩點有:
(1)所有嵌入的kobject可能嵌入了其它類型,不僅是kset;
(2)並不是kobject的父對象一定爲kset。
對於初始化和設置,kset具有與kobject非常相似的接口。存在以下功能:
void kset_init(struct kset *kset);
int kset_add(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);
很大程度上,這些函數稱爲kset的kobject_型函數的近似函數。
爲了管理kset的引用計數,情況大致相同:
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);
kset也具有一個名稱,該名稱存儲在嵌入的kobject中。因此,如果您有一個名爲my_set的kset,則可以使用以下命令設置其名稱:
kobject_set_name(my_set-> kobj,“名稱”);
Kset還具有指向kobj_type結構的指針(在ktype字段中),該結構描述了其中包含的kobject。此類型將應用於不包含指向其自身kobj_type結構的指針的任何kobject。
kset的另一個屬性是一組熱插拔操作。只要有kobject進入或離開kset,就會調用這些操作。他們能夠確定是否爲此更改生成了用戶空間熱插拔事件,並可以影響該事件的顯示方式。熱插拔操作超出了本文檔的範圍;稍後將與sysfs討論它們。
假設沒有提供執行該功能的功能,則可能會問如何將kobject準確地添加到kset。答案是此任務由kobject_add處理。將kobject傳遞給kobject_add時,其kset成員應指向該kobject所屬的kset。 kobject_add將處理其餘部分。當前沒有其他方法可以將kobject添加到kset中而不直接弄亂列表指針。
最後,一個kset包含一個子系統指針(稱爲subsys)。因此,現在該是討論子系統的時候了。
子系統
子系統代表整個內核的高層部分。它實際上是一個簡單的結構體:
struct subsystem {
struct kset kset;
struct rw_semaphore rwsem;
};
因此,子系統實際上只是圍繞kset的包裝。實際上,並不是那麼簡單。一個子系統可以包含多個kset。這是由struct kset中的subsys指針表示。因此,如果子系統中有多個kset,則不可能直接從subsystem中找到所有的kset。
每個kset必須屬於一個子系統。子系統的rwsem信號量用於控制對kset內部鏈接列表的連續訪問。
子系統通常使用特殊的宏聲明:
decl_subsys(char *name, struct kobj_type *type,
struct kset_hotplug_ops *hotplug_ops);
這個宏只是創建一個結構子系統(name加上_subsys就會是subsys的名字,type是內部kset初始化的需要,hotplug_ops也是)。
子系統函數:
void subsystem_init(struct subsystem *subsys);
int subsystem_register(struct subsystem *subsys);
void subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys)
void subsys_put(struct subsystem *subsys);
這些操作內部其實也就是對子系統kset的操作。
Kobject initialization again
現在我們已經涵蓋了所有這些內容,我們可以詳細討論如何爲kobject在內核中的存在做好準備。這是所有必須以某種方式初始化的struct kobject字段:
- name和k_name-對象的名稱。這些字段應始終使用kobject_set_name()初始化。
- refcount是kobject的引用計數;它由kobject_init()初始化
- parent是kobject所屬層次結構中的父對象。可以由創建者顯式設置。如果在調用kobject_add()時parent爲NULL,它將被設置爲包含kset的kobject。
- kset是指向將包含此kobject的kset的指針;應該在調用kobject_add()之前設置它。
- ktype是kobject的類型。如果kobject包含在kset中,並且該kset在其ktype字段中設置了類型,則不會使用kobject中的該字段。否則,應將其設置爲合適的kobj_type結構。
通常,kobject的大部分初始化是由管理包含的kset的層處理的。因此,回到我們的舊示例,字符驅動程序可以創建一個結構cdev,但不必擔心設置嵌入式kobject中的任何字段(名稱除外)。其他所有內容都由字符設備層處理。
Looking forward
到目前爲止,我們已經介紹了用於設置和操作kobject的操作。 核心概念相對簡單:kobjects可用於(1)維護對象的引用計數並在不再使用該對象時清除,以及(2)通過kset成員資格創建分層數據結構。
到目前爲止,缺少的是kobject如何在用戶空間中表示自己。 到kobjects的sysfs接口可以很容易地將信息導出到用戶空間(以及從用戶空間接收信息)。 sysfs的符號鏈接功能允許跨不同的kobject層次結構創建指針。 請繼續關注所有工作原理的描述。