Linux裝置模型-1-主要概念
0 linux裝置模型出現的背景
隨著計算機的周邊外設越來越豐富,裝置管理已經成為現代作業系統的一項重要任務,這對於Linux來說也是同樣的情況。每次Linux核心新版本的釋出,都會伴隨著一批裝置驅動進入核心。在Linux核心裡,驅動程式的程式碼量佔有了相當大的比重。
為了降低裝置多樣性帶來的Linux驅動開發的複雜度,以及裝置熱拔插處理、電源管理等,Linux核心提出了裝置模型(也稱作Driver Model)的概念。裝置模型將硬體裝置歸納、分類,然後抽象出一套標準的資料結構和介面。驅動的開發,就簡化為對核心所規定的資料結構的填充和實現。
本文將會從裝置模型的基本概念開始,通過分析核心相應的程式碼,一步一步解析Linux
1 linux裝置模型的基本概念
1.1 Bus, Class, Device和Device Driver的概念
下圖是嵌入式系統常見的硬體拓撲的一個示例:
硬體拓撲描述Linux裝置模型中四個重要概念中三個:Bus,Class和Device(第四個為Device Driver,後面會說)。
Bus(匯流排):Linux認為(可以參考include/linux/device.h中struct bus_type的註釋),匯流排是CPU和一個或多個裝置之間資訊互動的通道。而為了方便裝置模型的抽象,所有的裝置都應連線到總線上(無論是CPU內部匯流排、虛擬的匯流排還是“platform Bus”)。
Class(分類):在Linux裝置模型中,Class的概念非常類似面向物件程式設計中的Class(類),它主要是集合具有相似功能或屬性的裝置, 這樣就可以抽象出一套可以在多個裝置之間共用的資料結構和介面函式。因而從屬於相同Class的裝置的驅動程式,就不再需要重複定義這些公共資源,直接從 Class中繼承即可。
Device(裝置):抽象系統中所有的硬體裝置,描述它的名字、屬性、從屬的Bus、從屬的Class等資訊。
Device Driver(驅動):Linux裝置模型用Driver抽象硬體裝置的驅動程式,它包含裝置初始化、電源管理相關的介面實現。而Linux核心中的驅動開發,基本都圍繞該抽象進行(實現所規定的介面函式)。
注:什麼是Platform Bus? 在計算機中有這樣一類裝置,它們通過各自的裝置控制器,直接和CPU連線,CPU可以通過常規的定址操作訪問它們(或者說訪問它們的控制器)。這種連線方式,並不屬於傳統意義上的匯流排連線。但裝置模型應該具備普適性,因此Linux就虛構了一條Platform Bus,供這些裝置掛靠。
1.2 裝置模型的核心思想
Linux裝置模型的核心思想是(通過xxx手段,實現xxx目的):
1. 用Device(struct device)和Device Driver(struct device_driver)兩個資料結構,分別從“有什麼用”和“怎麼用”兩個角度描述硬體裝置。這樣就統一了編寫裝置驅動的格式,使驅動開發從論述題 變為填空體,從而簡化了裝置驅動的開發。
2. 同樣使用Device和Device Driver兩個資料結構,實現硬體裝置的即插即用(熱拔插)。
在Linux核心中,只要任何Device和Device Driver具有相同的名字,核心就會執行Device Driver結構中的初始化函式(probe),該函式會初始化裝置,使其為可用狀態。
而對大多數熱拔插裝置而言,它們的Device Driver一直存在核心中。當裝置沒有插入時,其Device結構不存在,因而其Driver也就不執行初始化操作。當裝置插入時,核心會建立一個 Device結構(名稱和Driver相同),此時就會觸發Driver的執行。這就是即插即用的概念。
3. 通過"Bus-->Device”型別的樹狀結構(見2.1章節的圖例)解決裝置之間的依賴,而這種依賴在開關機、電源管理等過程中尤為重要。
試想,一個裝置掛載在一條總線上,要啟動這個裝置,必須先啟動它所掛載的匯流排。很顯然,如果系統中裝置非常多、依賴關係非常複雜的時候,無論是核心還是驅動的開發人員,都無力維護這種關係。
裝置模型中的這種樹狀結構,可以自動處理這種依賴關係。啟動某一個裝置前,核心會檢查該裝置是否依賴其它裝置或者匯流排,如果依賴,則檢查所依賴的物件是否已經啟動,如果沒有,則會先啟動它們,直到啟動該裝置的條件具備為止。而驅動開發人員需要做的,就是在編寫裝置驅動時,告知核心該裝置的依賴關係即可。
4. 使用Class結構,在裝置模型中引入面向物件的概念,這樣可以最大限度地抽象共性,減少驅動開發過程中的重複勞動,降低工作量。
2 使用者和核心裝置模型之間的橋樑
由《第一章 linux裝置模型的基本概念》可知,Linux裝置模型的核心是使用Bus、Class、Device、Driver四個核心資料結構,將大量的、不同功能的硬體裝置(以及驅動該硬體裝置的方法),以樹狀結構的形式,進行歸納、抽象,從而方便Kernel的統一管理。
當然,Linux裝置模型給我們帶來的便利遠不止如此。既然已經建立了一個組織所有裝置和驅動的樹狀結構,使用者就可以通過這棵樹去遍歷所有的裝置,建立裝置和驅動程式之間的聯絡,根據型別不同也可以對裝置進行歸類,這樣就可以更清晰的去“看”這顆枝繁葉茂的大樹。另外,Linux驅動模型把很多裝置共有的一些操作抽象出來,大大減少了重複造輪子的可能。同時Linux裝置模型提供了一些輔助的機制,比如引用計數,讓開發者可以安全高效的開發驅動程式。達成了以上這些好處之後,我們還得到了一個非常方便的副產品,這就是sysfs----一個虛擬的檔案系統。sysfs給使用者提供了一個從使用者空間去訪問核心裝置的方法,它在Linux裡的路徑是/sys。這個目錄並不是儲存在硬碟上的真實的檔案系統,只有在系統啟動之後才會建起來。
下面這個命令可以用來顯示sysfs的大致結構:
$:tree /sys
這個命令的資訊量非常大,我就不貼出來了,如果有興趣的話可以看看這裡,或者自己動手實驗一下。
我們來看看第一層目錄結構:
/sys
|-- block
|-- bus
|-- class
|-- dev
|-- devices
|-- firmware
|-- fs
|-- kernel
|-- module
`-- power
這裡有10個子目錄,但並不是說這10個目錄代表了10種完全不同的裝置型別,實際上這些目錄只是給我們提供瞭如何去看整個裝置模型的不同的視角。其實從不同的目錄出發都有可能找到同一個裝置的。那真正的裝置資訊到底放在哪裡呢?看看目錄的名稱就應該能猜到,對,就是devices子目錄,Linux的所有裝置都可以在這個目錄裡找到。這裡是一個大雜燴,雖然五臟俱全但我們卻無從下手。這裡還是以U盤為例,插上U盤之後,在devices目錄裡如何找到這支U盤呢?真得很難辦到。但是如果知道這個U盤在系統裡的裝置檔名(暫且假設為sdb),那就可以從block目錄著手。
透過block目錄,我們很容易就可以找到這個U盤裝置,符號連結device正是指向devices目錄下的位置。
到這裡,我們總結一下/sys目錄下各個子目錄的作用。block目錄是從塊裝置的角度來組織裝置;bus目錄是從系統匯流排這個角度來組織裝置,比如PCI匯流排或者USB匯流排;class目錄把看問題的視角提高到了類別的高度,比如PCI裝置或者USB裝置等;dev目錄的視角是裝置節點;devices目錄在前面提到了,這裡是所有裝置的大本營;firmware目錄包含了一些比較低階的子系統,比如ACPI、EFI等;fs目錄裡看到的是系統支援的所有檔案系統;kernel目錄下包含的是一些核心的配置選項;modules目錄下包含的是所有核心模組的資訊,核心模組實際上和裝置之間是有對應關係的,通過這個目錄順藤摸瓜找到devices或者反過來都是可以做到的;power目錄存放的是系統電源管理的資料,使用者可以通過它來查詢目前的電源狀態,甚至可以直接“命令”系統進入休眠等省電模式。
sysfs正是使用者和核心裝置模型之間的一座橋樑,通過這個橋樑我們可以從核心中讀取資訊,也可以向核心裡寫入資訊。
在Linux裡也可以找到一些圖形化的工具來查詢裝置資訊。比如GNOME下基於HAL的Device Manager:
或者KDE下基於Solid的KInfoCenter:
這些圖形化的工具提供了更加直觀的方式來訪問裝置,但是它們的提供的資訊還不夠全面,而且沒有向核心裝置寫資料的功能。
如果具體到某一型別的裝置,Linux下還有一些專用的工具可以使用。比如面向PCI裝置的pciutils,面向USB裝置的usbutils,以及面向SCSI裝置的lsscsi等。對於Linux開發者來說,有時使用這些專用的工具更加方便。
我們如果要寫程式來訪問sysfs,可以像讀寫普通檔案一樣來操作/sys目錄下的檔案,或者,也可以使用libsysfs。不過需要注意的是,Linux核心社群並不推薦用libsysfs,因為這個API的更新不夠快,趕不上核心的變化。libsysfs已經逐漸背離最初建立它的目標,這個lib帶來的問題似乎比它解決的還要多。當然,如果只是要訪問裝置,一般很少會直接操作sysfs,它太細節太底層了,大部分情況下可以使用更加方便的DeviceKit或者libudev。
總結一下,本文主要簡單介紹了Linux裝置模型的基本概念和虛擬檔案系統sysfs。接下來的篇章裡將和大家繼續探討裝置模型在核心空間的一些細節。