## 14.1.?Kobjects, Ksets 和 Subsystems
Kobject 是基礎的結構, 它保持設備模型在一起. 初始地它被作為一個簡單的引用計數, 但是它的責任已隨時間增長, 并且因此有了它自己的戰場. struct kobject 所處理的任務和它的支持代碼現在包括:
對象的引用計數
常常, 當一個內核對象被創建, 沒有方法知道它會存在多長時間. 一種跟蹤這種對象生命周期的方法是通過引用計數. 當沒有內核代碼持有對給定對象的引用, 那個對象已經完成了它的有用壽命并且可以被刪除.
sysfs 表示
在 sysfs 中出現的每個對象在它的下面都有一個 kobject, 它和內核交互來創建它的可見表示.
數據結構粘和
設備模型是, 整體來看, 一個極端復雜的由多級組成的數據結構, 各級之間有許多連接. kobject 實現這個結構并且保持它在一起.
熱插拔事件處理
kobject 子系統處理事件的產生, 事件通知用戶空間關于系統中硬件的來去.
你可能從前面的列表總結出 kobject 是一個復雜的結構. 這可能是對的. 通過一次看一部分, 但是, 是有可能理解這個結構和它如何工作的.
### 14.1.1.?Kobject 基礎
一個 kobject 有類型 struct kobject; 它在 <linux/kobject.h> 中定義. 這個文件還包含許多其他和 kobject 相關的結構的聲明, 一個操作它們的函數的長列表.
#### 14.1.1.1.?嵌入的 kobjects
在我們進入細節前, 值得花些時間理解如何使用 kobjects. 如果你回看被 kobjects 處理的函數列表, 你會看到它們都是代表其他對象進行的服務. 一個 kobject, 換句話說, 對其自己很少感興趣; 它存在僅僅為了結合一個高級對象到設備模型.
因此, 對于內核代碼它很少(甚至不知道)創建一個孤立的 kobject; 相反, kobject 被用來控制存取更大的, 特定域的對象. 為此, kobject 被嵌入到其他結構中. 如果你習慣以面向對象的術語考慮事情, kobject 可被看作一個頂級的, 抽象類, 其他的類自它而來. 一個 kobject 實現一系列功能, 這些功能對自己不是特別有用而對其他對象是好的. C 語言不允許直接表達繼承, 因此其他的技術 -- 例如將一個結構嵌入另一個 -- 必須使用.
作為一個例子, 讓我們回看 struct cdev, 我們在第 3 章遇到過它. 那個結構, 如同在 2.6.10 內核中發現的, 看來如此:
~~~
struct cdev {
struct kobject kobj;
struct module *owner;
struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
~~~
我們可以看出, cdev 結構有一個 kobject 嵌在里面. 如果你有一個這樣的結構, 會發現它的嵌入的 kobject 只是使用 kobj 成員. 使用 kobjects 的代碼有相反的問題, 但是: 如果一個 struct kobject 指針, 什么是指向包含結構的指針? 你應當避免竅門(例如假定 kobject 是在結構的開始), 并且, 相反, 使用 container_of 宏 (在第 3 章的"open 方法"一節中介紹的). 因此轉換一個指向嵌在一個結構 cdev 中的一個 struct kobject 的指針 kp 的方法是:
~~~
struct cdev *device = container_of(kp, struct cdev, kobj);
~~~
程序員常常定義一個簡單的宏來"后向轉換" kobject 指針到包含類型.
#### 14.1.1.2.?kobject 初始化
本書已經展示了許多數據類型, 帶有簡單的在編譯或者運行時初始化機制. 一個 kobject 的初始化有些復雜, 特別當使用它的所有函數時. 不管一個 kobject 如何使用, 但是, 必須進行幾個步驟.
這些步驟的第一個是僅僅設置整個 kobject 為 0, 常常使用一個對 memset 的調用. 常常這個初始化作為清零這個 kobjiect 嵌入的結構的一部分. 清零一個 kobject 失敗導致非常奇怪的崩潰, 進一步會掉線; 這不是你想跳過的一步.
下一步是設立一些內部成員, 使用對 kobject_init() 的調用:
~~~
void kobject_init(struct kobject *kobj);
~~~
在其他事情中, kobject_init 設置 kobject 的引用計數為 1. 調用 kobject_init 不夠, 但是. kobject 用戶必須, 至少, 設置 kobject 的名子. 這是用在 sysfs 入口的名子. 如果你深入內核代碼, 你可以發現直接拷貝一個字符串到 kobject 的名子成員的代碼, 但是應當避免這個方法. 相反, 使用:
~~~
int kobject_set_name(struct kobject *kobj, const char *format, ...);
~~~
這個函數采用一個 printk 風格的變量參數列表. 不管你信或不信, 對這種操作實際上可能失敗( 他可能試圖分配內存 ); 負責任的代碼應當檢查返回值并且有針對性的相應.
其他的由創建者應當設置的 kobject 成員, 直接或間接, 是 ktype, kset, 和 parent. 我們在本章稍后到這些.
#### 14.1.1.3.?引用計數的操作
一個 kobject 的其中一個關鍵函數是作為一個引用計數器, 給一個它被嵌入的對象. 只要對這個對象的引用存在, 這個對象( 和支持它的代碼) 必須繼續存在. 來操作一個 kobject 的引用計數的低級函數是:
~~~
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
~~~
一個對 kobject_get 的成功調用遞增 kobject 的 引用計數并且返回一個指向 kobject 的指針. 如果, 但是, 這個 kobject 已經在被銷毀的過程中, 這個操作失敗, 并且 kobject_get 返回 NULL. 這個返回值必須總是被測試, 否則可能導致無法結束的令人不愉快的競爭情況.
當一個引用被釋放, 對 kobject_put 的調用遞減引用計數, 并且可能地, 釋放這個對象. 記住 kobject _init 設置這個引用計數為 1; 因此當你創建一個 kobject, 你應當確保對應地采取 kobject_put 調用, 當這個初始化引用不再需要.
注意, 在許多情況下, 在 kobject 自身中的引用計數可能不足以阻止競爭情況. 一個 kobject 的存在( 以及它的包含結構 ) 可能非常, 例如, 需要創建這個 kobject 的模塊的繼續存在. 在這個 kobject 仍然在被傳送時不能卸載那個模塊. 這是為什么我們上面看到的 cdev 結構包含一個 struct module 指針. struct 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_get 使用 try_module_get 來試圖遞增這個模塊的使用計數. 如果這個操作成功, kobject_get 被同樣用來遞增 kobject 的引用計數. 那個操作可能失敗, 當然, 因此這個代碼檢查自 kobject_get 的返回值并且釋放它的對模塊的引用如果事情沒有解決.
#### 14.1.1.4.?釋放函數和 kobject 類型
討論中仍然缺失的一個重要事情是當一個 kobject 的引用計數到 0 時會發生什么. 創建 kobject 的代碼通常不知道什么時候要發生這個情況; 如果它知道, 在第一位使用一個引用計數就沒有意義了. 即便當引入 sysfs 時可預測的對象生命周期變得更加復雜; 用戶空間程序可保持一個對 kobject 的引用( 通過保持一個它的關聯的 sysfs 文件打開 )一段任意的時間.
最后的結果是一個被 kobject 保護的結構無法在任何一個單個的, 可預測的驅動生命周期中的點被釋放, 但是可以在必須準備在 kobject 的引用計數到 0 的任何時刻運行的代碼中. 引用計數不在創建 kobject 的代碼的直接控制之下. 因此這個代碼必須被異步通知, 無論何時對它的 kobject 的最后引用消失.
這個通知由 kobject 的一個釋放函數來完成. 常常地, 這個方法有一個形式如下:
~~~
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);
}
~~~
要強調的重要一點是: 每個 kobject 必須有一個釋放函數, 并且這個 kobject 必須持續( 以一致的狀態 ) 直到這個方法被調用. 如果這些限制不滿足, 代碼就有缺陷. 當這個對象還在使用時被釋放會有風險, 或者在最后引用被返回后無法釋放對象.
有趣的是, 釋放方法沒有存儲在 kobject 自身里面; 相反, 它被關聯到包含 kobject 的結構類型中. 這個類型被跟蹤, 用一個 struct kobj_type 結構類型, 常常簡單地稱為一個 "ktype". 這個結構看來如下:
~~~
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
~~~
在 struct kobj_type 中的 release 成員是, 當然, 一個指向這個 kobject 類型的 release 方法的指針. 我們將回到其他 2 個成員( sysfs_ops 和 default_attrs )在本章后面.
每一個 kobject 需要有一個關聯的 kobj_type 結構. 易混淆地, 指向這個結構的指針能在 2 個不同的地方找到. kobject 結構自身包含一個成員(稱為 ktype)包含這個指針. 但是, 如果這個 kobject 是一個 kset 的成員, kobj_type 指針由 kset 提供. ( 我們將在下一節查看 ksets. ) 其間, 這個宏定義:
~~~
struct kobj_type *get_ktype(struct kobject *kobj); finds the kobj_type pointer for a given kobject.
~~~
### 14.1.2.?kobject 層次, kset, 和子系統
kobject 結構常常用來連接對象到一個層級的結構中, 匹配正被建模的子系統的結構. 有 2 個分開的機制對于這個連接: parent 指針和 ksets.
在結構 kobject 中的 parent 成員是一個指向其他對象的指針 -- 代表在層次中之上的下一級. 如果, 例如, 一個 kobject 表示一個 USB 設備, 它的 parent 指針可能指示這個設備被插入的 hub.
parent 指針的主要用途是在 sysfs 層次中定位對象. 我們將看到這個如何工作, 在"低級 sysfs 操作"一節中.
#### 14.1.2.1.?Ksets 對象
很多情況, 一個 kset 看來象一個 kobj_type 結構的擴展; 一個 kset 是一個嵌入到相同類型結構的 kobject 的集合. 但是, 雖然 struct kobj_type 關注的是一個對象的類型, struct kset 被聚合和集合所關注. 這 2 個概念已被分開以至于一致類型的對象可以出現在不同的集合中.
因此, 一個 kset 的主要功能是容納; 它可被當作頂層的給 kobjects 的容器類. 實際上, 每個 kset 在內部容納它自己的 kobject, 并且它可以, 在許多情況下, 如同一個 kobject 相同的方式被對待. 值得注意的是 ksets 一直在 sysfs 中出現; 一旦一個 kset 已被建立并且加入到系統, 會有一個 sysfs 目錄給它. kobjects 沒有必要在 sysfs 中出現, 但是每個是 kset 成員的 kobject 都出現在那里.
增加一個 kobject 到一個 kset 常常在一個對象創建時完成; 它是一個 2 步的過程. kobject 的 kset 成員必須 ???; 接著kobject 應當被傳遞到:
~~~
int kobject_add(struct kobject *kobj);
~~~
如常, 程序員應當小心這個函數可能失敗(在這個情況下它返回一個負錯誤碼)并且相應地反應. 有一個內核提供的方便函數:
~~~
extern int kobject_register(struct kobject *kobj);
~~~
這個函數僅僅是一個 kobject_init 和 kobject_add 的結合.
當一個 kobject 被傳遞給 kobject_add, 它的引用計數被遞增. kset 中容納的, 畢竟, 是一個對這個對象的引用. 某種意義上, kobject 可能要必須從 kset 中移出來清除這個引用; 完成這個使用:
~~~
void kobject_del(struct kobject *kobj);
~~~
還有一個 kobject_unregister 函數, 是 kobject_del 和 kobject_put 的結合.
一個 kset 保持它的子女在一個標準的內核鏈表中. 在大部分情況下, 被包含的 kobjects 也有指向這個 kset 的指針( 或者, 嚴格地, 它的嵌入 kobject)在它們的 parent 的成員. 因此, 典型地, 一個 kset 和它的 kobjects 看來有些象你在圖 [一個簡單的 kset 層次](# "圖?14.2.?一個簡單的 kset 層次")中所見. 記住:
**圖?14.2.?一個簡單的 kset 層次**

-
圖表中的所有的被包含的 kobjects 實際上被嵌入在一些其他類型中, 甚至可能其他的 ksets.
-
一個 kobject 的 parent 不要求是包含 kset( 盡管任何其他的組織可能是奇怪的和稀少的).
#### 14.1.2.2.?ksets 之上的操作
對于初始化和設置, ksets 有一個接口非常類似于 kobjects. 下列函數存在:
~~~
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_ 函數.
為管理 ksets 的引用計數, 情況大概相同:
~~~
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);
~~~
一個 kset 還有一個名子, 存儲于嵌入的 kobject. 因此, 如果你有一個 kset 稱為 my_set, 你將設置它的名子用:
~~~
kobject_set_name(&my_set->kobj, "The name");
~~~
ksets 還有一個指針( 在 ktye 成員 )指向 kobject_type 結構來描述它包含的 kobject. 這個類型優先于在 kobject 自身中的 ktype 成員. 結果, 在典型的應用中, 在 struct kobject 中的 ktype 成員被留為 NULL, 因為 kset 中的相同成員是實際使用的那個.
最后, 一個 kset 包含一個子系統指針(稱為 subsys). 因此是時候討論子系統了.
#### 14.1.2.3.?子系統
一個子系統是作為一個整體對內核一個高級部分的代表. 子系統常常(但是不是一直)出現在 sysfs 層次的頂級. 一些內核中的例子子系統包括 block_subsys(/sys/block, 給塊設備), devices_subsys(/sys/devices, 核心設備層次), 以及一個特殊子系統給每個內核已知的總線類型. 一個驅動作者幾乎從不需要創建一個新子系統; 如果你想這樣做, 再仔細想想. 你可能需要什么, 最后, 是增加一個新類別, 如同在"類別"一節中描述的.
一個子系統由一個簡單結構代表:
~~~
struct subsystem {
struct kset kset;
struct rw_semaphore rwsem;
};
~~~
一個子系統, 因此, 其實只是一個對 kset 的包裝, 有一個旗標丟在里面.
每個 kset 必須屬于一個子系統. 子系統成員關系幫助建立 kset 的位置在層次中, 但是, 更重要的, 子系統的 rwsem 旗標用來串行化對 kset 的內部鏈表的存取. 這個成員關系由在 struct kset 中的 subsys 指針所表示. 因此, 可以從 kset 的結構找到每個 kset 的包含子系統, 但是卻無法直接從子系統結構發現多個包含在子系統中的 kset.
子系統常常用一個特殊的宏聲明:
~~~
decl_subsys(name, struct kobj_type *type, struct kset_hotplug_ops *hotplug_ops);
~~~
這個宏創建一個 struct subsystem 使用一個給這個宏的名子并后綴以 _subsys 而形成的名子. 這個宏還初始化內部的 kset 使用給定的 type 和 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上.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考