內核驅動模型的初衷
2.6
內核增加了一個引人注目的新特性——統一設備模型
(device
model)
。設備模型提供了一個獨立的機制專門來表示設備,並描述其在系統中的拓撲結構,從而使得系統具有以下優點:
l
代碼重複最小化。
l
提供諸如引用計數這樣的統一機制。
l
可以列舉系統中所有的設備,觀察它們的狀態,並且查看它們連接的總線。
l
可以將系統中的全部設備結構以樹的形式完整、有效的展
現出來——包括所有的總線和內部連接。
l
可以將設備和其對應的驅動聯繫起來,反之亦然。
l
可以將設備按照類型加以歸類,比如分類爲輸入設備,而無需理解物理設備的拓撲結構。
l
可以沿設備樹的葉子向其根的方向依次遍歷,以保證能以正確順序關閉各設備的電源。
最後一點是實現設備模型的最初動機。若想在內核中實現智能的電源管理,就需要來建立表示系統中設備拓撲關係的樹結構。當在樹上端的設備關閉電源時,內核必須首先關閉該設備節點以下的(處於葉子上的)設備電源。比如內核需要先關閉一個
USB
鼠標,然後纔可關閉
USB
控制器;同樣內核也必須在關閉
PCI
總線前先關閉
USB
控制器。簡而言之,若要準確而又高效的完成上述電源管理目標,內核無疑需要一顆設備樹。
( 注:設備模型與電源管理相關聯,貌似匪夷所思,可實際上,一個新觀點或模型出現,在其背後都是需求的刺激。最近以來,節電一直是 Intel 、 IBM 等大公司孜孜追求的目標,虛擬化技術的本質其實就是節能。或者說,在能源日趨緊張的今日,節能是人類共同追求的目標 ) 。
爲什麼驅動模型有助於電源管理,再說兩句:
系統中所有硬件設備由內核全權負責電源管理。例如,在以電池供電的計算機進入“待機”狀態時,內核應立刻強制每個硬件設備(硬盤,顯卡,聲卡,網卡,總 線控制器等等)處於低功率狀態。因此,每個能夠響應“待機”狀態的設備驅動程序必須包含一個回調函數,它能夠使得硬件設備處於低功率狀態。而且,硬件設備
必須按準確的順序進入“待機”狀態,否則一些設備可能會處於錯誤的電源狀態。例如,內核必須首先將硬盤置於“待機”狀態,然後纔是它們的磁盤控制器,因爲 若按照相反的順序執行,磁盤控制器就不能向硬盤發送命令。
追根溯源:
雖然設備模型的初衷是爲了方便電源管理而提供出的一種設備拓撲結構,但是
,
爲了方便調試,設備模型的開發者決定將設備結構樹導出爲一個文件系統,這就是sysfs
文件系統,它可以幫助用戶能以一個簡單文件系統的方式來觀察系統中各種設備的拓撲結構。這個舉措很快被證明是非常明智的,首先
sysfs
代替了先前處於
/proc
下的設備相關文件;另外它爲系統對象提供了一個很有效的視圖。實際上,
sysfs
起初被稱爲
driverfs
。最終
sysfs
使得我們認識到一個全新的對象模型非常有利於系統。今天所有
2.6
內核的系統都擁有
sysfs
文件系統,而且幾乎都毫無例外的將其掛載。
圖一是掛載於
/sys
目錄下的
sysfs
文件系統的局部視圖。
/sys
|
—block/
| |
–fd0
| |
–hda
| |
–dev
| |
–device->../../devices/pci0000:00/0000:00:1f.1/ide0/0.0
|
…
|
–bus/
|
–class/
|
–devices/
|
–firmware/
|
–power/
|
–module/
Sysfs
的根目錄下包含了七個目錄:
block
,
bus
,
class
,
devices
,
firmware
, ,
module
和power
。
block
目錄下的每個子目錄都對應着系統中的一個塊設備。反過來,每個目錄下又都包含了該塊設備的所有分區。bus
目錄提供了一個系統總線視圖。 class
目錄包含了以高層功能邏輯組織起來的系統設備視圖。devices
目錄是系統中設備拓撲結構視圖,它直接映射出了內核中設備結構體的組織層次。 firmware
目錄包含了一些諸如ACPI,EDD,EFI
等低層子系統的特殊樹。power
目錄包含了系統範圍的電源管理數據。
其中最重要的目錄是devices
,該目錄將設備模型導出到用戶空間。目錄結構就是系統中實際的設備拓撲。其它目錄中的很多數據都是將devices
目錄下的數據加以轉換加工而得。比如,/sys/class/net/
目錄是以註冊網絡接口這一高層概念來組織設備關係的,在這個目中可能會有目錄eth0
,它裏面包含的devices
文件其實就是一個指回到devices
下實際設備目錄的符號連接。
隨便看看任何你可訪問到的Linux
系統,這種系統設備視圖相當準確和漂亮,而且可以看到class
中的高層概念與devices
中的低層物理設備,以及bus
中的實際驅動程序之間互相聯絡是非常廣泛的。
軟件設計的根本是把現實世界的事物用計算機世界的模型表示出來,Linux 設備模型的設計採用了面向對象的思想。
在前一講中,提到sysfs 文件系統,Sysfs 文件系統的目標就是要展現設備驅動模型組件之間的層次關係。在Linux 中,sysfs 文件系統被安裝於/sys 目錄下:
mount
-t sysfs sysfs /sys
/sys
|
—block/
| |
–fd0
| |
–hda
| |
–dev
| |
–device->../../devices/pci0000:00/0000:00:1f.1/ide0/0.0
|
…
|
–bus/
|
–class/
|
–devices/
|
–firmware/
|
–power/
|
–module/
那麼,在這樣的目錄樹中,哪些目錄是驅動模型要關注的對象?
bus
-
系統中用於連接設備的總線,在內核中對應的結構體爲
struct
bus_type {
… };
device
-
內核所識別的所有設備,依照連接它們的總線對其進行組織,
對應的結構體爲
struct
device {
… };
class
-
系統中設備的類型(聲卡,網卡,顯卡,輸入設備等),同一類中包含的設備可能連接到不同的總線,
對應的結構體爲
struct class {
… };
爲什麼不對Power
進行單獨描述?實際上,Power
與device
有關,它只是device
中的一個字段。
除此之外,立馬閃現在我們腦子裏的對象還有:
driver
-
在內核中註冊的設備驅動程序,
對應的結構體爲
struct device_driver{
… };
以上bus,device,class,driver
,是可以感受到的對象,在內核中都用相應的結構體來描述。而實際上,按照面向對象的思想,我們需要抽象出一個最基本的對象,這就是設備模型的核心對象kobject.
Kobject
是Linux 2.6
引入的新的設備管理機制,在內核中就是一個struct kobject
結構體。有了這個數據結構,內核中所有設備在底層都具有統一的接口,kobject
提供基本的對象管理,是構成Linux2.6
設備模型的核心結構,它與sysfs
文件系統緊密關聯,每個在內核中註冊的kobject
對象都對應於sysfs
文件系統中的一個目錄。Kobject
是組成設備模型的基本結構。類似於C++
中的基類,它嵌入於更大的對象中,即所謂的容器,如上面提到的
bus,class
,devices, drivers
都是典型的容器,它們是描述設備模型的組件。
下一講,將圍繞
kobject
展開。
話說
kboject
是驅動模型的核心對象,但在
sysfs
文件系統中似乎並沒有對應的項,而這種看似“無”,實際上蘊藏着“有”。
這“有”從何說起。回想文件系統中的核心對象“索引節點(
indoe
)”和目錄項“
dentry
”
:
Inode
—
與文件系統中的一個文件相對應(而實際上,只有文件被訪問時,纔在內存創建索引節點)。
Dentry
-
每個路徑中的一個分量,例如路徑
/bin/ls
,其中
/
、
bin
和
ls
三個都是目錄項,只是前兩個是目錄,而最後一個是普通文件。也就是說,目錄項目錄項或者是一子目錄,或者是一個文件。
從上面的定義可以看出,
indoe
和
dentry
誰的包容性更大?當然是
dentry
!
那麼,
kobject
與
dentry
有何關係?由此我們可以推想,把
dentry
作爲
kobject
中的一個字段,恍然間,
kobject
變得強大起來了。何謂“強大“,因爲這樣以來,就可以方便地將
kobject
映射到一個
dentry
上,也就是說,
kobject
與
/sys
下的任何一個目錄或文件相對應了,進一步說,把
kobject
導出形成文件系統就變得如同在內存中構建目錄項一樣簡單。由此可知,
kobject
其實已經形成一棵樹了。這就是以隱藏在背後的對象模型爲橋樑,將驅動模型和
sysfs
文件系統全然聯繫起來。由於
kobjec
t
被映射到目錄項,同時對象模型層次結構也已經在內存形成一個樹,因此
sysfs
的形成就水到渠成了。
既然
,
kobject
要形成一顆樹,那麼其中的字段就要有
parent
,以表示樹的層次關係;另外,
kobject
不能是無名氏,得有
name
字段,按說,目錄或文件名並不會很長,但是,
sysfs
文件系統爲了表示對象之間複雜的關係,需要通過軟鏈接達到,而軟鏈接常常有較長的名字,通過以上的分析,目前可以得知
kobject
對象包含的字段有:
struct kobject {
char *k_name; /*
長名字*/
char
name[kOBJ_NAME _LEN]; /*
短名字*/
struct
kobject *parent; /*
表示對象的層次關係*/
struct
dentry *dentry; /*
表示sysfs
中的一個目錄項 */};
分析到這裏,似乎已經知道
kobject
說包含的字段了,但且慢,查看
kobject.h
頭文件,看到它還包含以下字段:
struct kobject { struct kref
kref;
struct list_head
entry;
struct kset
*kset;
struct kobj_type
*ktype;};
這四個字段,每一個都是結構體,其中struct
list_head
是內核中形成雙向鏈表的基本結構,而其他三個結構體存在的理由是什麼?
1.
引用計數kref
kobject
的主要功能之一就是爲我們提供了一個統一的引用計數系統,爲什麼說它具有“統一”的能力?那是因爲
kobject
是“基”對象,就像大廈的基地,其他對象(如
devic,bus,class,device_driver
等容器)都將其包含,以後,其他對象的引用技術繼承或封裝
kobject
的引用技術就可以了。
初始化時,
kobject
的引用計數設置爲
1
。只要引用計數不爲零,那麼該對象就會繼續保留在內存中,也可以說是被“釘住”了。任何包含對象引用的代碼首先要增加該對象的引用計數,當代碼結束後則減少它的引用計數。增加引用計數稱爲獲得(
getting
)對象的引用,減少引用計數稱爲釋放
(putting)
對象的引用。當引用計數跌到零時,對象便可以被銷燬,同時相關內存也都被釋放。
增加一個引用計數可通過
koject_get
()函數完成:
struct kobject * kobject_get(struct kobject *kobj);
該函數正常情況下將返回一個指向
kobject
的指針,如果失敗則返回
NULL
指針;
減少引用計數通過
kobject_put()
完成:
void kobject_put(struct kobject *kobj);
如果對應的kobject
的引用計數減少到零,則與該kobject
關聯的ktype
中的析構函數將被調用。
我們深入到引用計數系統的內部去看,會發現
kobject
的引用計數是通過
kref
結構體實現的,該結構體定義在頭文件<linux/kref.h
>
中:
struct kref { atomic_t refcount;};
其中唯一的字段是用來存放引用計數的原子變量。那爲什麼採用結構體?這是爲了便於進行類型檢測。在使用
kref
前,必須先通過kref_init()
函數來初始化它:
void kref_init(struct kref *kref){
atomic_set(&kref->refcount, 1);}
正如你所看到的,這個函數簡單的將原子變量置
1
,所以
kref
一但被初始化,它表示的引用計數便固定爲
1
。
開發者現在不必在內核代碼中利用atmoic_t
類型來實現其自己的引用計數。對開發者而言,在內核代碼中最好的方法是利用kref
類型和它相應的輔助函數,爲自己提供一個通用的、正確的引用計數機制。
上述的所有函數定義與聲明分別在在文件lib/kref.c
和文件<linux/kref.h>
中。
2.
共同特性的ktype
如上所述,kobject 是一個抽象而基本的對象。對於一族具有共同特性的 kobject ,就是用ktype 來描述:
struct kobj_type {
void
(*release)(struct kobject *);
struct sysfs_ops
*sysfs_ops;
struct attribute
**default_attrs;};
定義於頭文件<linux/kobject.h>
中。
release
指針指向在
kobject
引用計數減至零時要被調用的析構函數。該函數負責釋放所有
kobject
使用的內存和其它相關清理工作。
sysfs_ops
變量指向sysfs_ops
結構體,其中包含兩個函數,也就是對屬性進行操作的讀寫函數show()
和store
()。
最後,default_attrs
指向一個attribute
結構體數組。這些結構體定義了kobject
相關的默認屬性
。屬性描述了給定對象的特徵,其實,屬性就是對應/sys
樹形結構中的葉子結點,也就是文件。
3.
對象集合體kset
Kset
,顧名思義就是
kobject
對象的集合體,可以把它看成是一個容器,可將所有相關的
kobject
對象聚集起來,比如“全部的塊設備”就是一個
kset
。聽起來
kset
與
ktypes
非常類似,好像沒有多少實質內容。那麼“爲什麼會需要這兩個類似的東西呢”。
ksets
可把
kobject
集中到一個集合中,而
ktype
描述相關類型
kobject
所共有的特性,它們之間的重要區別在於:具有相同
ktype
的
kobject
可以被分組到不同的
ksets
。
kobject
的
kset
指針指向相應的
kset
集合。
kset
集合由
kset
結構體表示,定義於頭文件<linux/kobject.h>
中:
struct kset {
struct kobj_type
*ktype;
struct list_head
list;
struct kobject
kobj;
struct
kset_uevent_ops
*
uevent_ops
;};
其中
ktype
指針指向集合(
kset
)中
kobject
對象的類型(
ktype
),
list
連接該集合(
kset
)中所有的
kobject
對象。
kobj
指向的
koject
對象代表了該集合的基類,uevent_ops
指向一個用於處理集合中
kobject
對象的熱插拔操作的結構體。
總結:
kobject
通常是嵌入到其它結構中的,其單獨意義其實並不大。相反,那些更爲重要的結構體,比如在struct cdev
中才真正需要用到kobject
結構。
/* cdev structure
– 該對象代表一個字符設備 */
struct cdev
{ struct kobject
kobj;
struct module
*owner;
struct file_operations *ops;
struct list_head
list;
dev_t
dev;
unsigned int
count;};
當kobject
被嵌入到其它結構中時,該結構便擁有了kobject
提供的標準功能。更重要的一點是,嵌入kobject
的結構體可以成爲對象層
次架構中的一部分。比如cdev
結構體就可通過其父指針cdev->kobj->parent
和鏈表 cdev->kobj->entry
來插入到對象層次結構中。
參考
LDK
,
ULK
以及
lxr.linux.no