1. 前言
Kobject是Linux設(shè)備模型的基礎(chǔ),也是設(shè)備模型中最難理解的一部分(可參考Documentation/kobject.txt的表述)。因此有必要先把它分析清楚。
2. 基本概念
由“Linux設(shè)備模型(1)_基本概念”可知,Linux設(shè)備模型的核心是使用Bus、Class、Device、Driver四個(gè)核心數(shù)據(jù)結(jié)構(gòu),將大量的、不同功能的硬件設(shè)備(以及驅(qū)動(dòng)該硬件設(shè)備的方法),以樹狀結(jié)構(gòu)的形式,進(jìn)行歸納、抽象,從而方便Kernel的統(tǒng)一管理。
而硬件設(shè)備的數(shù)量、種類是非常多的,這就決定了Kernel中將會(huì)有大量的有關(guān)設(shè)備模型的數(shù)據(jù)結(jié)構(gòu)。這些數(shù)據(jù)結(jié)構(gòu)一定有一些共同的功能,需要抽象出來(lái)統(tǒng)一實(shí)現(xiàn),否則就會(huì)不可避免的產(chǎn)生冗余代碼。這就是Kobject誕生的背景。
目前為止,Kobject主要提供如下功能:
通過(guò)parent指針,可以將所有Kobject以層次結(jié)構(gòu)的形式組合起來(lái)。
使用一個(gè)引用計(jì)數(shù)(reference count),來(lái)記錄Kobject被引用的次數(shù),并在引用次數(shù)變?yōu)?時(shí)把它釋放(這是Kobject誕生時(shí)的唯一功能)。
和sysfs虛擬文件系統(tǒng)配合,將每一個(gè)Kobject及其特性,以文件的形式,開放到用戶空間(有關(guān)sysfs,會(huì)在其它文章中專門描述,本文不會(huì)涉及太多內(nèi)容)。
注1:在Linux中,Kobject幾乎不會(huì)單獨(dú)存在。它的主要功能,就是內(nèi)嵌在一個(gè)大型的數(shù)據(jù)結(jié)構(gòu)中,為這個(gè)數(shù)據(jù)結(jié)構(gòu)提供一些底層的功能實(shí)現(xiàn)。?
注2:Linux driver開發(fā)者,很少會(huì)直接使用Kobject以及它提供的接口,而是使用構(gòu)建在Kobject之上的設(shè)備模型接口。
3. 代碼解析
3.1 在Linux Kernel source code中的位置
在Kernel源代碼中,Kobject由如下兩個(gè)文件實(shí)現(xiàn):
include/linux/kobject.h
lib/kobject.c
其中kobject.h為Kobject的頭文件,包含所有的數(shù)據(jù)結(jié)構(gòu)定義和接口聲明。kobject.c為核心功能的實(shí)現(xiàn)。
3.2 主要的數(shù)據(jù)結(jié)構(gòu)
在描述數(shù)據(jù)結(jié)構(gòu)之前,有必要說(shuō)明一下Kobject, Kset和Ktype這三個(gè)概念。
Kobject是基本數(shù)據(jù)類型,每個(gè)Kobject都會(huì)在"/sys/“文件系統(tǒng)中以目錄的形式出現(xiàn)。
Ktype代表Kobject(嚴(yán)格地講,是包含了Kobject的數(shù)據(jù)結(jié)構(gòu))的屬性操作集合(由于通用性,多個(gè)Kobject可能共用同一個(gè)屬性操作集,因此把Ktype獨(dú)立出來(lái)了)。?
注3:在設(shè)備模型中,ktype的命名和解釋,都非常抽象,理解起來(lái)非常困難,后面會(huì)詳細(xì)說(shuō)明。
Kset是一個(gè)特殊的Kobject(因此它也會(huì)在"/sys/“文件系統(tǒng)中以目錄的形式出現(xiàn)),它用來(lái)集合相似的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的名稱,同時(shí)也是sysfs中的目錄名稱。由于Kobject添加到Kernel時(shí),需要根據(jù)名字注冊(cè)到sysfs中,之后就不能再直接修改該字段。如果需要修改Kobject的名字,需要調(diào)用kobject_rename接口,該接口會(huì)主動(dòng)處理sysfs的相關(guān)事宜。
entry,用于將Kobject加入到Kset中的list_head。
parent,指向parent kobject,以此形成層次結(jié)構(gòu)(在sysfs就表現(xiàn)為目錄結(jié)構(gòu))。
kset,該kobject屬于的Kset??梢詾镹ULL。如果存在,且沒有指定parent,則會(huì)把Kset作為parent(別忘了Kset是一個(gè)特殊的Kobject)。
ktype,該Kobject屬于的kobj_type。每個(gè)Kobject必須有一個(gè)ktype,或者Kernel會(huì)提示錯(cuò)誤。
sd,該Kobject在sysfs中的表示。
kref,"struct kref”類型(在include/linux/kref.h中定義)的變量,為一個(gè)可用于原子操作的引用計(jì)數(shù)。
state_initialized,指示該Kobject是否已經(jīng)初始化,以在Kobject的Init,Put,Add等操作時(shí)進(jìn)行異常校驗(yàn)。
state_in_sysfs,指示該Kobject是否已在sysfs中呈現(xiàn),以便在自動(dòng)注銷時(shí)從sysfs中移除。
state_add_uevent_sent/state_remove_uevent_sent,記錄是否已經(jīng)向用戶空間發(fā)送ADD uevent,如果有,且沒有發(fā)送remove uevent,則在自動(dòng)注銷時(shí),補(bǔ)發(fā)REMOVE uevent,以便讓用戶空間正確處理。
uevent_suppress,如果該字段為1,則表示忽略所有上報(bào)的uevent事件。
注4:Uevent提供了“用戶空間通知”的功能實(shí)現(xiàn),通過(guò)該功能,當(dāng)內(nèi)核中有Kobject的增加、刪除、修改等動(dòng)作時(shí),會(huì)通知用戶空間。有關(guān)該功能的具體內(nèi)容,會(huì)在其它文章詳細(xì)描述。
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是一個(gè)特殊的kobject,也會(huì)在sysfs中以目錄的形式體現(xiàn))。
uevent_ops,該kset的uevent操作函數(shù)集。當(dāng)任何Kobject需要上報(bào)uevent時(shí),都要調(diào)用它所從屬的kset的uevent_ops,添加環(huán)境變量,或者過(guò)濾event(kset可以決定哪些event可以上報(bào))。因此,如果一個(gè)kobject不屬于任何kset時(shí),是不允許發(fā)送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,通過(guò)該回調(diào)函數(shù),可以將包含該種類型kobject的數(shù)據(jù)結(jié)構(gòu)的內(nèi)存空間釋放掉。
sysfs_ops,該種類型的Kobject的sysfs文件系統(tǒng)接口。
default_attrs,該種類型的Kobject的atrribute列表(所謂attribute,就是sysfs文件系統(tǒng)中的一個(gè)文件)。將會(huì)在Kobject添加到內(nèi)核時(shí),一并注冊(cè)到sysfs中。
child_ns_type/namespace,和文件系統(tǒng)(sysfs)的命名空間有關(guān),這里不再詳細(xì)說(shuō)明。
總結(jié),Ktype以及整個(gè)Kobject機(jī)制的理解。?
Kobject的核心功能是:保持一個(gè)引用計(jì)數(shù),當(dāng)該計(jì)數(shù)減為0時(shí),自動(dòng)釋放(由本文所講的kobject模塊負(fù)責(zé))Kobject所占用的meomry空間。這就決定了Kobject必須是動(dòng)態(tài)分配的(只有這樣才能動(dòng)態(tài)釋放)。?
而Kobject大多數(shù)的使用場(chǎng)景,是內(nèi)嵌在大型的數(shù)據(jù)結(jié)構(gòu)中(如Kset、device_driver等),因此這些大型的數(shù)據(jù)結(jié)構(gòu),也必須是動(dòng)態(tài)分配、動(dòng)態(tài)釋放的。那么釋放的時(shí)機(jī)是什么呢?是內(nèi)嵌的Kobject釋放時(shí)。但是Kobject的釋放是由Kobject模塊自動(dòng)完成的(在引用計(jì)數(shù)為0時(shí)),那么怎么一并釋放包含自己的大型數(shù)據(jù)結(jié)構(gòu)呢??
這時(shí)Ktype就派上用場(chǎng)了。我們知道,Ktype中的release回調(diào)函數(shù)負(fù)責(zé)釋放Kobject(甚至是包含Kobject的數(shù)據(jù)結(jié)構(gòu))的內(nèi)存空間,那么Ktype及其內(nèi)部函數(shù),是由誰(shuí)實(shí)現(xiàn)呢?是由上層數(shù)據(jù)結(jié)構(gòu)所在的模塊!因?yàn)橹挥兴徘宄﨣object嵌在哪個(gè)數(shù)據(jù)結(jié)構(gòu)中,并通過(guò)Kobject指針以及自身的數(shù)據(jù)結(jié)構(gòu)類型,找到需要釋放的上層數(shù)據(jù)結(jié)構(gòu)的指針,然后釋放它。?
講到這里,就清晰多了。所以,每一個(gè)內(nèi)嵌Kobject的數(shù)據(jù)結(jié)構(gòu),例如kset、device、device_driver等等,都要實(shí)現(xiàn)一個(gè)Ktype,并定義其中的回調(diào)函數(shù)。同理,sysfs相關(guān)的操作也一樣,必須經(jīng)過(guò)ktype的中轉(zhuǎn),因?yàn)閟ysfs看到的是Kobject,而真正的文件操作的主體,是內(nèi)嵌Kobject的上層數(shù)據(jù)結(jié)構(gòu)!?
順便提一下,Kobject是面向?qū)ο蟮乃枷朐贚inux kernel中的極致體現(xiàn),但C語(yǔ)言的優(yōu)勢(shì)卻不在這里,所以Linux kernel需要用比較巧妙(也很啰嗦)的手段去實(shí)現(xiàn),
3.3 功能分析
3.3.1 Kobject使用流程
Kobject大多數(shù)情況下(有一種例外,下面會(huì)講)會(huì)嵌在其它數(shù)據(jù)結(jié)構(gòu)中使用,其使用流程如下:
定義一個(gè)struct kset類型的指針,并在初始化時(shí)為它分配空間,添加到內(nèi)核中
根據(jù)實(shí)際情況,定義自己所需的數(shù)據(jù)結(jié)構(gòu)原型,該數(shù)據(jù)結(jié)構(gòu)中包含有Kobject
定義一個(gè)適合自己的ktype,并實(shí)現(xiàn)其中回調(diào)函數(shù)
在需要使用到包含Kobject的數(shù)據(jù)結(jié)構(gòu)時(shí),動(dòng)態(tài)分配該數(shù)據(jù)結(jié)構(gòu),并分配Kobject空間,添加到內(nèi)核中
每一次引用數(shù)據(jù)結(jié)構(gòu)時(shí),調(diào)用kobject_get接口增加引用計(jì)數(shù);引用結(jié)束時(shí),調(diào)用kobject_put接口,減少引用計(jì)數(shù)
當(dāng)引用計(jì)數(shù)減少為0時(shí),Kobject模塊調(diào)用ktype所提供的release接口,釋放上層數(shù)據(jù)結(jié)構(gòu)以及Kobject的內(nèi)存空間
上面有提過(guò),有一種例外,Kobject不再嵌在其它數(shù)據(jù)結(jié)構(gòu)中,可以單獨(dú)使用,這個(gè)例外就是:開發(fā)者只需要在sysfs中創(chuàng)建一個(gè)目錄,而不需要其它的kset、ktype的操作。這時(shí)可以直接調(diào)用kobject_create_and_add接口,分配一個(gè)kobject結(jié)構(gòu)并把它添加到kernel中。
3.3.2 Kobject的分配和釋放
前面講過(guò),Kobject必須動(dòng)態(tài)分配,而不能靜態(tài)定義或者位于堆棧之上,它的分配方法有兩種。
1. 通過(guò)kmalloc自行分配(一般是跟隨上層數(shù)據(jù)結(jié)構(gòu)分配),并在初始化后添加到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,初始化通過(guò)kmalloc等內(nèi)存分配函數(shù)獲得的struct kobject指針。主要執(zhí)行邏輯為:
確認(rèn)kobj和ktype不為空
如果該指針已經(jīng)初始化過(guò)(判斷kobj->state_initialized),打印錯(cuò)誤提示及堆棧信息(但不是致命錯(cuò)誤,所以還可以繼續(xù))
初始化kobj內(nèi)部的參數(shù),包括引用計(jì)數(shù)、list、各種標(biāo)志等
根據(jù)輸入?yún)?shù),將ktype指針賦予kobj->ktype
kobject_add,將初始化完成的kobject添加到kernel中,參數(shù)包括需要添加的kobject、該kobject的parent(用于形成層次結(jié)構(gòu),可以為空)、用于提供kobject name的格式化字符串。主要執(zhí)行邏輯為:
確認(rèn)kobj不為空,確認(rèn)kobj已經(jīng)初始化,否則錯(cuò)誤退出
調(diào)用內(nèi)部接口kobject_add_varg,完成添加操作
kobject_init_and_add,是上面兩個(gè)接口的組合,不再說(shuō)明。
==========================內(nèi)部接口======================================
kobject_add_varg,解析格式化字符串,將結(jié)果賦予kobj->name,之后調(diào)用kobject_add_internal接口,完成真正的添加操作。
kobject_add_internal,將kobject添加到kernel。主要執(zhí)行邏輯為:
校驗(yàn)kobj以及kobj->name的合法性,若不合法打印錯(cuò)誤信息并退出
調(diào)用kobject_get增加該kobject的parent的引用計(jì)數(shù)(如果存在parent的話)
如果存在kset(即kobj->kset不為空),則調(diào)用kobj_kset_join接口加入kset。同時(shí),如果該kobject沒有parent,卻存在kset,則將它的parent設(shè)為kset(kset是一個(gè)特殊的kobject),并增加kset的引用計(jì)數(shù)
通過(guò)create_dir接口,調(diào)用sysfs的相關(guān)接口,在sysfs下創(chuàng)建該kobject對(duì)應(yīng)的目錄
如果創(chuàng)建失敗,執(zhí)行后續(xù)的回滾操作,否則將kobj->state_in_sysfs置為1
kobj_kset_join,負(fù)責(zé)將kobj加入到對(duì)應(yīng)kset的鏈表中。
這種方式分配的kobject,會(huì)在引用計(jì)數(shù)變?yōu)?時(shí),由kobject_put調(diào)用其ktype的release接口,釋放內(nèi)存空間,具體可參考后面有關(guān)kobject_put的講解。
2. 使用kobject_create創(chuàng)建
Kobject模塊可以使用kobject_create自行分配空間,并內(nèi)置了一個(gè)ktype(dynamic_kobj_ktype),用于在計(jì)數(shù)為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 ", 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分配內(nèi)存空間,并以dynamic_kobj_ktype為參數(shù),調(diào)用kobject_init接口,完成后續(xù)的初始化操作。
kobject_create_and_add,是kobject_create和kobject_add的組合,不再說(shuō)明。
dynamic_kobj_release,直接調(diào)用kfree釋放kobj的空間。
3.3.3 Kobject引用計(jì)數(shù)的修改
通過(guò)kobject_get和kobject_put可以修改kobject的引用計(jì)數(shù),并在計(jì)數(shù)為0時(shí),調(diào)用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,調(diào)用kref_get,增加引用計(jì)數(shù)。
kobject_put,以內(nèi)部接口kobject_release為參數(shù),調(diào)用kref_put。kref模塊會(huì)在引用計(jì)數(shù)為零時(shí),調(diào)用kobject_release。
==========================內(nèi)部接口======================================
kobject_release,通過(guò)kref結(jié)構(gòu),獲取kobject指針,并調(diào)用kobject_cleanup接口繼續(xù)。
kobject_cleanup,負(fù)責(zé)釋放kobject占用的空間,主要執(zhí)行邏輯如下:
檢查該kobject是否有ktype,如果沒有,打印警告信息
如果該kobject向用戶空間發(fā)送了ADD uevent但沒有發(fā)送REMOVE uevent,補(bǔ)發(fā)REMOVE uevent
如果該kobject有在sysfs文件系統(tǒng)注冊(cè),調(diào)用kobject_del接口,刪除它在sysfs中的注冊(cè)
調(diào)用該kobject的ktype的release接口,釋放內(nèi)存空間
釋放該kobject的name所占用的內(nèi)存空間
3.3.4 Kset的初始化、注冊(cè)
Kset是一個(gè)特殊的kobject,因此其初始化、注冊(cè)等操作也會(huì)調(diào)用kobject的相關(guān)接口,除此之外,會(huì)有它特有的部分。另外,和Kobject一樣,kset的內(nèi)存分配,可以由上層軟件通過(guò)kmalloc自行分配,也可以由Kobject模塊負(fù)責(zé)分配,具體如下。
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,主要包括調(diào)用kobject_init_internal初始化其kobject,然后初始化kset的鏈表。需要注意的時(shí),如果使用此接口,上層軟件必須提供該kset中的kobject的ktype。
kset_register,先調(diào)用kset_init,然后調(diào)用kobject_add_internal將其kobject添加到kernel。
kset_unregister,直接調(diào)用kobject_put釋放其kobject。當(dāng)其kobject的引用計(jì)數(shù)為0時(shí),即調(diào)用ktype的release接口釋放kset占用的空間。
kset_create_and_add,會(huì)調(diào)用內(nèi)部接口kset_create動(dòng)態(tài)創(chuàng)建一個(gè)kset,并調(diào)用kset_register將其注冊(cè)到kernel。
==========================內(nèi)部接口======================================
kset_create,該接口使用kzalloc分配一個(gè)kset空間,并定義一個(gè)kset_ktype類型的ktype,用于釋放所有由它分配的kset空間。
?
評(píng)論
查看更多