天天看點

linux裝置模型詳解【轉】

Linux 2.6核心的一個重要特色是提供了統一的核心裝置模型。随着技術的不斷進步,系統的拓撲結構越來越複雜,對智能電源管理、熱插拔以及plug and play的支援要求也越來越高,2.4核心已經難以滿足這些需求。為适應這種形勢的需要,2.6核心開發了全新的裝置模型。

2.6 裝置模型提供了這個抽象. 現在它用在核心來支援廣泛的任務, 包括:

電源管理和系統關機

這些需要一個對系統的結構的了解. 例如, 一個 USB 宿主擴充卡不可能被關閉, 在處理所有的連接配接到這個擴充卡的裝置之前. 這個裝置模型使能了一個按照正确順序的系統硬體的周遊.

與使用者空間的通訊

sysfs 虛拟檔案系統的實作被緊密地捆綁進裝置模型, 并且暴露它所代表的結構. 關于系統到使用者空間的資訊提供和改變操作參數的旋紐正越來越多地通過 sysfs 和 通過裝置模型來完成.

可熱插拔裝置

計算機硬體正更多地動态變化; 外設可因使用者的一時念頭而進出. 在核心中使用的來處理和(特别的)與使用者空間關于裝置插入和拔出的通訊, 是由裝置模型來管理.

裝置類别

系統的許多部分對裝置如何連接配接沒有興趣, 但是它們需要知道什麼類型的裝置可用. 裝置模型包括一個機制來配置設定裝置給類别, 它在一個更高的功能性的級别描述了這些裝置, 并且允許它們從使用者空間被發現.

對象生命期

許多上面描述的功能, 包括熱插拔支援和 sysfs, 使在核心中建立和操作對象複雜了. 裝置模型的實作要求建立一套機制來處理對象生命期, 它們之間的關系, 和它們在使用者空間的表示.

1. Sysfs檔案系統

Sysfs檔案系統是一個類似于proc檔案系統的特殊檔案系統,用于将系統中的裝置組織成層次結構,并向使用者模式程式提供詳細的核心資料結構資訊。其頂層目錄主要有:

Block目錄:包含所有的塊裝置

Devices目錄:包含系統所有的裝置,并根據裝置挂接的總線類型組織成層次結構

Bus目錄:包含系統中所有的總線類型

Drivers目錄:包括核心中所有已注冊的裝置驅動程式

Class目錄:系統中的裝置類型(如網卡裝置,聲霸卡裝置等)

2. 核心對象機制關鍵資料結構

2.1 kobject核心對象

Kobject 是Linux 2.6引入的新的裝置管理機制,在核心中由struct kobject表示。通過這個資料結構使所有裝置在底層都具有統一的接口,kobject提供基本的對象管理,是構成Linux 2.6裝置模型的核心結構,它與sysfs檔案系統緊密關聯,每個在核心中注冊的kobject對象都對應于sysfs檔案系統中的一個目錄。

Kobject 是基礎的結構, 它保持裝置模型在一起. 初始地它被作為一個簡單的引用計數, 但是它的責任已随時間增長, 并且是以有了它自己的戰場. struct kobject 所處理的任務和它的支援代碼現在包括:

對象的引用計數

常常, 當一個核心對象被建立, 沒有方法知道它會存在多長時間. 一種跟蹤這種對象生命周期的方法是通過引用計數. 當沒有核心代碼持有對給定對象的引用, 那個對象已經完成了它的有用壽命并且可以被删除.

sysfs 表示

在 sysfs 中出現的每個對象在它的下面都有一個 kobject, 它和核心互動來建立它的可見表示.

資料結構粘和

裝置模型是, 整體來看, 一個極端複雜的由多級組成的資料結構, 各級之間有許多連接配接. kobject 實作這個結構并且保持它在一起.

熱插拔事件處理

kobject 子系統處理事件的産生, 事件通知使用者空間關于系統中硬體的來去.

你可能從前面的清單總結出 kobject 是一個複雜的結構. 這可能是對的. 通過一次看一部分, 但是, 是有可能了解這個結構和它如何工作的.

Kobject結構定義為:

struct kobject {

char * k_name; 指向裝置名稱的指針

char name[KOBJ_NAME_LEN]; 裝置名稱

struct kref kref; 對象引用計數

struct list_head entry; 挂接到所在kset中去的單元

struct kobject * parent; 指向父對象的指針

struct kset * kset; 所屬kset的指針

struct kobj_type * ktype; 指向其對象類型描述符的指針

struct dentry * dentry; sysfs檔案系統中與該對象對應的檔案節點路徑指針

};

