14.1. Kobjects, Ksets 和 Subsystems

2018-02-24 15:50 更新

14.1.?Kobjects, Ksets 和 Subsystems

Kobject 是基礎(chǔ)的結(jié)構(gòu), 它保持設(shè)備模型在一起. 初始地它被作為一個簡單的引用計數(shù), 但是它的責(zé)任已隨時間增長, 并且因此有了它自己的戰(zhàn)場. struct kobject 所處理的任務(wù)和它的支持代碼現(xiàn)在包括:

對象的引用計數(shù)
常常, 當(dāng)一個內(nèi)核對象被創(chuàng)建, 沒有方法知道它會存在多長時間. 一種跟蹤這種對象生命周期的方法是通過引用計數(shù). 當(dāng)沒有內(nèi)核代碼持有對給定對象的引用, 那個對象已經(jīng)完成了它的有用壽命并且可以被刪除.

sysfs 表示
在 sysfs 中出現(xiàn)的每個對象在它的下面都有一個 kobject, 它和內(nèi)核交互來創(chuàng)建它的可見表示.

數(shù)據(jù)結(jié)構(gòu)粘和
設(shè)備模型是, 整體來看, 一個極端復(fù)雜的由多級組成的數(shù)據(jù)結(jié)構(gòu), 各級之間有許多連接. kobject 實現(xiàn)這個結(jié)構(gòu)并且保持它在一起.

熱插拔事件處理
kobject 子系統(tǒng)處理事件的產(chǎn)生, 事件通知用戶空間關(guān)于系統(tǒng)中硬件的來去.

你可能從前面的列表總結(jié)出 kobject 是一個復(fù)雜的結(jié)構(gòu). 這可能是對的. 通過一次看一部分, 但是, 是有可能理解這個結(jié)構(gòu)和它如何工作的.

14.1.1.?Kobject 基礎(chǔ)

一個 kobject 有類型 struct kobject; 它在 <linux/kobject.h> 中定義. 這個文件還包含許多其他和 kobject 相關(guān)的結(jié)構(gòu)的聲明, 一個操作它們的函數(shù)的長列表.

14.1.1.1.?嵌入的 kobjects

在我們進入細節(jié)前, 值得花些時間理解如何使用 kobjects. 如果你回看被 kobjects 處理的函數(shù)列表, 你會看到它們都是代表其他對象進行的服務(wù). 一個 kobject, 換句話說, 對其自己很少感興趣; 它存在僅僅為了結(jié)合一個高級對象到設(shè)備模型.

因此, 對于內(nèi)核代碼它很少(甚至不知道)創(chuàng)建一個孤立的 kobject; 相反, kobject 被用來控制存取更大的, 特定域的對象. 為此, kobject 被嵌入到其他結(jié)構(gòu)中. 如果你習(xí)慣以面向?qū)ο蟮男g(shù)語考慮事情, kobject 可被看作一個頂級的, 抽象類, 其他的類自它而來. 一個 kobject 實現(xiàn)一系列功能, 這些功能對自己不是特別有用而對其他對象是好的. C 語言不允許直接表達繼承, 因此其他的技術(shù) -- 例如將一個結(jié)構(gòu)嵌入另一個 -- 必須使用.

作為一個例子, 讓我們回看 struct cdev, 我們在第 3 章遇到過它. 那個結(jié)構(gòu), 如同在 2.6.10 內(nèi)核中發(fā)現(xiàn)的, 看來如此:


struct cdev {
 struct kobject kobj;
 struct module *owner;
 struct file_operations *ops;
 struct list_head list;
 dev_t dev;
 unsigned int count; 
};

我們可以看出, cdev 結(jié)構(gòu)有一個 kobject 嵌在里面. 如果你有一個這樣的結(jié)構(gòu), 會發(fā)現(xiàn)它的嵌入的 kobject 只是使用 kobj 成員. 使用 kobjects 的代碼有相反的問題, 但是: 如果一個 struct kobject 指針, 什么是指向包含結(jié)構(gòu)的指針? 你應(yīng)當(dāng)避免竅門(例如假定 kobject 是在結(jié)構(gòu)的開始), 并且, 相反, 使用 container_of 宏 (在第 3 章的"open 方法"一節(jié)中介紹的). 因此轉(zhuǎn)換一個指向嵌在一個結(jié)構(gòu) cdev 中的一個 struct kobject 的指針 kp 的方法是:


struct cdev *device = container_of(kp, struct cdev, kobj); 

程序員常常定義一個簡單的宏來"后向轉(zhuǎn)換" kobject 指針到包含類型.

14.1.1.2.?kobject 初始化

本書已經(jīng)展示了許多數(shù)據(jù)類型, 帶有簡單的在編譯或者運行時初始化機制. 一個 kobject 的初始化有些復(fù)雜, 特別當(dāng)使用它的所有函數(shù)時. 不管一個 kobject 如何使用, 但是, 必須進行幾個步驟.

