1. 前言
Kobject是Linux裝置模型的基礎,也是裝置模型中最難了解的一部分(可參考Documentation/kobject.txt的表述)。是以有必要先把它分析清楚。
2. 基本概念
由“Linux裝置模型(1)_基本概念”可知,Linux裝置模型的核心是使用Bus、Class、Device、Driver四個核心資料結構,将大量的、不同功能的硬體裝置(以及驅動該硬體裝置的方法),以樹狀結構的形式,進行歸納、抽象,進而友善Kernel的統一管理。
而硬體裝置的數量、種類是非常多的,這就決定了Kernel中将會有大量的有關裝置模型的資料結構。這些資料結構一定有一些共同的功能,需要抽象出來統一實作,否則就會不可避免的産生備援代碼。這就是Kobject誕生的背景。
目前為止,Kobject主要提供如下功能:
- 通過parent指針,可以将所有Kobject以層次結構的形式組合起來。
- 使用一個引用計數(reference count),來記錄Kobject被引用的次數,并在引用次數變為0時把它釋放(這是Kobject誕生時的唯一功能)。
- 和sysfs虛拟檔案系統配合,将每一個Kobject及其特性,以檔案的形式,開放到使用者空間(有關sysfs,會在其它文章中專門描述,本文不會涉及太多内容)。
注1:在Linux中,Kobject幾乎不會單獨存在。它的主要功能,就是内嵌在一個大型的資料結構中,為這個資料結構提供一些底層的功能實作。
注2:Linux driver開發者,很少會直接使用Kobject以及它提供的接口,而是使用建構在Kobject之上的裝置模型接口。
3. 代碼解析
3.1 在Linux Kernel source code中的位置
在Kernel源代碼中,Kobject由如下兩個檔案實作:
- include/linux/kobject.h
- lib/kobject.c
其中kobject.h為Kobject的頭檔案,包含所有的資料結構定義和接口聲明。kobject.c為核心功能的實作。
3.2 主要的資料結構
在描述資料結構之前,有必要說明一下Kobject, Kset和Ktype這三個概念。
Kobject是基本資料類型,每個Kobject都會在"/sys/“檔案系統中以目錄的形式出現。
Ktype代表Kobject(嚴格地講,是包含了Kobject的資料結構)的屬性操作集合(由于通用性,多個Kobject可能共用同一個屬性操作集,是以把Ktype獨立出來了)。
注3:在裝置模型中,ktype的命名和解釋,都非常抽象,了解起來非常困難,後面會詳細說明。
Kset是一個特殊的Kobject(是以它也會在"/sys/“檔案系統中以目錄的形式出現),它用來集合相似的Kobject(這些Kobject可以是相同屬性的,也可以不同屬性的)。
- 首先看一下Kobject的原型
1: /* Kobject: include/linux/kobject.h line 60 */
2: struct kobject {
3: const char *name;
4: struct list_head entry;
5: struct kobject *parent;
6: struct kset *kset;
7: struct kobj_type *ktype;
8: struct sysfs_dirent *sd;
9: struct kref kref;
10: unsigned int state_initialized:1;
11: unsigned int state_in_sysfs:1;
12: unsigned int state_add_uevent_sent:1;
13: unsigned int state_remove_uevent_sent:1;
14: unsigned int uevent_suppress:1;
15: };
name,該Kobject的名稱,同時也是sysfs中的目錄名稱。由于Kobject添加到Kernel時,需要根據名字注冊到sysfs中,之後就不能再直接修改該字段。如果需要修改Kobject的名字,需要調用kobject_rename接口,該接口會主動處理sysfs的相關事宜。
entry,用于将Kobject加入到Kset中的list_head。
parent,指向parent kobject,以此形成層次結構(在sysfs就表現為目錄結構)。
kset,該kobject屬于的Kset。可以為NULL。如果存在,且沒有指定parent,則會把Kset作為parent(别忘了Kset是一個特殊的Kobject)。
ktype,該Kobject屬于的kobj_type。每個Kobject必須有一個ktype,或者Kernel會提示錯誤。
sd,該Kobject在sysfs中的表示。
kref,"struct kref”類型(在include/linux/kref.h中定義)的變量,為一個可用于原子操作的引用計數。
state_initialized,訓示該Kobject是否已經初始化,以在Kobject的Init,Put,Add等操作時進行異常校驗。
state_in_sysfs,訓示該Kobject是否已在sysfs中呈現,以便在自動登出時從sysfs中移除。
state_add_uevent_sent/state_remove_uevent_sent,記錄是否已經向使用者空間發送ADD uevent,如果有,且沒有發送remove uevent,則在自動登出時,補發REMOVE uevent,以便讓使用者空間正确處理。
uevent_suppress,如果該字段為1,則表示忽略所有上報的uevent事件。
注4:Uevent提供了“使用者空間通知”的功能實作,通過該功能,當核心中有Kobject的增加、删除、修改等動作時,會通知使用者空間。有關該功能的具體内容,會在其它文章較長的描述。
- Kset的原型為
1: /* include/linux/kobject.h, line 159 */
2: struct kset {
3: struct list_head list;
4: spinlock_t list_lock;
5: struct kobject kobj;
6: const struct kset_uevent_ops *uevent_ops;
7: };
list/list_lock,用于儲存該kset下所有的kobject的連結清單。
kobj,該kset自己的kobject(kset是一個特殊的kobject,也會在sysfs中以目錄的形式展現)。
uevent_ops,該kset的uevent操作函數集。當任何Kobject需要上報uevent時,都要調用它所從屬的kset的uevent_ops,添加環境變量,或者過濾event(kset可以決定哪些event可以上報)。是以,如果一個kobject不屬于任何kset時,是不允許發送uevent的。
- Ktype的原型為
1: /* include/linux/kobject.h, line 108 */
2: struct kobj_type {
3: void (*release)(struct kobject *kobj);
4: const struct sysfs_ops *sysfs_ops;
5: struct attribute **default_attrs;
6: const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
7: const void *(*namespace)(struct kobject *kobj);
8: };
release,通過該回調函數,可以将包含該種類型kobject的資料結構的記憶體空間釋放掉。
sysfs_ops,該種類型的Kobject的sysfs檔案系統接口。
default_attrs,該種類型的Kobject的atrribute清單(所謂attribute,就是sysfs檔案系統中的一個檔案)。将會在Kobject添加到核心時,一并注冊到sysfs中。
child_ns_type/namespace,和檔案系統(sysfs)的命名空間有關,這裡不再詳細說明。
總結,Ktype以及整個Kobject機制的了解。 Kobject的核心功能是:保持一個引用計數,當該計數減為0時,自動釋放(由本文所講的kobject子產品負責)Kobject所占用的meomry空間。這就決定了Kobject必須是動态配置設定的(隻有這樣才能動态釋放)。 而Kobject大多數的使用場景,是内嵌在大型的資料結構中(如Kset、device_driver等),是以這些大型的資料結構,也必須是動态配置設定、動态釋放的。那麼釋放的時機是什麼呢?是内嵌的Kobject釋放時。但是Kobject的釋放是由Kobject子產品自動完成的(在引用計數為0時),那麼怎麼一并釋放包含自己的大型資料結構呢? 這時Ktype就派上用場了。我們知道,Ktype中的release回調函數負責釋放Kobject(甚至是包含Kobject的資料結構)的記憶體空間,那麼Ktype及其内部函數,是由誰實作呢?是由上層資料結構所在的子產品!因為隻有它,才清楚Kobject嵌在哪個資料結構中,并通過Kobject指針以及自身的資料結構類型,找到需要釋放的上層資料結構的指針,然後釋放它。 講到這裡,就清晰多了。是以,每一個内嵌Kobject的資料結構,例如kset、device、device_driver等等,都要實作一個Ktype,并定義其中的回調函數。同理,sysfs相關的操作也一樣,必須經過ktype的中轉,因為sysfs看到的是Kobject,而真正的檔案操作的主體,是内嵌Kobject的上層資料結構! 順便提一下,Kobject是面向對象的思想在Linux kernel中的極緻展現,但C語言的優勢卻不在這裡,是以Linux kernel需要用比較巧妙(也很啰嗦)的手段去實作, |
3.3 功能分析
3.3.1 Kobject使用流程
Kobject大多數情況下(有一種例外,下面會講)會嵌在其它資料結構中使用,其使用流程如下:
- 定義一個struct kset類型的指針,并在初始化時為它配置設定空間,添加到核心中
- 根據實際情況,定義自己所需的資料結構原型,該資料結構中包含有Kobject
- 定義一個适合自己的ktype,并實作其中回調函數
- 在需要使用到包含Kobject的資料結構時,動态配置設定該資料結構,并配置設定Kobject空間,添加到核心中
- 每一次引用資料結構時,調用kobject_get接口增加引用計數;引用結束時,調用kobject_put接口,減少引用計數
- 當引用計數減少為0時,Kobject子產品調用ktype所提供的release接口,釋放上層資料結構以及Kobject的記憶體空間
上面有提過,有一種例外,Kobject不再嵌在其它資料結構中,可以單獨使用,這個例外就是:開發者隻需要在sysfs中建立一個目錄,而不需要其它的kset、ktype的操作。這時可以直接調用kobject_create_and_add接口,配置設定一個kobject結構并把它添加到kernel中。
3.3.2 Kobject的配置設定和釋放
前面講過,Kobject必須動态配置設定,而不能靜态定義或者位于堆棧之上,它的配置設定方法有兩種。
1. 通過kmalloc自行配置設定(一般是跟随上層資料結構配置設定),并在初始化後添加到kernel。這種方法涉及如下接口:
1: /* include/linux/kobject.h, line 85 */
2: extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
3: extern __printf(3, 4) __must_check
4: int kobject_add(struct kobject *kobj, struct kobject *parent,
5: const char *fmt, ...);
6: extern __printf(4, 5) __must_check
7: int kobject_init_and_add(struct kobject *kobj,
8: struct kobj_type *ktype, struct kobject *parent,
9: const char *fmt, ...);
kobject_init,初始化通過kmalloc等記憶體配置設定函數獲得的struct kobject指針。主要執行邏輯為:kobject_add,将初始化完成的kobject添加到kernel中,參數包括需要添加的kobject、該kobject的parent(用于形成層次結構,可以為空)、用于提供kobject name的格式化字元串。主要執行邏輯為:
- 确認kobj和ktype不為空
- 如果該指針已經初始化過(判斷kobj->state_initialized),列印錯誤提示及堆棧資訊(但不是緻命錯誤,是以還可以繼續)
- 初始化kobj内部的參數,包括引用計數、list、各種标志等
- 根據輸入參數,将ktype指針賦予kobj->ktype
- 确認kobj不為空,确認kobj已經初始化,否則錯誤退出
- 調用内部接口kobject_add_varg,完成添加操作
kobject_init_and_add,是上面兩個接口的組合,不再說明。
==========================内部接口======================================
kobject_add_varg,解析格式化字元串,将結果賦予kobj->name,之後調用kobject_add_internal接口,完成真正的添加操作。
kobject_add_internal,将kobject添加到kernel。主要執行邏輯為:
kobj_kset_join,負責将kobj加入到對應kset的連結清單中。
- 校驗kobj以及kobj->name的合法性,若不合法列印錯誤資訊并退出
- 調用kobject_get增加該kobject的parent的引用計數(如果存在parent的話)
- 如果存在kset(即kobj->kset不為空),則調用kobj_kset_join接口加入kset。同時,如果該kobject沒有parent,卻存在kset,則将它的parent設為kset(kset是一個特殊的kobject),并增加kset的引用計數
- 通過create_dir接口,調用sysfs的相關接口,在sysfs下建立該kobject對應的目錄
- 如果建立失敗,執行後續的復原操作,否則将kobj->state_in_sysfs置為1
這種方式配置設定的kobject,會在引用計數變為0時,由kobject_put調用其ktype的release接口,釋放記憶體空間,具體可參考後面有關kobject_put的講解。
2. 使用kobject_create建立
Kobject子產品可以使用kobject_create自行配置設定空間,并内置了一個ktype(dynamic_kobj_ktype),用于在計數為0是釋放空間。代碼如下:
1: /* include/linux/kobject.h, line 96 */
2: extern struct kobject * __must_check kobject_create(void);
3: extern struct kobject * __must_check kobject_create_and_add(const char *name,
4: struct kobject *parent);
1: /* lib/kobject.c, line 605 */
2: static void dynamic_kobj_release(struct kobject *kobj)
3: {
4: pr_debug("kobject: (%p): %s\n", kobj, __func__);
5: kfree(kobj);
6: }
7:
8: static struct kobj_type dynamic_kobj_ktype = {
9: .release = dynamic_kobj_release,
10: .sysfs_ops = &kobj_sysfs_ops,
11: };
kobject_create,該接口為kobj配置設定記憶體空間,并以dynamic_kobj_ktype為參數,調用kobject_init接口,完成後續的初始化操作。
kobject_create_and_add,是kobject_create和kobject_add的組合,不再說明。
dynamic_kobj_release,直接調用kfree釋放kobj的空間。
3.3.3 Kobject引用計數的修改
通過kobject_get和kobject_put可以修改kobject的引用計數,并在計數為0時,調用ktype的release接口,釋放占用空間。
1: /* include/linux/kobject.h, line 103 */
2: extern struct kobject *kobject_get(struct kobject *kobj);
3: extern void kobject_put(struct kobject *kobj);
kobject_get,調用kref_get,增加引用計數。
kobject_put,以内部接口kobject_release為參數,調用kref_put。kref子產品會在引用計數為零時,調用kobject_release。
==========================内部接口======================================
kobject_release,通過kref結構,擷取kobject指針,并調用kobject_cleanup接口繼續。
kobject_cleanup,負責釋放kobject占用的空間,主要執行邏輯如下:
- 檢查該kobject是否有ktype,如果沒有,列印警告資訊
- 如果該kobject向使用者空間發送了ADD uevent但沒有發送REMOVE uevent,補發REMOVE uevent
- 如果該kobject有在sysfs檔案系統注冊,調用kobject_del接口,删除它在sysfs中的注冊
- 調用該kobject的ktype的release接口,釋放記憶體空間
- 釋放該kobject的name所占用的記憶體空間
3.3.4 Kset的初始化、注冊
Kset是一個特殊的kobject,是以其初始化、注冊等操作也會調用kobject的相關接口,除此之外,會有它特有的部分。另外,和Kobject一樣,kset的記憶體配置設定,可以由上層軟體通過kmalloc自行配置設定,也可以由Kobject子產品負責配置設定,具體如下。
1: /* include/linux/kobject.h, line 166 */
2: extern void kset_init(struct kset *kset);
3: extern int __must_check kset_register(struct kset *kset);
4: extern void kset_unregister(struct kset *kset);
5: extern struct kset * __must_check kset_create_and_add(const char *name,
6: const struct kset_uevent_ops *u,
7: struct kobject *parent_kobj);
kset_init,該接口用于初始化已配置設定的kset,主要包括調用kobject_init_internal初始化其kobject,然後初始化kset的連結清單。需要注意的時,如果使用此接口,上層軟體必須提供該kset中的kobject的ktype。
kset_register,先調用kset_init,然後調用kobject_add_internal将其kobject添加到kernel。
kset_unregister,直接調用kobject_put釋放其kobject。當其kobject的引用計數為0時,即調用ktype的release接口釋放kset占用的空間。
kset_create_and_add,會調用内部接口kset_create動态建立一個kset,并調用kset_register将其注冊到kernel。
==========================内部接口======================================
kset_create,該接口使用kzalloc配置設定一個kset空間,并定義一個kset_ktype類型的ktype,用于釋放所有由它配置設定的kset空間。
原創文章,轉發請注明出處。蝸窩科技,www.wowotech.net。