其中的kref域表示該對象引用的計數,核心通過kref實作對象引用計數管理,核心提供兩個函數kobject_get()、kobject_put()分别用于增加和減少引用計數,當引用計數為0時,所有該對象使用的資源将被釋放。

Ktype域是一個指向kobj_type結構的指針,表示該對象的類型。Kobj_type資料結構包含三個域:

一個release方法用于釋放kobject占用的資源;

一個sysfs_ops指針指向sysfs操作表和一個sysfs檔案系統預設屬性清單。

Sysfs操作表包括兩個函數store()和 show()。當使用者态讀取屬性時,show()函數被調用,該函數編碼指定屬性值存入buffer中傳回給使用者态;而store()函數用于存儲使用者态傳入的屬性值。

2.2 kset核心對象集合

Kobject通常通過kset組織成階層化的結構,kset是具有相同類型的kobject的集合,在核心中用kset資料結構表示,定義為:

struct kset {

struct subsystem * subsys; 所在的subsystem的指針

struct kobj_type * ktype; 指向該kset對象類型描述符的指針

struct list_head list; 用于連接配接該kset中所有kobject的連結清單頭

struct kobject kobj; 嵌入的kobject

struct kset_hotplug_ops * hotplug_ops; 指向熱插拔操作表的指針

包含在kset中的所有kobject被組織成一個雙向循環連結清單,list域正是該連結清單的頭。Ktype域指向一個kobj_type結構,被該 kset中的所有kobject共享,表示這些對象的類型。Kset資料結構還内嵌了一個kobject對象(由kobj域表示),所有屬于這個kset 的kobject對象的parent域均指向這個内嵌的對象。此外,kset還依賴于kobj維護引用計數:kset的引用計數實際上就是内嵌的 kobject對象的引用計數。

2.3 subsystem核心對象子系統

Subsystem是一系列kset的集合,描述系統中某一類裝置子系統,如block_subsys表示所有的塊裝置,對應于sysfs檔案系統中的block目錄。類似的,devices_subsys對應于sysfs中的devices目錄,描述系統中所有的裝置。Subsystem由struct subsystem資料結構描述,定義為:

struct subsystem {

struct kset kset; 内嵌的kset對象

struct rw_semaphore rwsem; 互斥通路信号量

每個kset必須屬于某個subsystem,通過設定kset結構中的subsys域指向指定的subsystem可以将一個kset加入到該subsystem。所有挂接到同一subsystem的kset共享同一個rwsem信号量,用于同步通路kset中的連結清單。

3. 核心對象機制主要相關函數

針對核心對象不同層次的資料結構,linux 2.6核心定義了一系列操作函數,定義于lib/kobject.c檔案中。

3.1 kobject相關函數

void kobject_init(struct kobject * kobj);

kobject初始化函數。設定kobject引用計數為1,entry域指向自身,其所屬kset引用計數加1。

int kobject_set_name(struct kobject *kobj, const char *format, ...);

設定指定kobject的名稱。

void kobject_cleanup(struct kobject * kobj)和void kobject_release(struct kref *kref);

kobject清除函數。當其引用計數為0時,釋放對象占用的資源。

struct kobject *kobject_get(struct kobject *kobj);

将kobj 對象的引用計數加1,同時傳回該對象的指針。

void kobject_put(struct kobject * kobj);

将kobj對象的引用計數減1,如果引用計數降為0,則調用kobject_release()釋放該kobject對象。

int kobject_add(struct kobject * kobj);

将kobj對象加入Linux裝置層次。挂接該kobject對象到kset的list鍊中,增加父目錄各級kobject的引用計數,在其parent指向的目錄下建立檔案節點,并啟動該類型核心對象的hotplug函數。

int kobject_register(struct kobject * kobj);

kobject注冊函數。通過調用kobject_init()初始化kobj,再調用kobject_add()完成該核心對象的注冊。

void kobject_del(struct kobject * kobj);

從Linux裝置層次(hierarchy)中删除kobj對象。

void kobject_unregister(struct kobject * kobj);

kobject登出函數。與kobject_register()相反,它首先調用kobject_del從裝置層次中删除該對象,再調用kobject_put()減少該對象的引用計數,如果引用計數降為0,則釋放該kobject對象。

3.2 kset相關函數

與kobject 相似,kset_init()完成指定kset的初始化,kset_get()和kset_put()分别增加和減少kset對象的引用計數。 Kset_add()和kset_del()函數分别實作将指定keset對象加入裝置層次和從其中删除;kset_register()函數完成 kset的注冊而kset_unregister()函數則完成kset的登出。

3.3 subsystem相關函數

subsystem有一組完成類似的函數,分别是:

void subsystem_init(struct subsystem *subsys);

int subsystem_register(struct subsystem *subsys);

void subsystem_unregister(struct subsystem *subsys);

struct subsystem *subsys_get(struct subsystem *subsys)

void subsys_put(struct subsystem *subsys);

4. 裝置模型元件

在上述核心對象機制的基礎上,Linux的裝置模型建立在幾個關鍵元件的基礎上,下面我們詳細闡述這些元件。

4.1 devices

系統中的任一裝置在裝置模型中都由一個device對象描述,其對應的資料結構struct device定義為:

struct device {

struct list_head g_list; 

struct list_head node;

struct list_head bus_list;

struct list_head driver_list;

struct list_head children;

struct device *parent;

struct kobject kobj;

char bus_id[BUS_ID_SIZE];

struct bus_type *bus;

struct device_driver *driver;

void *driver_data;

/* Several fields omitted */

g_list 将該device對象挂接到全局裝置連結清單中,所有的device對象都包含在devices_subsys中,并組織成層次結構。Node域将該對象挂接到其兄弟對象的連結清單中,而bus_list則用于将連接配接到相同總線上的裝置組織成連結清單,driver_list則将同一驅動程式管理的所有裝置組織為連結清單。此外,children域指向該device對象子對象連結清單頭,parent域則指向父對象。Device對象還内嵌一個kobject對象,用于引用計數管理并通過它實作裝置層次結構。Driver域指向管理該裝置的驅動程式對象,而driver_data則是提供給驅動程式的資料。Bus域描述裝置所連接配接的總線類型。

核心提供了相應的函數用于操作device對象。其中Device_register()函數将一個新的device對象插 入裝置模型,并自動在/sys/devices下建立一個對應的目錄。Device_unregister()完成相反的操作,登出裝置對象。Get_device()和put_device()分别增加與減少裝置對象的引用計數。通常device結構不單獨使用,而是包含在更大的結構中作為一個子結構使用,比如描述PCI裝置的struct pci_dev,其中的dev域就是一個device對象。

4.2 drivers

系統中的每個驅動程式由一個device_driver對象描述,對應的資料結構定義為:

struct device_driver {

char *name; 裝置驅動程式的名稱

struct bus_type *bus; 該驅動所管理的裝置挂接的總線類型

struct kobject kobj; 内嵌kobject對象

struct list_head devices; 該驅動所管理的裝置連結清單頭

int (*probe)(struct device *dev); 指向裝置探測函數,用于探測裝置是否可以被該驅動程式管理

int (*remove)(struct device *dev); 用于删除裝置的函數

/* some fields omitted*/

};

與device結構類似,device_driver對象依靠内嵌的kobject對象實作引用計數管理和層次結構組織。核心提供類似的函數用于操作 device_driver對象,如get_driver()增加引用計數,driver_register()用于向裝置模型插入新的driver對象,同時在sysfs檔案系統中建立對應的目錄。Device_driver()結構還包括幾個函數,用于處理熱拔插、即插即用和電源管理事件。

4.3 buses

系統中總線由struct bus_type描述,定義為:

struct bus_type { 

char * name; 總線類型的名稱

struct subsystem subsys; 與該總線相關的subsystem

struct kset drivers; 所有與該總線相關的驅動程式集合

struct kset devices; 所有挂接在該總線上的裝置集合

struct bus_attribute * bus_attrs; 總線屬性

struct device_attribute * dev_attrs; 裝置屬性

struct driver_attribute * drv_attrs; 驅動程式屬性

int (*match)(struct device * dev, struct device_driver * drv);

int (*hotplug) (struct device *dev, char **envp, int num_envp, char *buffer,int buffer_size); 

int (*suspend)(struct device * dev, u32 state);

int (*resume)(struct device * dev);

每個bus_type對象都内嵌一個subsystem對象,bus_subsys對象管理系統中所有總線類型的subsystem對象。每個bus_type對象都對應/sys/bus目錄下的一個子目錄,如PCI總線類型對應于/sys/bus/pci。在每個這樣的目錄下都存在兩個子目 錄:devices和drivers(分别對應于bus_type結構中的devices和drivers域)。其中devices子目錄描述連接配接在該總線上的所有裝置,而drivers目錄則描述與該總線關聯的所有驅動程式。與device_driver對象類似,bus_type結構還包含幾個函數(match()、hotplug()等)處理相應的熱插拔、即插即拔和電源管理事件。

4.4 classes

系統中的裝置類由 struct class描述,表示某一類裝置。所有的class對象都屬于class_subsys子系統,對應于sysfs檔案系統中的/sys/class目錄。 每個class對象包括一個class_device連結清單,每個class_device對象表示一個邏輯裝置,并通過struct class_device中的dev域(一個指向struct device的指針)關聯一個實體裝置。這樣,一個邏輯裝置總是對應于一個實體裝置,但是一個實體裝置卻可能對應于多個邏輯裝置。此外,class結構中 還包括用于處理熱插拔、即插即拔和電源管理事件的函數,這與device對象和driver對象相似。

總線, 裝置, 和驅動

再一次, 許多驅動作者将不會需要這裡涉及的材料. 這個水準的細節通常在總線級别處理, 并且很少作者需要添加一個新總線類型. 這個資訊是有用的, 但是, 對任何人好奇在 PCI, USB 等層面的裡面發生了什麼或者誰需要在那個級别做改變.

<a>14.4.1. 總線</a>

<a>一個總線是處理器和一個或多個裝置之間的通道. 為裝置模型的目的, 所有的裝置都通過一個總線連接配接, 甚至當它是一個内部的虛拟的,"平台"總線. 總線可以插入另一個 - 一個 USB 控制器常常是一個 PCI 裝置, 例如. 裝置模型表示在總線和它們控制的裝置之間的實際連接配接.</a>

<a>在 Linux 裝置模型中, 一個總線由 bus_type 結構代表, 定義在 &lt;linux/device.h&gt;. 這個結構看來象:</a>

<a>struct bus_type {</a>

<a>char *name;</a>

<a>struct subsystem subsys;</a>

<a>struct kset drivers;</a>

<a>struct kset devices;</a>

<a>int (*match)(struct device *dev, struct device_driver *drv);</a>

<a>struct device *(*add)(struct device * parent, char * bus_id);</a>

<a>int (*hotplug) (struct device *dev, char **envp,</a>

<a>int num_envp, char *buffer, int buffer_size);</a>

<a>/* Some fields omitted */</a>

<a>};</a>

<a> </a>

<a>name 成員是總線的名子, 有些同 pci. 你可從這個結構中見到每個總線是它自己的子系統; 這個子系統不位于 sysfs 的頂層, 但是. 相反, 它們在總線子系統下面. 一個總線包含 2 個 ksets, 代表已知的總線的驅動和所有插入總線的裝置. 是以, 有一套方法我們馬上将涉及.</a>

<a>如同我們提過的, 例子源碼包含一個虛拟總線實作稱為 lddbus. 這個總線建立它的 bus_type 結構, 如下:</a>

<a>struct bus_type ldd_bus_type = { .name = "ldd", .match = ldd_match, .hotplug = ldd_hotplug, };</a>

<a>注意很少 bus_type 成員要求初始化; 大部分由裝置模型核心處理. 但是, 我們确實不得不指定總線的名子, 以及任何伴随它的方法.</a>

<a>不可避免地, 一個新總線必須注冊到系統, 通過一個對 bus_register 的調用. lddbus 代碼這樣做以這樣的方式:</a>

<a>ret = bus_register(&amp;ldd_bus_type);</a>

<a>if (ret)</a>

<a>return ret;</a>

<a>這個調用可能失敗, 當然, 是以傳回值必須一直檢查. 如果它成功, 新總線子系統已被添加到系統; 在 sysfs 中 /sys/bus 的下面可以見到, 并且可能啟動添加裝置.</a>

<a>如果有必要從系統中去除一個總線(當關聯子產品被去除, 例如), 調用調用 bus_unregister:</a>

<a>void bus_unregister(struct bus_type *bus);</a>

<a>有幾個給 bus_type 結構定義的方法; 它們允許總線代碼作為一個裝置核心和單獨驅動之間的中介. 在 2.6.10 核心中定義的方法是:</a>

<a>int (*match)(struct device *device, struct device_driver *driver);</a>

<a>這個方法被調用, 大概多次, 無論何時一個新裝置或者驅動被添加給這個總線. 它應當傳回一個非零值如果給定的裝置可被給定的驅動處理. (我們馬上進入裝置和 device_driver 結構的細節). 這個函數必須在總線級别處理, 因為那是合适的邏輯存在的地方; 核心核心不能知道如何比對每個可能總線類型的裝置和驅動.</a>

<a>int (*hotplug) (struct device *device, char **envp, int num_envp, char *buffer, int buffer_size);</a>

<a>這個子產品允許總線添加變量到環境中, 在産生一個熱插拔事件在使用者空間之前. 參數和 kset 熱插拔方法相同( 在前面的 "熱插拔事件産生" 一節中描述 ).</a>

<a>lddbus 驅動有一個非常簡單的比對函數, 它僅僅比較驅動和裝置的名子:</a>

<a>static int ldd_match(struct device *dev, struct device_driver *driver)</a>

<a>{</a>

<a>return !strncmp(dev-&gt;bus_id, driver-&gt;name, strlen(driver-&gt;name));</a>

<a>}</a>

<a>當涉及到真實硬體, match 函數常常在有裝置自身提供的硬體 ID 和驅動提供的 ID 之間, 做一些比較.</a>

<a>lddbus 熱插拔方法看來象這樣:</a>

<a>static int ldd_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)</a>

<a>envp[0] = buffer;</a>

<a>if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s",</a>

<a>Version) &gt;= buffer_size)</a>

<a>return -ENOMEM;</a>

<a>envp[1] = NULL;</a>

<a>return 0;</a>

<a>這裡, 我們加入 lddbus 源碼的目前版本号, 隻是以防有人好奇.</a>

<a>如果你在編寫總線級别的代碼, 你可能不得不對所有已經注冊到你的總線的裝置或驅動進行一些操作. 它可能會誘惑人直接進入 bus_type 結構中的各種結構, 但是最好使用已經提供的幫助函數.</a>

<a>為操作每個對總線已知的裝置, 使用:</a>

<a>int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));</a>