這些步驟的第一個是僅僅設(shè)置整個 kobject 為 0, 常常使用一個對 memset 的調(diào)用. 常常這個初始化作為清零這個 kobjiect 嵌入的結(jié)構(gòu)的一部分. 清零一個 kobject 失敗導(dǎo)致非常奇怪的崩潰, 進一步會掉線; 這不是你想跳過的一步.

下一步是設(shè)立一些內(nèi)部成員, 使用對 kobject_init() 的調(diào)用:


void kobject_init(struct kobject *kobj); 

在其他事情中, kobject_init 設(shè)置 kobject 的引用計數(shù)為 1. 調(diào)用 kobject_init 不夠, 但是. kobject 用戶必須, 至少, 設(shè)置 kobject 的名子. 這是用在 sysfs 入口的名子. 如果你深入內(nèi)核代碼, 你可以發(fā)現(xiàn)直接拷貝一個字符串到 kobject 的名子成員的代碼, 但是應(yīng)當(dāng)避免這個方法. 相反, 使用:


int kobject_set_name(struct kobject *kobj, const char *format, ...); 

這個函數(shù)采用一個 printk 風(fēng)格的變量參數(shù)列表. 不管你信或不信, 對這種操作實際上可能失敗( 他可能試圖分配內(nèi)存 ); 負責(zé)任的代碼應(yīng)當(dāng)檢查返回值并且有針對性的相應(yīng).

其他的由創(chuàng)建者應(yīng)當(dāng)設(shè)置的 kobject 成員, 直接或間接, 是 ktype, kset, 和 parent. 我們在本章稍后到這些.

14.1.1.3.?引用計數(shù)的操作

一個 kobject 的其中一個關(guān)鍵函數(shù)是作為一個引用計數(shù)器, 給一個它被嵌入的對象. 只要對這個對象的引用存在, 這個對象( 和支持它的代碼) 必須繼續(xù)存在. 來操作一個 kobject 的引用計數(shù)的低級函數(shù)是:


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

一個對 kobject_get 的成功調(diào)用遞增 kobject 的 引用計數(shù)并且返回一個指向 kobject 的指針. 如果, 但是, 這個 kobject 已經(jīng)在被銷毀的過程中, 這個操作失敗, 并且 kobject_get 返回 NULL. 這個返回值必須總是被測試, 否則可能導(dǎo)致無法結(jié)束的令人不愉快的競爭情況.

當(dāng)一個引用被釋放, 對 kobject_put 的調(diào)用遞減引用計數(shù), 并且可能地, 釋放這個對象. 記住 kobject _init 設(shè)置這個引用計數(shù)為 1; 因此當(dāng)你創(chuàng)建一個 kobject, 你應(yīng)當(dāng)確保對應(yīng)地采取 kobject_put 調(diào)用, 當(dāng)這個初始化引用不再需要.

注意, 在許多情況下, 在 kobject 自身中的引用計數(shù)可能不足以阻止競爭情況. 一個 kobject 的存在( 以及它的包含結(jié)構(gòu) ) 可能非常, 例如, 需要創(chuàng)建這個 kobject 的模塊的繼續(xù)存在. 在這個 kobject 仍然在被傳送時不能卸載那個模塊. 這是為什么我們上面看到的 cdev 結(jié)構(gòu)包含一個 struct module 指針. struct cdev 的引用計數(shù)實現(xiàn)如下:


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;
}

創(chuàng)建一個對 cdev 結(jié)構(gòu)的引用還需要創(chuàng)建一個對擁有它的模塊的引用. 因此, cdev_get 使用 try_module_get 來試圖遞增這個模塊的使用計數(shù). 如果這個操作成功, kobject_get 被同樣用來遞增 kobject 的引用計數(shù). 那個操作可能失敗, 當(dāng)然, 因此這個代碼檢查自 kobject_get 的返回值并且釋放它的對模塊的引用如果事情沒有解決.

14.1.1.4.?釋放函數(shù)和 kobject 類型

討論中仍然缺失的一個重要事情是當(dāng)一個 kobject 的引用計數(shù)到 0 時會發(fā)生什么. 創(chuàng)建 kobject 的代碼通常不知道什么時候要發(fā)生這個情況; 如果它知道, 在第一位使用一個引用計數(shù)就沒有意義了. 即便當(dāng)引入 sysfs 時可預(yù)測的對象生命周期變得更加復(fù)雜; 用戶空間程序可保持一個對 kobject 的引用( 通過保持一個它的關(guān)聯(lián)的 sysfs 文件打開 )一段任意的時間.

最后的結(jié)果是一個被 kobject 保護的結(jié)構(gòu)無法在任何一個單個的, 可預(yù)測的驅(qū)動生命周期中的點被釋放, 但是可以在必須準(zhǔn)備在 kobject 的引用計數(shù)到 0 的任何時刻運行的代碼中. 引用計數(shù)不在創(chuàng)建 kobject 的代碼的直接控制之下. 因此這個代碼必須被異步通知, 無論何時對它的 kobject 的最后引用消失.

這個通知由 kobject 的一個釋放函數(shù)來完成. 常常地, 這個方法有一個形式如下:


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);
}

