面向物件地分析Linux核心裝置驅動(2)——Linux核心裝置模型與匯流排
Linux核心裝置模型與匯流排
- 核心版本 Linux Kernel 2.6.34, 與 Robert.Love的《Linux Kernel Development》(第三版)所講述的核心版本一樣
1. Linux核心裝置模型分析
1) kobject物件的設計思想
- 對於沒有接觸過JAVA、Python等高階面向物件程式語言工程師,第一次看到struct kobject物件,都會對它的作用感到困惑,不知道為什麼這麼多Linux核心物件結構體中,都要有一個看起來沒什麼用的struct kobject。
- 對於接觸過JAVA、Python等面向物件程式語言的工程師,對object物件肯定不陌生。在JAVA、Python中,object物件是所有物件的基類,所有的物件最終都會繼承到object物件。關於JAVA中object物件及其相關方法的描述,可以參閱
- 在Linux核心裝置模型中,也是借鑑了JAVA、Python中的做法,讓struct kobject物件作為所有核心裝置的基類,kobject是一個最高層次的抽象基類,這樣Linux核心才能夠通過一個繼承樹體系,管理到系統裡的每一個裝置。
- 在Linux核心中,雖然kobject物件可以作為所有裝置物件的基類,但是類比於JAVA、Python,我們一般不直接使用kobject這種最高抽象層次的基類作為實際需要開發的裝置程式的的直接基類,原因見圖1。kobject類比於物質在自然界繼承體系裡的概念,物質是一個抽象的概念,所有的生物和非生物都繼承自物質,即他們和物質都是IS-A的關係。狗 IS-A 物質,水IS-A 物質。但是我們真正研究狗的時候,一般是從其犬科動物
- 同理,在研究一般具體的Linux裝置驅動,如視訊裝置V4L2驅動的程式碼,一般都從其上層基類struct video_device研究起,或者再抽血一些,研究struct video_device 的基類struct cdev, 很少直接使用最上層的kobject基類。但是V4L2裝置驅動,確實 IS-A kobject。
Figure 1 自然界繼承體系,所有物件都繼承自物質
2) kobject物件的特性
- kobjet物件的宣告在與相關的API在include/linux/kobject.h檔案中。
- kobject 物件有物件引用計數(kref),父子object索引(parent)等基本屬性。
- kobject提供了sysfs檔案系統相關的節點描述,屬性與函式,使得Linux系統可以通過特殊的sysfs檔案系統,以樹形繼承的關係來訪問Linux核心中的每個具體的kobject物件。實際上,kobject最初的設計目的就是為了在sysfs中模仿windows的裝置管理器,提供一個類似樹形的,可以管理額訪問系統所有裝置的介面。
- kobject物件還提供了Linux系統裝置中hotplug熱插拔相關的事件與函式方法,使得核心裝置可以支援熱插拔機制。
- 每個繼承了object的Linux裝置物件,在呼叫者獲得kobject基類物件例項之後,可以通過container_of()函式,一層一層轉換,最終獲得具體子類物件。有了kobject,就可以實現通過基類來訪問子類物件的機制。
3) Linux核心中繼承kobject的主要基礎類裝置模型
- Kobject類似於JAVA中的object類,一般不作為核心裝置物件的直接基類。但是類似於JDK中有不少物件直接繼承自object物件,JDK中直接繼承自object的物件,一般都是最為基礎類物件,提供給開發者使用。同樣,Linux核心中也有不少繼承自kobject的基礎類物件,Linux核心驅動開發者可以使用這些直接繼承自kobject的基礎類裝置,來構建實際的Linux裝置驅動。
- kset是一個集合容器,用於管理一類object子類物件的集合,繼承自某個基類kobject的所有基類的kobject物件都可以用一個kset容器來管理。
- Linux核心在繼承自kobject的重要基礎類物件如圖2所示。
Figure 2 Linux核心中直接繼承kobject的重要基礎類物件
- device類物件一般用於Linux各種匯流排裝置(platform虛擬平臺匯流排、USB匯流排等)的基類,下一節詳細介紹。
- module物件是用於模組管理介面(就是上一篇文章中單繼承與介面一節的介面),介面本身也是一個物件(JAVA中的interface也是物件,繼承object),實現了module介面的物件,可以通過模組的方式,動態裝載、解除安裝程式碼塊。
- class物件是用於裝置分類管理的相關介面,通過class介面可以將核心中各種裝置型別的資訊匯出到使用者態。
- cdev物件就是典型的字元裝置基類物件,所有的字元裝置最終都會繼承到cdev物件。cdev物件同時制定了字元裝置標準的訪問介面函式方法。
- 總之,擁有了以上這些基礎類,核心開發者就能開發自己特殊的裝置驅動,並且通過這些類和介面,將驅動程式整合到Linux核心中。
3). Linux核心中是怎麼管理維護這些繼承類物件
- 之前的內容講了一堆面向物件的概念,描述了一堆與Linux裝置驅動有關的物件之間的關係,可Linux核心畢竟是C語言寫的,核心中如何維護這些基礎類物件呢?
- 在Linux核心drivers/base/ 目錄下,有很多重要的程式碼,目錄命名為base/,可見基礎類物件這個 名詞還是有來源依據的,Linux裝置驅動裡的物件基本都是繼承自drivers/base/裡面的物件。
- drivers/base/base.h中聲明瞭base的一些私有物件屬性,以及API,其中很多API如devices_init(),buses_init(),classes_init(),platform_bus_init()等初始化函式都會在Linux核心init函式的driver_init()中被呼叫,因此可見,Linux核心在啟動時通過base.h中的這些初始化*_init()函式使得Linux核心中整個驅動系統相關的基礎類元件物件都能進入工作狀態。
- 檢視devices_init()函式實現程式碼如下,我們發現實際上,核心在初始化devices的時候,使用kobject物件建立了dev_kobj作為所有device子類物件的基類。並且建立了相應的devices_kset來管理這些子類。
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;
return 0;
char_kobj_err:
kobject_put(sysfs_dev_block_kobj);
block_kobj_err:
kobject_put(dev_kobj);
dev_kobj_err:
kset_unregister(devices_kset);
return -ENOMEM;
}
- 最後,我們實現device的子類物件,並通過device_register()函式將其註冊的時候(Linux核心還有很多類似的register函式,例如register_chrdev_region,註冊函式意圖大同小異,都是讓基類能夠獲取註冊後的子類物件),使用者就可以通過基類訪問註冊後的子類物件了。
- 在device_register()函式中,我們看到註冊的device子類會和基類的kset容器以及關聯起來,最終系統可以通過基類device物件例項dev_kobj所關聯的kset容器來訪問deivce的子類物件(一般會通過container_of()函式獲取子類物件)。
- 實際上,Linux核心中,cdev,device等基礎類物件,都會在初始化時init()相關的全域性變數,Linux核心需要維護這些全域性變數以及相關的容器(kset、陣列、list都可以看成容器)。
- 由此可見,面向物件思想裡面,繼承,多型,虛擬函式的實現並不神祕,還是通過精巧的設計,用全部變數加容易的方法來管理這些關係。由於有這些由Linux核心維護的全域性變數和相關容器,所以在開發裝置驅動模組子類時,需要通過註冊函式(register)才能讓基類能夠關聯到子類的物件。當有了面向物件的觀點,我們可以在更高層次理解Linux核心這些物件的關係,從而設計並改進我們的系統。
2. Linux核心匯流排、裝置與驅動
1). 計算機系統匯流排模型
- Linux核心匯流排的思想來源於如圖3所示的計算機系統匯流排模型。
- 計算機系統匯流排控制著外部裝置與計算機CPU的通訊,任意CPU N都可以通過匯流排訪問到任意外部裝置。
- 一般情況下,外部裝置數量都會大於CPU的數量,有了匯流排,無需為每個外部裝置都配備一個CPU。只有外部裝置需要CPU來訪問讀取處理資料,傳送控制訊號時,一個空閒的CPU才會通過匯流排控制器與某個外部裝置建立通訊連線。
- 一旦CPU處理完某個外部裝置的資料之後,CPU可以通過匯流排控制器,斷開和某個外部裝置的連線,去處理其它外部裝置的訪問需求。
- 總之,計算機系統中的匯流排模型為數量較多的外部裝置提供了一種共享數量較少的CPU的控制訪問機制。
Figure 3 計算機系統中的匯流排
2). Linux核心中的與匯流排
- Linux核心之後有各種各樣的軟體匯流排,系統中所支援的所有匯流排型驅動裝置在/sys/bus/ 目錄下可以看到。主要的匯流排型裝置驅動有USB、platform、I2C、SPI、SCSI、mmc等。
- Linux核心中所有匯流排介面的的基類以及相關的API都是在include/linux/device.h中宣告。其中一個匯流排介面包括三個核心的基類物件:struct bus_type、struct device 與struct device_driver。這些基類物件與Linux核心中實際的USB、platform、I2C匯流排介面的繼承關係如圖4所示。
Figure 4 Linux核心中,各種匯流排裝置與匯流排介面基類的對應關係
- Linux 核心匯流排驅動實際上是模仿計算機系統匯流排的機制,在一個具體型別的總線上(例如I2C、USB、platform)多數的device裝置共享少數的device_driver提供了一種管理機制。
- 通過匯流排,將一個裝置驅動中,邏輯功能部分(device_driver)與硬體具體資源bsp相關的部分(device)分隔開來,使得同一種類型的多數裝置例項(device),能夠共享同一個驅動程式邏輯程式碼(device_driver)。
- struct bus_type 物件與struct device 物件、structdevice_driver物件構成了一個裝置例項化管理介面,物件之間的行為模式如圖5所示,類似於抽象共產,將每個device與device_driver裝配起來,構造出真正的裝置例項。
- struct bus_type物件在 match()函式方法中,通過對比新發現的device 與 device_driver 的 Id(Id可以是name也可以是dts的描述節點或者實際匯流排自己定義的匹配Id號都可以匹配),為新加入的device裝置找到合適的Id匹配的device_driver,然後呼叫device_driver的probe()函式方法,進行構造例項化,最終產生例項化的裝置驅動並且作為節點掛載在/dev目錄下。
- struct bus_type物件的 remove()函式則處理裝置解除安裝析構的行為
- bus_type物件與device物件、device_driver物件除了用於例項化的函式方法,還有suspend()、resume()、shutdown()等函式方法,用於實現裝置的休眠、喚醒等電源管理相關的功能。
- struct bus_type物件的uevent()函式方法提供了熱插拔事件的相關通訊機制,通過該函式介面,可以給使用者態傳送熱插拔相關的非同步事件。
Figure 5 bus_type 與 device、device_driver物件的行為模式
3).platform匯流排與裝置簡介
- 在Linux 核心中,USB、I2C、SPI等匯流排都是實際存在的匯流排,都有對應的相關的外部硬體電路以及相關的標準化通訊協議最終以電訊號為載體與實際的I2C、USB等實際硬體裝置通訊。
- USB、I2C等匯流排裝置,可以通過真正的硬體熱插拔,從而觸發具體的bus_type匯流排進行driver與device匹配,最終在/dev/*目錄下構造例項化裝置驅動。
- 而struct platform_bus_type物件在Linux核心中代表一個虛擬的平臺裝置匯流排。即在系統硬體中,不存在與platform_bus_type對應的硬體電路,在SOC中也不存在對應的匯流排控制器(USB、I2C等模組在SOC晶片中都有相關的硬體控制器)。
- 雖然struct platform_bus_type不存在真正的匯流排,但是我們在處理各種雜七雜八的驅動時(比如LED、智慧卡、雜項裝置、framebuffer等),也有把device_driver的驅動實現邏輯程式碼和device硬體bsp相關的程式碼分離出來的需求, 這樣使得同樣型別但是佔用不同埠或者資源(比如 LED1、 LED2都是LED裝置,但是一般會佔用不同的GPIO口)的device能夠共享同一份device_driver的邏輯程式碼,不需要為每一個LED裝置都寫一份驅動(維護量無比巨大)。
- 因而Linux核心採用 struct platform_bus_type、structplatform_device 與struct platform_driver三個物件繼承了匯流排裝置相關的基類物件,模仿系統匯流排的行為模式,通過struct platform_bus_type來管理 structplatform_driver與struct platform_device的裝置匹配與裝置構造例項化。
- 雖然platform虛擬平臺匯流排不像usb、I2C等匯流排介面有真正的硬體裝置插拔事件。但是struct platform_driver與struct platform_device物件都實現了module介面,可以編譯成module進行insmod/rmmod等動態裝載於解除安裝。那麼struct platform_driver與struct platform_device物件在在作為module動態地裝載與解除安裝時,相當於模擬了匯流排的熱插拔事件,那麼可以通過insmod/rmmod模擬匯流排裝置的熱插拔,來觸發struct platform_bus_type物件進行driver與device匹配,並在/dev/*目錄下構造例項化真正的裝置驅動。