<a>這個函數列舉總線上的每個裝置, 傳遞關聯的裝置結構給 fn, 連同作為 data 來傳遞的值. 如果 start 是 NULL, 列舉從總線的第一個裝置開始; 否則列舉從 start 之後的第一個裝置開始. 如果 fn 傳回一個非零值, 列舉停止并且那個值從 bus_for_each_dev 傳回.</a>

<a>有一個類似的函數來列舉驅動:</a>

<a>int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));</a>

<a>這個函數就像 buf_for_each_dev, 除了, 當然, 它替之作用于驅動.</a>

<a>應當注意, 這 2 個函數持有總線子系統的讀者/寫者旗标在工作期間. 是以試圖一起使用這 2 個會死鎖 -- 每個将試圖擷取同一個旗标. 修改總線的操作( 例如登出裝置 )也将鎖住. 是以, 小心使用 bus_for_each 函數.</a>

<a>幾乎 Linux 驅動模型中的每一層都提供一個添加屬性的接口, 并且總線層不例外. bus_attribute 類型定義在 &lt;linux/device.h&gt; 如下:</a>

<a>struct bus_attribute {</a>

<a>struct attribute attr;</a>

<a>ssize_t (*show)(struct bus_type *bus, char *buf);</a>

<a>ssize_t (*store)(struct bus_type *bus, const char *buf,</a>

<a>size_t count);</a>

<a>我們已經見到 struct attribute 在 "預設屬性" 一節. bus_attribute 類型也包含 2 個方法來顯示和設定屬性值. 大部分在 kobject 之上的裝置模型層以這種方式工作.</a>

<a>已經提供了一個友善的宏為在編譯時間建立和初始化 bus_attribute 結構:</a>

<a>BUS_ATTR(name, mode, show, store);</a>

<a>這個宏聲明一個結構, 産生它的名子通過字首字元串 bus_attr_ 到給定的名子.</a>

<a>任何屬于一個總線的屬性應當明确使用 bus_create_file 來建立:</a>

<a>int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);</a>

