Linux裝置驅動程式學習(12) -Linux裝置模型(底層原理簡介)
以《LDD3》的說法:Linux裝置模型這部分內容可以認為是高階教材,對於多數程式作者來說是不必要的。但是我個人認為:對於一個嵌入式Linux的底層程式設計師來說,這部分內容是很重要的。 以我學習的ARM9為例,有很多匯流排(如SPI、IIC、IIS等等)在Linux下已經被編寫成了子系統,無需自己寫驅動;而這些匯流排又不像PCI、USB等在《LDD3》上有教程,有時還要自己研究它的子系統構架,甚至要自己新增一個新的匯流排型別。
對於這方面的學習,我推薦幾個網頁,這些也是我這部分文章的參考資料:
(1)《 Linux
那些事兒 之 我是Sysfs
》來源於復旦和交大三個牛人的Linux技術部落格:
(復旦_abc)他們還分析了很多Linux的驅動,值得珍藏!
(4)luofuchong的部落格 ,此人分析了一些2410中的Linux子系統(如SPI,input等),實力不凡,值得關注。網址:http://www.cnitblog.com/luofuchong/
在這部分的學習中,將會先研究linux裝置模型的每個元素,最後將其一 步一步整合,至底向上地分析。一開始會比較摸不著頭腦,到了整合階段就柳暗花明了。我之所以沒有先介紹整體,再分析每個部分是因為如果不對每個元素做認真 分析,看了整體也會雲裡霧裡(我試過了,恕小生愚鈍)。所以一開始要耐著性子看,到整合階段就會豁然開朗。
Linux裝置模型的目的是: 為核心建立起一個統一的裝置模型,從而有一個對系統結構的一般性抽象描述。
現在核心使用裝置模型支援多種不同的任務:
電源管理和系統關機
:這些需要對系統結構的理解,裝置模型使OS能以正確順序遍歷系統硬體。
與使用者空間的通訊 :sysfs 虛擬檔案系統的實現與裝置模型的緊密相關, 並向外界展示它所表述的結構。向用戶空間提供系統資訊、改變操作引數的介面正越來越多地通過 sysfs , 也就是裝置模型來完成。
熱插拔裝置
裝置型別:裝置模型包括了將裝置分類的機制,在一個更高的功能層上描述這些裝置, 並使裝置對使用者空間可見。
物件生命週期 :裝置模型的實現需要建立一系列機制來處理物件的生命週期、物件間的關係和物件在使用者空間的表示。
Linux 裝置模型是一個複雜的資料結構。但對模型的大部分來說, Linux 裝置模型程式碼會處理好這些關係, 而不是把他們強加於驅動作者。模型隱藏於互動的背後,與裝置模型的直接互動通常由匯流排級的邏輯和其他的核心子系統處理。所以許多驅動作者可完全忽略裝置模 型, 並相信裝置模型能處理好他所負責的事。
Kobject、Kset 和 Subsystem
Kobjects
kobject是一種資料結構,定義在 <linux/kobject.h> 。
|
kobject 是組成裝置模型的基本結構,初始它只被作為一個簡單的引用計數, 但隨時間的推移,其任務越來越多。現在kobject 所處理的任務和支援程式碼包括:
物件的引用計數 :跟蹤物件生命週期的一種方法是使用引用計數。當沒有核心程式碼持有該物件的引用時, 該物件將結束自己的有效生命期並可被刪除。
sysfs 表述 :在 sysfs 中出現的每個物件都對應一個 kobject, 它和核心互動來建立它的可見表述。
資料結構關聯 :整體來看, 裝置模型是一個極端複雜的資料結構,通過其間的大量連結而構成一個多層次的體系結構。kobject 實現了該結構並將其聚合在一起。
熱插拔事件處理 :kobject 子系統將產生的熱插拔事件通知使用者空間。
一個kobject對自身並不感興趣,它存在的意義在於把高階物件連線到裝置模型上。因此核心程式碼很少(甚至不知道)創 建一個單獨的 kobject;而kobject 被用來控制對大型域(domain)相關物件的訪問,所以kobject 被嵌入到其他結構中。kobject 可被看作一個最頂層的基類,其他類都它的派生產物。 kobject 實現了一系列方法,對自身並沒有特殊作用,而對其他物件卻非常有效。
對於給定的kobject指標,可使用container_of 巨集得到包含它的結構體的指標。
kobject 初始化kobject的初始化較為複雜,但是必須的步驟 如下:
(1)將整個kobject清零,通常使用memset函式。
(2)呼叫kobject_init()函式,設定結構內部一些成員。所做的一件事情是設定kobject的引用計數為1。具體的原始碼如下:
|
(3)設定kobject的名字
|
(4)直接或間接設定其它成員:ktype、kset和parent。 (重要)
對引用計數的操作
kobject 的一個重要函式是為包含它的結構設定引用計數。只要對這個物件的引用計數存在, 這個物件( 和支援它的程式碼) 必須繼續存在。底層控制 kobject 的引用計數的函式有:
|
注意:kobject _init 設定這個引用計數為 1,因此建立一個 kobject時, 當這個初始化引用不再需要,應當確保採取 kobject_put 呼叫。同理:struct cdev 的引用計數實現如下:
|
建立一個對 cdev 結構的引用時,還需要建立包含它的模組的引用。因此, cdev_get 使用 try_module_get 來試圖遞增這個模組的使引用計數。如果這個操作成功, kobject_get 被同樣用來遞增 kobject 的引用計數。kobject_get 可能失敗, 因此這個程式碼檢查 kobject_get 的返回值,如果呼叫失敗,則釋放它的對模組的引用計數。
release 函式和 kobject 型別
引用計數不由建立 kobject 的程式碼直接控制,當 kobject 的最後引用計數消失時,必須非同步通知,而後kobject中ktype所指向的kobj_type結構體包含的release函式會被呼叫。通常原型如下:
|
每個 kobject 必須有一個release函式, 並且這個 kobject 必須在release函式被呼叫前保持不變( 穩定狀態 ) 。這樣,每一個 kobject 需要有一個關聯的 kobj_type 結構,指向這個結構的指標能在 2 個不同的地方找到:
(1)kobject 結構自身包含一個成員(ktype)指向kobj_type ;
(2)如果這個 kobject 是一個 kset 的成員, kset 會提供kobj_type 指標。
|
以下巨集用以查詢指定kobject的kobj_type 指標:
|
這個函式其實就是從以上提到的這兩個地方返回kobj_type指標,原始碼如下:
|
kobject 層次結構、kset 和子系統
核心通常用kobject 結構將各個物件連線起來組成一個分層的結構體系,與模型化的子系統相匹配。有 2 個獨立的機制用於連線: parent 指標和 kset 。
parent 是指向另外一個kobject 結構(分層結構中上一層的節點)的指標,主要用途是在 sysfs 層次中定位物件.
kset
kset 象 kobj_type 結構的擴充套件; 一個 kset 是嵌入到相同型別結構的 kobject 的集合。但 struct kobj_type 關注的是物件的型別,而struct kset 關心的是物件的聚合和集合,其主要功能是包容,可認為是kobjects 的頂層容器類。每個 kset 在內部包含自己的 kobject, 並可以用多種處理kobject 的方法處理kset。 ksets 總是在 sysfs 中出現; 一旦設定了 kset 並把它新增到系統中, 將在 sysfs 中建立一個目錄;kobjects 不必在 sysfs 中表示, 但kset中的每一個 kobject 成員都在sysfs中得到表述。
增加 kobject 到 kset 中去,通常是在kobject 建立時完成,其過程分為2步:
(1)完成kobject的初始化,特別注意mane和parent和初始化。
(2)把kobject 的 kset 成員指向目標kset。
(3)將kobject 傳遞給下面的函式:
|
核心提供了一個組合函式:
|
|
kset 在一個標準的核心連結串列中儲存了它的子節點,在大部分情況下, 被包含的 kobjects 在它們的 parent 成員中儲存指向 kset內嵌的 kobject的指標,關係如下:
圖表中的所有的被包含的 kobjects 實際上被嵌入在一些其他型別中, 甚至可能其他的 kset。
kset 上的操作
ksets 有類似於kobjects初始化和設定介面:
|
ksets 還有一個指標指向 kobj_type 結構來描述它包含的 kobject,這個型別優先於 kobject 自身中的 ktype
。因此在典型的應用中, 在 struct kobject 中的 ktype 成員被設為 NULL, 而 kset
中的ktype是實際被使用的。
在新的核心裡, kset 不再包含一個子系統指標struct subsystem * subsys, 而且subsystem已經被kset取代。
子系統
子系統是對整個核心中一些高階部分的表述。子系統通常(但不一定)出現在 sysfs分層結構中的頂層,核心子系統包括 block_subsys(/sys/block 塊裝置)、 devices_subsys(/sys/devices 核心裝置層)以及核心已知的用於各種匯流排的特定子系統。
對於新的核心已經不再有 subsystem資料結構 了,用kset代替了。每個 kset 必須屬於一個子系統,子系統成員幫助核心在分層結構中定位 kset 。
|
底層sysfs操作
kobject 是在 sysfs 虛擬檔案系統後的機制。對每個在 sysfs 中的目錄
, 在核心中都會有一個 kobject 與之對應。每個 kobject 都輸出一個或多個屬性, 它在 kobject 的 sysfs 目錄中以檔案
的形式出現, 其中的內容由核心產生。 <linux/sysfs.h>
包含 sysfs 的工作程式碼。
在 sysfs 中建立kobject的入口是kobject_add的工作的一部分,只要呼叫 kobject_add 就會在sysfs 中顯示,還有些知識值得記住:
(1)kobjects 的 sysfs 入口始終為目錄, kobject_add 的呼叫將在sysfs 中建立一個目錄,這個目錄包含一個或多個屬性(檔案);
(2)
分配給 kobject 的名字( 用 kobject_set_name ) 是 sysfs 中的目錄名,出現在 sysfs 層次的相同部分的
kobjects 必須有唯一的名字. 分配給 kobjects 的名字也應當是合法的檔名字:
它們不能包含非法字元(如:斜線)且不推薦使用空白。
(3)sysfs 入口位置對應 kobject 的 parent 指標。若
parent 是 NULL ,則它被設定為嵌入到新 kobject 的 kset 中的 kobject;若 parent 和 kset 都是
NULL, 則sysfs 入口目錄在頂層,通常不推薦。
當建立kobject 時, 每個 kobject 都被給定一系列預設屬性。這些屬性儲存在 kobj_type 結構中:
|
sysfs 讀寫這些屬性是由 kobj_type->sysfs_ops 成員中的函式完成的:
|
當用戶空間讀取一個屬性時,核心會使用指向 kobject 的指標(kobj)和正確的屬性結構(*attr)來呼叫show 方法,該方法將給定屬性值編碼進緩衝(buffer)(注意不要越界( PAGE_SIZE 位元組)), 並返回實際資料長度。sysfs 的約定要求每個屬性應當包含一個單個人眼可讀值; 若返回大量資訊,需將它分為多個屬性.
也可對所有 kobject 關聯的屬性使用同一個 show 方法,用傳遞到函式的 attr 指標來判斷所請求的屬性。有的 show 方法包含對屬性名字的檢查。有的show 方法會將屬性結構嵌入另一個結構, 這個結構包含需要返回屬性值的資訊,這時可用container_of 獲得上層結構的指標以返回屬性值的資訊。
store 方法將存在緩衝(buffer)的資料( size 為資料的長度,不能超過 PAGE_SIZE )解碼並儲存新值到屬性(*attr), 返回實際解碼的位元組數。store 方法只在擁有屬性的寫許可權時才能被呼叫。此時注意:接收來自使用者空間的資料一定要驗證其合法性。如果到資料不匹配, 返回一個負的錯誤值。
非預設屬性
雖然 kobject 型別的 default_attrs 成員描述了所有的 kobject 會擁有的屬性,倘若想新增新屬性到 kobject 的 sysfs 目錄屬性只需簡單地填充一個attribute結構並傳遞到以下函式:
|
若要刪除屬性,呼叫:
|
二進位制屬性
sysfs 通常要求所有屬性都只包含一個可讀文字格式的值,很少需要建立能夠處理大量二進位制資料的屬性。但當在使用者空間和裝置間傳遞不可改變的資料時(如上傳韌體到裝置 )就需要這個特性。二進位制屬性使用一個 bin_attribute 結構來描述:
|
符號連結
sysfs 檔案系統具有樹型結構, 反映 kobject之間的組織層次關係。為了表示驅動程式和所管理的裝置間的關係,需要額外的指標,其在 sysfs 中通過符號連結實現。
|
熱插拔事件產生
一個熱插拔事件是一個從核心空間傳送到使用者空間的通知, 表明系統配置已經改變. 無論 kobject
被建立或刪除,都會產生這種事件。熱插拔事件會導致對 /sbin/hotplug 的呼叫, 它通過載入驅動程式, 建立裝置節點,
掛載分割槽或其他正確動作響應事件。
熱插拔事件的實際控制是通過一套儲存於 kset_uevent_ops (《LDD3》中介紹的struct kset_hotplug_ops * hotplug_ops;在2.6.22.2中已經被kset_uevent_ops 結構體替換
)
結構的方法完成:
|
可以在 kset 結構的uevent_ops 成員中找到指向kset_uevent_ops結構的指標。
若在 kobject 中不包含指定的 kset , 核心將通過 parent 指標在分層結構中進行搜尋,直到發現一個包含有kset的 kobject ; 接著使用這個 kset 的熱插拔操作。
kset_uevent_ops 結構中的三個方法作用如下:(1) filter 函式讓 kset 程式碼決定是否將事件傳遞給使用者空間。如果 filter 返回 0,將不產生事件。以磁碟的 filter 函式為例,它只允許kobject產生磁碟和分割槽的事件,原始碼如下:
|
(2) 當呼叫使用者空間的熱插拔程式時,相關子系統的名字將作為唯一的引數傳遞給它。name 函式負責返回合適的字串傳遞給使用者空間的熱插拔程式。
(3)熱插拔指令碼想得到的任何其他引數都通過環境變數傳遞。uevent 函式的作用是在呼叫熱插拔指令碼之前將引數新增到環境變數中。函式原型:
|
熱插拔事件的產生通常是由在匯流排驅動程式層的邏輯所控制。
以上是Linux裝置模型的底層原理簡介,具體的細節應該參閱核心原始碼和《ULK3》。