kobject之禪翻譯

簡介

  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的類型,memberkobject指針所指的數據結構中具體域的類型名字(如果指向的是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中的屬性要麼是直接要麼間接的被賦值,通過其創造者比如ktypekset,和其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看起來類似於您在下圖中所看到的。

kobject And kset structure
爲消除歧義,上圖要注意的兩點有:
(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層次結構創建指針。 請繼續關注所有工作原理的描述。

原文: The zen of kobject

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章