<a>屬性也可被去除, 使用:</a>

<a>void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);</a>

<a>lddbus 驅動建立一個簡單屬性檔案, 再次, 包含源碼版本号. show 方法和 bus_attribute 結構設定如下:</a>

<a>static ssize_t show_bus_version(struct bus_type *bus, char *buf)</a>

<a>return snprintf(buf, PAGE_SIZE, "%s\n", Version);</a>

<a>static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);</a>

<a>建立屬性檔案在子產品加載時間完成:</a>

<a>if (bus_create_file(&amp;ldd_bus_type, &amp;bus_attr_version))</a>

<a>printk(KERN_NOTICE "Unable to create version attribute\n");</a>

<a>這個調用建立一個屬性檔案(/sys/busldd/version) 包含 lddbus 代碼的版本号.</a>

<a>在最低層, Linux 系統中的每個裝置由一個 struct device 代表:</a>

<a>struct device { struct device *parent; struct kobject kobj; char bus_id[BUS_ID_SIZE]; struct bus_type *bus; struct device_driver *driver; void *driver_data; void (*release)(struct device *dev); /* Several fields omitted */ };</a>

<a>有許多其他的 struct device 成員隻對裝置核心代碼感興趣. 但是, 這些成員值得了解:</a>

<a>struct device *parent</a>

