Linux裝置驅動模型概述
2.6核心增加了一個引人注目的新特性——統一裝置模型(device model)。裝置模型提供了一個獨立的機制專門來表示裝置,並描述其在系統中的拓撲結構,從而使得系統具有以下優點:
(1)程式碼重複最小化。
(2)提供諸如引用計數這樣的統一機制。
(3)可以列舉系統中所有的裝置,觀察它們的狀態,並且檢視它們連線的匯流排。
(4)可以將系統中的全部裝置結構以樹的形式完整、有效的展現出來——包括所有的匯流排和內部連線。
(5)可以將裝置和其對應的驅動聯絡起來,反之亦然。
(6)可以將裝置按照型別加以歸類,比如分類為輸入裝置,而無需理解物理裝置的拓撲結構。
(7)可以沿裝置樹的葉子向其根的方向依次遍歷
,以保證能以正確順序關閉各裝置的電源。
最後一點是實現裝置模型的最初動機,若想在核心中實現智慧的電源管理,就需要來建立表示系統中裝置拓撲關係的樹結構。當在樹上端的裝置關閉電源時,核心必須首先關閉該裝置節點以下的(處於葉子上的)裝置電源。比如核心需要先關閉一個USB滑鼠,然後才可關閉USB控制器;同樣核心也必須在關閉PCI匯流排前先關閉USB控制器。簡而言之,若要準確而又高效的完成上述電源管理目標,核心無疑需要一顆裝置樹。
(注:裝置模型與電源管理相關聯,貌似匪夷所思,可實際上,一個新觀點或模型出現,在其背後都是需求的刺激。最近以來,節電一直是Intel、IBM等大公司孜孜追求的目標,虛擬化技術的本質其實就是節能。或者說,在能源日趨緊張的今日,節能是人類共同追求的目標
為什麼驅動模型有助於電源管理,再說兩句:
系統中所有硬體裝置由核心全權負責電源管理。例如,在以電池供電的計算機進入“待機”狀態時,核心應立刻強制每個硬體裝置(硬碟,顯示卡,音效卡,網絡卡,匯流排控制器等等)處於低功率狀態。因此,每個能夠響應“待機”狀態的裝置驅動程式必須包含一個回撥函式,它能夠使得硬體裝置處於低功率狀態。而且,硬體裝置 必須按準確的順序進入“待機”狀態,否則一些裝置可能會處於錯誤的電源狀態。例如,核心必須首先將硬碟置於“待機”狀態,然後才是它們的磁碟控制器,因為若按照相反的順序執行,磁碟控制器就不能向硬碟傳送命令。
雖然裝置模型的初衷是為了方便電源管理而提供出的一種裝置拓撲結構
圖一是掛載於/sys目錄下的sysfs檔案系統的區域性檢視。
> mount -t sysfs sysfs /sys
/sys
|—block/
| |–fd0
| |–had
| |–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。
(1)block目錄下的每個子目錄都對應著系統中的一個塊裝置。反過來,每個目錄下又都包含了該塊裝置的所有分割槽。
(2)bus目錄提供了一個系統匯流排檢視。
(3)class目錄包含了以高層功能邏輯組織起來的系統裝置檢視。
(4)devices目錄是系統中裝置拓撲結構檢視,它直接映射出了核心中裝置結構體的組織層次。
(5)firmware目錄包含了一些諸如ACPI,EDD,EFI等低層子系統的特殊樹。
(6)power目錄包含了系統範圍的電源管理資料。
(7)module目錄包含了核心模組。模組是一種向Linux核心新增裝置驅動程式、檔案系統及其他元件的有效方法。
其中最重要的目錄是devices,該目錄將裝置模型匯出到使用者空間。目錄結構就是系統中實際的裝置拓撲。其它目錄中的很多資料都是將devices目錄下的資料加以轉換加工而得。比如,/sys/class/net/目錄是以註冊網路介面這一高層概念來組織裝置關係的,在這個目中可能會有目錄eth0,它裡面包含的devices檔案其實就是一個指回到devices下實際裝置目錄的符號連線。
隨便看看任何你可訪問到的Linux系統,這種系統裝置檢視相當準確和漂亮,而且可以看到class中的高層概念與devices中的低層物理裝置,以及bus中的實際驅動程式之間互相聯絡是非常廣泛的。
軟體設計的根本是把現實世界的事物用計算機世界的模型表示出來,Linux裝置模型的設計採用了面向物件(Object Oriented)的思想。
在前一講中,提到sysfs檔案系統,sysfs檔案系統的目標就是要展現裝置驅動模型元件之間的層次關係。在Linux中,sysfs檔案系統被安裝於/sys目錄下,見上圖一。那麼,在這樣的目錄樹中,哪些目錄是驅動模型要關注的物件?
bus-系統中用於連線裝置的匯流排,在核心中對應的結構體為structbus_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++中的基類,好比MFC中的CObject、Java中的Object和COM中的IUnKnown。kobject嵌入於更大的物件中,即所謂的容器,如上面提到的bus,class,devices,drivers都是典型的容器,它們是描述裝置模型的元件。
話說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檔案系統全然聯絡起來。由於kobject被對映到目錄項,同時物件模型層次結構也已經在記憶體形成一個樹,因此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;
};
這四個欄位,每一個都是結構體,其中structlist_head是核心中形成雙向連結串列的基本結點結構,而其他三個結構體存在的理由是什麼?
(1)引用計數kref
kobject的主要功能之一就是為我們提供了一個統一的引用計數系統,為什麼說它具有“統一”的能力?那是因為kobject是“基”物件,就像大廈的基地,其他物件(如devic,bus,class,device_driver等容器)都將其包含,以後,其他物件的引用技術繼承或封裝kobject的引用技術就可以了。
初始化時,kobject的引用計數設定為1。只要引用計數不為零,那麼該物件就會繼續保留在記憶體中,也可以說是被“釘住”了。任何包含物件引用的程式碼首先要增加該物件的引用計數,當代碼結束後則減少它的引用計數。增加引用計數稱為獲得(getting)物件的引用,減少引用計數稱為釋放(putting)物件的引用。當引用計數跌到零時,物件便可以被銷燬,同時相關記憶體也都被釋放。COM中的IUnKnown亦實現了AddRef()/Release()引用計數介面。
增加一個引用計數可通過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,就是用定義在標頭檔案<linux/kobject.h>中的ktype來描述:
struct kobj_type {
void (*release)(structkobject *);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};
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指向一個用於處理集合中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。