要強調(diào)的重要一點是: 每個 kobject 必須有一個釋放函數(shù), 并且這個 kobject 必須持續(xù)( 以一致的狀態(tài) ) 直到這個方法被調(diào)用. 如果這些限制不滿足, 代碼就有缺陷. 當(dāng)這個對象還在使用時被釋放會有風(fēng)險, 或者在最后引用被返回后無法釋放對象.

有趣的是, 釋放方法沒有存儲在 kobject 自身里面; 相反, 它被關(guān)聯(lián)到包含 kobject 的結(jié)構(gòu)類型中. 這個類型被跟蹤, 用一個 struct kobj_type 結(jié)構(gòu)類型, 常常簡單地稱為一個 "ktype". 這個結(jié)構(gòu)看來如下:


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

在 struct kobj_type 中的 release 成員是, 當(dāng)然, 一個指向這個 kobject 類型的 release 方法的指針. 我們將回到其他 2 個成員( sysfs_ops 和 default_attrs )在本章后面.

每一個 kobject 需要有一個關(guān)聯(lián)的 kobj_type 結(jié)構(gòu). 易混淆地, 指向這個結(jié)構(gòu)的指針能在 2 個不同的地方找到. kobject 結(jié)構(gòu)自身包含一個成員(稱為 ktype)包含這個指針. 但是, 如果這個 kobject 是一個 kset 的成員, kobj_type 指針由 kset 提供. ( 我們將在下一節(jié)查看 ksets. ) 其間, 這個宏定義:


struct kobj_type *get_ktype(struct kobject *kobj); finds the kobj_type pointer for a given kobject. 

14.1.2.?kobject 層次, kset, 和子系統(tǒng)

kobject 結(jié)構(gòu)常常用來連接對象到一個層級的結(jié)構(gòu)中, 匹配正被建模的子系統(tǒng)的結(jié)構(gòu). 有 2 個分開的機制對于這個連接: parent 指針和 ksets.

在結(jié)構(gòu) kobject 中的 parent 成員是一個指向其他對象的指針 -- 代表在層次中之上的下一級. 如果, 例如, 一個 kobject 表示一個 USB 設(shè)備, 它的 parent 指針可能指示這個設(shè)備被插入的 hub.

parent 指針的主要用途是在 sysfs 層次中定位對象. 我們將看到這個如何工作, 在"低級 sysfs 操作"一節(jié)中.

14.1.2.1.?Ksets 對象

很多情況, 一個 kset 看來象一個 kobj_type 結(jié)構(gòu)的擴展; 一個 kset 是一個嵌入到相同類型結(jié)構(gòu)的 kobject 的集合. 但是, 雖然 struct kobj_type 關(guān)注的是一個對象的類型, struct kset 被聚合和集合所關(guān)注. 這 2 個概念已被分開以至于一致類型的對象可以出現(xiàn)在不同的集合中.

因此, 一個 kset 的主要功能是容納; 它可被當(dāng)作頂層的給 kobjects 的容器類. 實際上, 每個 kset 在內(nèi)部容納它自己的 kobject, 并且它可以, 在許多情況下, 如同一個 kobject 相同的方式被對待. 值得注意的是 ksets 一直在 sysfs 中出現(xiàn); 一旦一個 kset 已被建立并且加入到系統(tǒng), 會有一個 sysfs 目錄給它. kobjects 沒有必要在 sysfs 中出現(xiàn), 但是每個是 kset 成員的 kobject 都出現(xiàn)在那里.

增加一個 kobject 到一個 kset 常常在一個對象創(chuàng)建時完成; 它是一個 2 步的過程. kobject 的 kset 成員必須 ???; 接著kobject 應(yīng)當(dāng)被傳遞到:


int kobject_add(struct kobject *kobj); 

如常, 程序員應(yīng)當(dāng)小心這個函數(shù)可能失敗(在這個情況下它返回一個負錯誤碼)并且相應(yīng)地反應(yīng). 有一個內(nèi)核提供的方便函數(shù):


extern int kobject_register(struct kobject *kobj); 

這個函數(shù)僅僅是一個 kobject_init 和 kobject_add 的結(jié)合.

當(dāng)一個 kobject 被傳遞給 kobject_add, 它的引用計數(shù)被遞增. kset 中容納的, 畢竟, 是一個對這個對象的引用. 某種意義上, kobject 可能要必須從 kset 中移出來清除這個引用; 完成這個使用:


void kobject_del(struct kobject *kobj); 

還有一個 kobject_unregister 函數(shù), 是 kobject_del 和 kobject_put 的結(jié)合.

一個 kset 保持它的子女在一個標(biāo)準(zhǔn)的內(nèi)核鏈表中. 在大部分情況下, 被包含的 kobjects 也有指向這個 kset 的指針( 或者, 嚴(yán)格地, 它的嵌入 kobject)在它們的 parent 的成員. 因此, 典型地, 一個 kset 和它的 kobjects 看來有些象你在圖 一個簡單的 kset 層次中所見. 記住:

圖?14.2.?一個簡單的 kset 層次

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號