<a>裝置的 "parent" 裝置 -- 它所附着到的裝置. 在大部分情況, 一個父裝置是某種總線或者主要制器. 如果 parent 是 NULL, 裝置是一個頂層裝置, 這常常不是你所要的.</a>

<a>struct kobject kobj;</a>

<a>代表這個裝置并且連接配接它到層次中的 kobject. 注意, 作為一個通用的規則, device-&gt;kobj-&gt;parent 等同于 device-&gt;parent-&gt;kobj.</a>

<a>char bus_id[BUS_ID_SIZE];</a>

<a>唯一确定這個總線上的裝置的字元串. PCI 裝置, 例如, 使用标準的 PCI ID 格式, 包含域, 總線, 裝置, 和功能号.</a>

<a>struct bus_type *bus;</a>

<a>确定裝置位于哪種總線.</a>

<a>struct device_driver *driver;</a>

<a>管理這個裝置的驅動; 我們檢視 struct device_driver 在下一節.</a>

<a>void *driver_data;</a>

<a>一個可能被裝置驅動使用的私有資料成員.</a>

<a>void (*release)(struct device *dev);</a>

<a>當對這個裝置的最後引用被去除時調用的方法; 它從被嵌入的 kobject 的 release 方法被調用. 注冊到核心的所有的裝置結構必須有一個 release 方法, 否則核心列印出慌亂的抱怨.</a>

<a>最少, parent, bus_id, bus, 和 release 成員必須在裝置結構被注冊前設定.</a>

<a>通常的注冊和登出函數在:</a>

<a>int device_register(struct device *dev);</a>

<a>void device_unregister(struct device *dev);</a>

<a>我們已經見到 lddbus 代碼如何注冊它的總線類型. 但是, 一個實際的總線是一個裝置并且必須單獨注冊. 為簡單起見, lddbus 子產品隻支援一個單個虛拟總線, 是以這個驅動在編譯時建立它的裝置:</a>

<a>static void ldd_bus_release(struct device *dev)</a>

<a>printk(KERN_DEBUG "lddbus release\n");</a>

<a>struct device ldd_bus = {</a>

<a>.bus_id = "ldd0",</a>

<a>.release = ldd_bus_release</a>

<a>這是頂級總線, 是以 parent 和 bus 成員留為 NULL. 我們有一個簡單的, no-op release 方法, 并且, 作為第一個(并且唯一)總線, 它的名子時 ldd0. 這個總線裝置被注冊, 使用:</a>

<a>ret = device_register(&amp;ldd_bus);</a>

<a>printk(KERN_NOTICE "Unable to register ldd0\n");</a>

<a>一旦調用完成, 新總線可在 sysfs 中 /sys/devices 下面見到. 任何加到這個總線的裝置接着在 /sys/devices/ldd0 下顯示.</a>

<a>sysfs 中的裝置入口可有屬性. 相關的結構是:</a>

<a>struct device_attribute {</a>

<a>ssize_t (*show)(struct device *dev, char *buf);</a>

<a>ssize_t (*store)(struct device *dev, const char *buf,</a>

<a>這些屬性結構可在編譯時建立, 使用這些宏:</a>

<a>DEVICE_ATTR(name, mode, show, store);</a>

<a>結果結構通過字首 dev_attr_ 到給定名子上來命名. 屬性檔案的實際管理使用通常的函數對來處理:</a>

<a>int device_create_file(struct device *device, struct device_attribute *entry);</a>

<a>void device_remove_file(struct device *dev, struct device_attribute *attr);</a>

<a>struct bus_type 的 dev_attrs 成員指向一個預設的屬性清單, 這些屬性給添加到總線的每個裝置建立.</a>

<a>裝置結構包含裝置模型核心需要的來模型化系統的資訊. 大部分子系統, 但是, 跟蹤關于它們駐留的裝置的額外資訊. 結果, 對裝置很少由空裝置結構所代表; 相反, 這個結構, 如同 kobject 結構, 常常是嵌入一個更進階的裝置表示中. 如果你檢視 struct pci_dev 的定義或者 struct usb_device 的定義, 你會發現一個 struct device 埋在其中. 常常地, 低層驅動甚至不知道 struct device, 但是有例外.</a>

<a>lddbus 驅動建立它自己的裝置類型( struct ldd_device ) 并且期望單獨的裝置驅動來注冊它們的裝置使用這個類型. 它是一個簡單結構:</a>

<a>struct ldd_device {</a>

<a>struct ldd_driver *driver;</a>

<a>struct device dev;</a>

<a>#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);</a>

<a>這個結構允許驅動提供一個實際的名子給裝置( 這可以清楚地不同于它的總線 ID, 存儲于裝置結構) 以及一個這些驅動資訊的指針. 給真實裝置的結構常常還包含關于供應者資訊, 裝置型号, 裝置配置, 使用的資源, 等等. 可以在 struct pci_dev (&lt;linux/pci.h&gt;) 或者 struct usb_device (&lt;linux/usb.h&gt;) 中找到好的例子. 一個友善的宏( to_ldd_device ) 也為 struct ldd_device 定義, 使得容易轉換指向被嵌入的結構的指針為 ldd_device 指針.</a>

<a>lddbus 輸出的注冊接口看來如此:</a>

<a>int register_ldd_device(struct ldd_device *ldddev)</a>

<a>ldddev-&gt;dev.bus = &amp;ldd_bus_type;</a>

<a>ldddev-&gt;dev.parent = &amp;ldd_bus;</a>

<a>ldddev-&gt;dev.release = ldd_dev_release;</a>

<a>strncpy(ldddev-&gt;dev.bus_id, ldddev-&gt;name, BUS_ID_SIZE);</a>

<a>return device_register(&amp;ldddev-&gt;dev);</a>

<a>EXPORT_SYMBOL(register_ldd_device);</a>

<a>這裡, 我們簡單地填充一些嵌入的裝置結構成員( 單個驅動不應當需要知道這個 ), 并且注冊這個裝置到驅動核心. 如果我們想添加總線特定的屬性到裝置, 我們可在這裡做.</a>

<a>為顯示這個接口如何使用, 我們介紹另一個例子驅動, 我們稱為 sculld. 它是在第 8 章介紹的 scullp 驅動上的另一個變體. 它實作通用的記憶體區裝置, 但是 sculld 也使用 Linux 裝置模型, 通過 lddbus 接口.</a>

<a>sculld 驅動添加一個它自己的屬性到它的裝置入口; 這個屬性, 稱為 dev, 僅僅包含關聯的裝置号. 這個屬性可被一個子產品用來加載腳本或者熱插拔子系統, 來自動建立裝置節點, 當裝置被添加到系統時. 這個屬性的設定遵循常用模式:</a>

<a>static ssize_t sculld_show_dev(struct device *ddev, char *buf)</a>

<a>struct sculld_dev *dev = ddev-&gt;driver_data;</a>

<a>return print_dev_t(buf, dev-&gt;cdev.dev);</a>

<a>static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);</a>

<a>接着, 在初始化時間, 裝置被注冊, 并且 dev 屬性被建立通過下面的函數:</a>

<a>static void sculld_register_dev(struct sculld_dev *dev, int index)</a>

<a>sprintf(dev-&gt;devname, "sculld%d", index);</a>

<a>dev-&gt;ldev.name = dev-&gt;devname;</a>

<a>dev-&gt;ldev.driver = &amp;sculld_driver;</a>

<a>dev-&gt;ldev.dev.driver_data = dev;</a>

<a>register_ldd_device(&amp;dev-&gt;ldev);</a>

<a>device_create_file(&amp;dev-&gt;ldev.dev, &amp;dev_attr_dev);</a>

<a>注意, 我們使用 driver_data 成員來存儲指向我們自己的内部的裝置結構的指針.</a>

<a>裝置模型跟蹤所有對系統已知的驅動. 這個跟蹤的主要原因是使驅動核心能比對驅動和新裝置. 一旦驅動在系統中是已知的對象, 但是, 許多其他的事情變得有可能. 裝置驅動可輸出和任何特定裝置無關的資訊和配置變量, 例如:</a>

<a>驅動由下列結構定義:</a>

<a>struct device_driver {</a>

<a>struct list_head devices;</a>

<a>int (*probe)(struct device *dev);</a>

<a>int (*remove)(struct device *dev);</a>

<a>void (*shutdown) (struct device *dev);</a>

<a>再一次, 幾個結構成員被忽略( 全部内容見 &lt;linux/device.h&gt; ). 這裡, name 是驅動的名子( 它在 sysfs 中出現 ), bus 是這個驅動使用的總線類型, kobj 是必然的 kobject, devices 是目前綁定到這個驅動的所有裝置的清單, probe 是一個函數被調用來查詢一個特定裝置的存在(以及這個驅動是否可以使用它), remove 當裝置從系統中去除時被調用, shutdown 在關閉時被調用來關閉裝置.</a>

<a>使用 device_driver 結構的函數的形式, 現在應當看來是類似的(是以我們快速涵蓋它們). 注冊函數是:</a>

<a>int driver_register(struct device_driver *drv);</a>

<a>void driver_unregister(struct device_driver *drv);</a>

<a>通常的屬性結構在:</a>

<a>struct driver_attribute {</a>

<a>ssize_t (*show)(struct device_driver *drv, char *buf);</a>

<a>ssize_t (*store)(struct device_driver *drv, const char *buf,</a>

<a>DRIVER_ATTR(name, mode, show, store);</a>

<a>以及屬性檔案以通常的方法建立:</a>

<a>int driver_create_file(struct device_driver *drv, struct driver_attribute *attr);</a>

<a>void driver_remove_file(struct device_driver *drv, struct driver_attribute *attr);</a>

<a>bus_type 結構含有一個成員( drv_attrs ) 指向一套預設屬性, 對所有關聯到這個總線的驅動都建立.</a>

<a>如同大部分驅動核心結構的情形, device_driver 結構常常被發現嵌到一個更進階的, 總線特定的結構. lddbus 子系統不會和這樣的趨勢相反, 是以它已定義了它自己的 ldd_driver 結構:</a>

<a>struct ldd_driver {</a>

<a>char *version;</a>

<a>struct module *module;</a>

<a>struct device_driver driver;</a>

<a>struct driver_attribute version_attr;</a>

<a>#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);</a>

<a>這裡, 我們要求每個驅動提供特定目前軟體版本, 并且 lddbus 輸出這個版本字串為它知道的每個驅動. 總線特定的驅動注冊函數是:</a>

<a>int register_ldd_driver(struct ldd_driver *driver)</a>

<a>int ret;</a>

<a>driver-&gt;driver.bus = &amp;ldd_bus_type;</a>

<a>ret = driver_register(&amp;driver-&gt;driver);</a>

<a>driver-&gt;version_attr.attr.name = "version";</a>

<a>driver-&gt;version_attr.attr.owner = driver-&gt;module;</a>

<a>driver-&gt;version_attr.attr.mode = S_IRUGO;</a>

<a>driver-&gt;version_attr.show = show_version;</a>

<a>driver-&gt;version_attr.store = NULL;</a>

<a>return driver_create_file(&amp;driver-&gt;driver, &amp;driver-&gt;version_attr);</a>

<a>這個函數的第一部分隻注冊低級的 device_driver 結構到核心; 剩下的建立版本屬性. 因為這個屬性在運作時被建立, 我們不能使用 DRIVER_ATTR 宏; 反之, driver_attribute 結構必須手工填充. 注意我們設定屬性的擁有者為驅動子產品, 不是 lddbus 子產品; 這樣做的理由是可以在為這個屬性的 show 函數的實作中見到:</a>

<a>static ssize_t show_version(struct device_driver *driver, char *buf)</a>

<a>struct ldd_driver *ldriver = to_ldd_driver(driver);</a>

<a>sprintf(buf, "%s\n", ldriver-&gt;version);</a>

<a>return strlen(buf);</a>

<a>有人可能認為屬性擁有者應當是 lddbus 子產品, 因為實作這個屬性的函數在那裡定義. 這個函數, 但是, 是使用驅動自身所建立的 ldd_driver 結構. 如果那個結構在一個使用者空間程序試圖讀取版本号時要消失, 事情會變得麻煩. 指定驅動子產品作為屬性的擁有者阻止了子產品被解除安裝, 在使用者空間保持屬性檔案打開時. 因為每個驅動子產品建立一個對 lddbus 子產品的引用, 我們能确信 lddbus 不會在一個不合适的時間被解除安裝.</a>

<a>為完整起見, sculld 建立它的 ldd_driver 結構如下:</a>

<a>static struct ldd_driver sculld_driver = { .version = "$Revision: 1.1 $", .module = THIS_MODULE, .driver = { .name = "sculld", }, };</a>

<a>一個簡單的對 register_ldd_driver 的調用添加它到系統中. 一旦完成初始化, 驅動資訊可在 sysfs 中見到:</a>

<a>$ tree /sys/bus/ldd/drivers</a>

<a>/sys/bus/ldd/drivers</a>

<a>`-- sculld</a>

<a>|-- sculld0 -&gt; ../../../../devices/ldd0/sculld0</a>

<a>|-- sculld1 -&gt; ../../../../devices/ldd0/sculld1</a>

<a>|-- sculld2 -&gt; ../../../../devices/ldd0/sculld2</a>

<a>|-- sculld3 -&gt; ../../../../devices/ldd0/sculld3</a>

<a>`-- version</a>

linux裝置模型詳解【轉】

本文轉自feisky部落格園部落格,原文連結:http://www.cnblogs.com/feisky/archive/2010/05/30/1747320.html,如需轉載請自行聯系原作者

繼續閱讀