前言
在早期的Linux內(nèi)核中并沒有為設備驅動提供統(tǒng)一的設備模型。隨著內(nèi)核的不斷擴大及系統(tǒng)更加復雜,編寫一個驅動程序越來越困難,所以在Linux2.6內(nèi)核中添加了一個統(tǒng)一的設備模型。這樣,寫設備驅動程序就稍微容易一些了。本章將對設備模型進行詳細的介紹。
設備驅動模型概述
設備驅動模型比較復雜,Linux系統(tǒng)將設備和驅動歸一到設備驅動模型中來管理。設備驅動模型的提出,解決了以前編寫驅動程序沒有統(tǒng)一方法的局面。設備驅動模型給各種驅動程序提供了很多輔助性的函數(shù),這些函數(shù)經(jīng)過嚴格測試,可以很大程度上地提高驅動開發(fā)人員的工作效率。
設備驅動模型的功能
Linux內(nèi)核早期的版本為編寫驅動程序提供了簡單的功能:分配內(nèi)存、分配I/O地址、分配中斷請求等。寫好驅動后,直接把程序加入到內(nèi)核的相關初始化函數(shù)中,這是一個非常復雜的過程,所以開發(fā)驅動程序并不簡單。并且,沒有統(tǒng)一的設備驅動模型。幾乎每一種設備驅動程序都需要自己完成所有的工作,驅動程序中不免會產(chǎn)生錯誤和大量的重復代碼。
有了設備驅動模型后,現(xiàn)在的情況就不一樣了。設備驅動模型提供了硬件的抽象,內(nèi)核使用該抽象可以完成很多硬件重復的工作。這樣很多重復的代碼就不需要編寫和調(diào)試了,編寫驅動程序的難度就有所下降。這些抽象包括如下幾個方面:1.電源管理
電源管理一直是內(nèi)核的一個組成部分,在筆記本和嵌入式系統(tǒng)中更是如此,它們使用電池來供電。簡單地說,電源管理就是當系統(tǒng)的某些設備不需要工作時,暫時的以最低電耗的方式掛起設備,以節(jié)省系統(tǒng)的電能。電源管理的一個重要功能是:在省電模式下,使系統(tǒng)中的設備以一定的先后順序掛起;在全速工作模式下,使系統(tǒng)中的設備以一定的先后順序恢復運行。
例如:一條總線上連接了A、B、C三個設備,只有當A、B、C三個設備都掛起時,總線才能掛起。當A、B、C三個設備中的任何一個恢復以前,總線必須恢復??傊O備驅動模型使得電源管理子系統(tǒng)能夠以正確的順序遍歷系統(tǒng)上的設備。2.即插即用設備支持
越來越多的設備可以即插即用了,最常用的設備就是U盤,甚至連(移動)硬盤也可以即插即用。這種即插即用機制,使得用戶可以根據(jù)自己的需要安裝和卸載設備。設備驅動模型自動捕捉插拔信號,加載驅動程序,使內(nèi)核容易與設備進行通信。3.與用戶空間的通信
用戶空間程序通過sysfs
虛擬文件系統(tǒng)訪問設備的相關信息。這些信息被組織成層次結構,用sysfs
虛擬文件系統(tǒng)來表示。用戶通過對sysfs
文件系統(tǒng)的操作,就能控制設備,或者從系統(tǒng)中讀出設備的當前信息。
sysfs文件系統(tǒng)
sysfs
文件系統(tǒng)是Linux眾多文件系統(tǒng)中的一個。在Linux系統(tǒng)中,每個文件系統(tǒng)都有其特殊的用途。例如ext2
用于快速讀寫存儲文件;ext3
用來記錄日志文件。
Linux設備驅動模型由大量的數(shù)據(jù)結構和算法組成。這些數(shù)據(jù)結構之間的關系非常的復雜,多數(shù)結構之間通過指針相互關聯(lián),構成樹形或者網(wǎng)狀關系。顯示這種關系的最好方法是利用一種樹形的文件系統(tǒng),但是這種文件系統(tǒng)需要具有其他文件系統(tǒng)沒有的功能,例如顯示內(nèi)核中的一些關于設備、驅動和總線的信息。為了達到這個目的,Linux內(nèi)核開發(fā)者創(chuàng)建了一種新的文件系統(tǒng),這就是sysfs
文件系統(tǒng)。1.sys概述
sysfs
文件系統(tǒng)是Linux2.6內(nèi)核的一個新特性,其是一個只存在于內(nèi)存中的文件系統(tǒng)。內(nèi)核通過這個文件系統(tǒng)將信息導出到用戶空間中。sysfs文件系統(tǒng)的目錄之間的關系非常復雜,各目錄與文件之間既有樹形結構,又有目錄關系。
在內(nèi)核中,這種關系由設備驅動模型來表示。在sysfs
文件系統(tǒng)中產(chǎn)生的文件大多數(shù)是ASCII文件,通常每個文件有一個值,也可叫屬性文件。文件的ASCII碼特性保證了被導出信息的準確性,而且易于訪問,這些特點使sysfs
成為2.6內(nèi)核最直觀,最有用的特性之一。2.sysfs文件系統(tǒng)與內(nèi)核結構的關系
sysfs
文件系統(tǒng)是內(nèi)核對象(kobject
)、屬性(kobj_type
)及它們的相互關系的一種表現(xiàn)機制。用戶可以從sysfs
文件系統(tǒng)中讀出內(nèi)核的數(shù)據(jù),也可以將用戶空間的數(shù)據(jù)寫入內(nèi)核中。這是sysfs
文件系統(tǒng)非常重要的特性,通過這個特性,用戶空間的數(shù)據(jù)就能夠傳送到內(nèi)核空間中,從而設置驅動程序的屬性和狀態(tài)。下表揭示了內(nèi)核中的數(shù)據(jù)結構與sysfs
文件系統(tǒng)的關系。
sysfs文件系統(tǒng)的目錄結構
sysfs
文件系統(tǒng)中包含了一些重要的目錄,這些目錄中包含了與設備和驅動等相關的信息,現(xiàn)對其詳細介紹如下:1.sysfs文件系統(tǒng)的目錄
sysfs
文件系統(tǒng)與其他文件系統(tǒng)一樣,由目錄、文件、鏈接組成。與其他文件系統(tǒng)不同的是,sysfs
文件系統(tǒng)表示的內(nèi)容與其他文件系統(tǒng)中的內(nèi)容不同。另外,sysfs
文件系統(tǒng)只存在于內(nèi)存中,動態(tài)的表示著內(nèi)核的數(shù)據(jù)結構。sysfs
文件系統(tǒng)掛接了一些子目錄,這些目錄代表了注冊sysfs
中的主要子系統(tǒng)。
要查看這些子目錄和文件,可以使用ls
命令,命令執(zhí)行如下:
當設備啟動時,設備驅動模型會注冊kobject
對象,并在sysfs
文件系統(tǒng)中產(chǎn)生以上的目錄?,F(xiàn)對其中的主要目錄所包含的信息進行說明。2. block目錄
塊目錄包含了在系統(tǒng)中發(fā)現(xiàn)的每個塊設備的子目錄,每個塊設備對應一個子目錄。每個塊設備的目錄中有各種屬性,描述了設備的各種信息。例如設備的大小、設備號等。塊設備目錄中有一個表示I/O調(diào)度器的目錄,這個目錄中提供了一些屬性文件。它們是關于設備請求隊列信息和一些可調(diào)整的特性。用戶和管理員可以用它們優(yōu)化性能,包括用它們動態(tài)改變I/O調(diào)度器。塊設備的每個分區(qū)表示為塊設備的子目錄,這些目錄中包含了分區(qū)的讀寫屬性。3. bus目錄
總線目錄包含了在內(nèi)核中注冊而得到支持的每個物理總線的子目錄,例如ide、pci、scsi、i2c和pnp
總線等。使用ls
命令可以查看bus
目錄的結構信息,如下所示:ls
命令列出了注冊到系統(tǒng)中的總線,其中每個目錄中的結構都大同小異。這里以usb
目錄為例,分析其目錄的結構關系。使用cd usb
命令,進入usb
目錄,然后使用ls
命令列出usb
目錄中包含的目錄和文件,如下所示:usb
目錄中包含了devices和drivers
目錄。devices
目錄包含了USB總線下所有設備的列表,這些列表實際上是指向設備目錄中相應設備的符號鏈接。使用ls
命令查看如下所示。
其中1-0:1.0
和2-0:1.0
是USB設備的名字,這些名字由USB協(xié)議規(guī)范來定義??梢钥闯?code>devices目錄下包含的是符號鏈接,其指向/sys/devices
目錄下的相應硬件設備。硬件的設備文件是在/sys/devices/
目錄及其子目錄下,這個鏈接的目的是為了構建sysfs
文件系統(tǒng)的層次結構。drivers
目錄包含了USB總線下注冊時所有驅動程序的目錄。每個驅動目錄中有允許查看和操作設備參數(shù)的屬性文件,和指向該設備所綁定的物理設備的符號鏈接。class目錄
類目錄中的子目錄表示每一個注冊到內(nèi)核中的設備類。例如固件類(firmware
)、混雜設備類(misc
)、圖形類(graphics
)、聲音類(sound
)和輸入類(input
)等。這些類如下所示。
類對象只包含一些設備的總稱,例如網(wǎng)絡類包含一切的網(wǎng)絡設備,集中在/sys/class/net
目錄下。輸入設備類包含一切的輸入設備,如鼠標、鍵盤和觸摸板等,它們集中在/sys/class/input
目錄下。關于類的詳細概述將在后面講述。
設備驅動模型的核心數(shù)據(jù)結構
設備驅動模型由幾個核心的數(shù)據(jù)結構組成,分別是kobject、kset和subsystem
。這些結構使設備驅動模型組成了一個層次結構。該層次結構將驅動、設備和總線等聯(lián)系起來,形成一個完整的設備模型。下面分別對這些結構進行詳細的介紹。
kobject結構體
宏觀上來說,設備驅動模型是一個設備和驅動組成的層次結構。例如一條總線上掛接了很多設備,總線在Linux中也是一種設備,為了表述清楚,這里將其命名為A。在A總線上掛接了一個USB控制器硬件B,在B上掛接了設備C和D,當然如果C和D是一種可以掛接其他設備的父設備,那么在C和D設備下也可以掛接其他設備,但這里認為它們是普通設備。另外在A總線上還掛接了E和F設備,則這些設備的關系如下圖所示。
在sysfs
文件系統(tǒng),這些設備使用樹形目錄來表示,如下所示。
樹形結構中每個目錄與一個kobject
對象相對應,其包含了目錄的組織結構和名字等信息。在Linux系統(tǒng)中,kobject
結構體是組成設備驅動模型的基本結構。最初它作為設備的一個引用計數(shù)使用,隨著系統(tǒng)功能的增加,它的任務也越來越多。kobject
提供了最基本的設備對象管理能力,每一個在內(nèi)核中注冊的kobject
對象都對應于sysfs
文件系統(tǒng)中的一個目錄。kobject
結構體的定義如下:1.kobject
結構體kobject
結構體的定義如下:
struct kobject{
const char *name; /*kobject的名稱*/
struct list_head entry; /*連接下一個kobject結構*/
struct kobject *parent; /*指向父kobject結構體,如果存在父親*/
struct kset *kset; /*指向kset集合*/
struct kobj_type *ktype; /*指向kobject的類型描述符*/
struct sysfs_dirent *sd; /*對應sysfs的文件目錄*/
struct kref kref; /*kobject的引用計數(shù)*/
unsigned int state_initialized:1; /*該kobject對象是否初始化的位*/
unsigned int state_in_sysfs:1; /*是否已經(jīng)加入sysfs中*/
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
}
下面對kobject
的幾個重要成員介紹如下:
- 2行是
kobject
結構體的名稱,該名稱將顯示在sysfs
文件系統(tǒng)中,作為一個目錄的名字。 - 6行代表的
kobject
的屬性,可以將屬性看成sysfs
中的一個屬性文件。每個對象都有屬性,例如,電源管理需要一個屬性表示是否支持掛起;熱插拔事件管理需要一個屬性來實現(xiàn)設備的狀態(tài)。因為大部分的同類設備都有相同的屬性,因此將這個屬性單獨組織為一個數(shù)據(jù)結構kobject_type
,存放在ktype
中。這樣就可以靈活地管理屬性了。需要注意的是,對于sysfs
中的普通文件讀寫操作都是都是由kobject->ktype->sysfs_ops
指針來完成的。對于kobj_type
的詳細說明將在后面列出。 - 第8行的
kref
字段表示該對象引用的計數(shù),內(nèi)核通過kref
實現(xiàn)對象引用計數(shù)管理。內(nèi)核提供兩個函數(shù)kobject_get()、kobject_put()
分別用于增加和減少引用計數(shù),當引用計數(shù)為0時,所有該對象使用的資源被釋放。下文將對這兩個函數(shù)詳細解釋。 - 第9行的
state_initialized
表示kobject
是否已經(jīng)初始化過,1表示初始化,0表示未初始化。unsigend int state_initialized:1
中的1表示,只用unsigned int
的最低1位表示這個布爾值。 - 第10行的
state_in_sysfs
表示kobject
是否已經(jīng)注冊到sysfs
文件系統(tǒng)中。2.kobject結構體的初始化函數(shù)kobject_init()
對kobject
結構體進行初始化有些復雜。但無論如何,首先應將整個kobject
設置為0,一般使用memset()
函數(shù)來完成。如果沒有對kobject
置0,那么在以后使用kobject
時,可能發(fā)生一些奇怪的錯誤。對kobject
置0后,可以調(diào)用kobject_init()
函數(shù),對其中的成員進行初始化,該函數(shù)的代碼如下:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str; /*出錯時,保存錯誤字符串提示*/
if(!kobj){
err_str = "invaild kobject pointer!"; /*kobjetc為無效指針*/
goto error;
}
if(!ktype){
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if(kobj->state_initialized){ /*如果kobject已經(jīng)初始化,則出錯*/
/*打印錯誤信息,有時候可以恢復到正常狀態(tài)*/
printk(KERN_ERR "kobject (%p):tired to init an initialized"
"object, something is seriously wrong.\n",kobj);
dump_stack(); /*以堆棧方式追溯出錯信息*/
}
kobject_init_internel(kobj); /*初始化kobject的內(nèi)部成員變量*/
kobj->ktype = ktype; /*為kobject綁定一個ktype屬性*/
return;
error:
printk(KERN_ERR"kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
- 4~11行,檢查
kobj
和ktype
是否合法,它們都不應該是一個空指針。 - 12~16行,判斷該
kobj
是否已經(jīng)初始化過了,如果已經(jīng)初始化,則打印出錯信息。 - 18行調(diào)用
kobject_init_internel()
函數(shù)初始化kobj
結構體的內(nèi)部成員,該函數(shù)將在下面介紹。 - 19行將定義的一個屬性結構體
ktype
賦給kobj->ktype
。這是一個kobj_type
結構體,與sysfs
文件的屬性有關,將在后面介紹。例如一個喇叭設備在sysfs
目錄中注冊了一個A目錄,該目錄對應一個名為A的kobjetc
結構體。即使再普通的喇叭也應該有個音量屬性,用來控制和顯示音量的大小,這個屬性可以在A目錄下用一個名為B的屬性文件來表示。很顯然,如果要控制喇叭的聲音大小,應該對B文件進行寫操作,將新的音量值寫入;如果要查看當前的音量,應該讀B文件。所以屬性文件B應該是一個可讀可寫的文件。3.初始化kobject的內(nèi)部成員函數(shù)kobject_init_internel()
在前面的函數(shù)kobject_init()
第18行,調(diào)用了kobject_init_internal()
函數(shù)初始化kobject
的內(nèi)部成員。該函數(shù)的代碼如下:
static void kobject_init_internal(struct kobject *kobj)
{
if(!kobj) /*如果kobj為空,則出錯退出*/
return
kref_init(&kobj->kref); /*增加kobjetc的引用計數(shù)*/
INIT_LIST_HEAD(&kobj->entry); /*初始化kobject的鏈表*/
kobj->state_in_sysfs = 0; /*表示kobject還沒注冊到sysfs中*/
kobj->state_add_uevent_sent = 0;/*始終初始化為0*/
kobj->state_remove_uevent_sent =0; /*始終初始化為0*/
kobj->state_initialized = 1; /*表示該結構體已經(jīng)初始化了*/
}
該函數(shù)主要對kobjetc
的內(nèi)部成員進行初始化,例如引用計數(shù)kref
,連接kobjetc
的entry
鏈表等。4.kobject結構體的引用計數(shù)操作
kobject_get()
函數(shù)是用來增加kobject
的引用計數(shù),引用計數(shù)由kobject
結構體的kref
成員表示。主要對象的引用計數(shù)大于等于1,對象就必須繼續(xù)存在。kobject_get()
函數(shù)代碼如下:
struct kobject *kobject_get(struct kobject *kobj)
{
if(kobj)
kref_get(&kobj->kref); /*增加引用計數(shù)*/
return kobj;
}
kobject_get()
函數(shù)將增加kobject
的引用計數(shù),并返回指向kobject
的指針。如果當kobject
對象已經(jīng)在釋放的過程中,那么kobject_get()
函數(shù)將返回NULL值。kobject_put()
函數(shù)用來減少kobject()
的引用計數(shù),當kobject
的引用計數(shù)為0時,系統(tǒng)就將釋放該對象和其占用的資源。前面講的kobject_init()
函數(shù)設置了引用計數(shù)為1,所以在創(chuàng)建kobject
對象時,就不需要調(diào)用kobject_get()
函數(shù)增加引用計數(shù)了。當刪除kobject
對象時,需要調(diào)用kobject_put()
函數(shù)減少引用計數(shù)。該函數(shù)的代碼如下:
void kobject_put(struct kobject *kobj)
{
if(kobj){
if(!kobj->state_initialized)
/*為初始化kobjet減少引用計數(shù),則出錯*/
WARN(1, KERNEL_WARNING "kobject: '%s' (%p): is not"
"initialized, yet kobject_put() is being"
"called.\n", kobject_name(kobj), kobj);
kref_put(&kobj->kref, kobject_release); /*減少引用計數(shù)*/
}
}
前面已經(jīng)說過,當kobject
的引用計數(shù)為0時,將釋放kobject
對象和其占用的資源。由于每一個kobject
對象所占用的資源都不一樣,所以需要驅動開發(fā)人員自己實現(xiàn)釋放對象資源的函數(shù)。該釋放函數(shù)需要在kobject
的引用計數(shù)為0時,被系統(tǒng)自動調(diào)用。kobject_put()
函數(shù)的第8行的kref_put()
函數(shù)的第二個參數(shù)指定了釋放函數(shù),該釋放函數(shù)是kobject_release()
,其由內(nèi)核實現(xiàn),其內(nèi)部調(diào)用了kobj_type
結構中自定義的release()
函數(shù)。由此可見kobj_type
中的release()
函數(shù)是需要驅動開發(fā)人員真正實現(xiàn)的釋放函數(shù)。從kobject_put()
函數(shù)到調(diào)用自定義的release()
函數(shù)的路徑如下圖所示。5.設置kobject名字的函數(shù)
用來設置kobject.name
的函數(shù)有兩個,分別是kobject_set_name()
和kobject_rename()
函數(shù),這兩個函數(shù)的原型如下:
int kobject_set_name(struct kobject *kobj, const char *fmt,...)
int object_rename(struct kobject *kobj, const char *new_name);
第一個函數(shù)用來直接設置kobject
結構體的名字。該函數(shù)的第一個參數(shù)是需要設置名字的kobject
對象,第二個參數(shù)是一個用來格式化名字的字符串,與C語言中printf()
函數(shù)的對應參數(shù)相似。
第2個函數(shù)用來當kobject
已經(jīng)注冊到系統(tǒng)后,如果一定要該kobject
結構體的名字時使用。
設備屬性kobj_type
每個kobject
對象都有一些屬性,這些屬性由kobj_type
結構體表示。最開始,內(nèi)核開發(fā)者考慮將屬性包含在kobject
結構體中,后來考慮到同類設備會具有相同的屬性,所以將屬性隔離開來,由kobj_type
表示。kobject
中有指向kobj_type
的指針,如下圖所示。
結合上圖解釋幾個重要的問題。
-
kobject
始終代表sysfs
文件系統(tǒng)中的一個目錄,而不是文件。對kobject_add()
函數(shù)的調(diào)用將在sysfs
文件系統(tǒng)中創(chuàng)建一個目錄。最底層目錄對應于系統(tǒng)中的一個設備、驅動或者其他內(nèi)容。通常一個目錄中包含一個或者多個屬性,以文件的方式表示,屬性由ktype
指向。 -
kobject
對象的成員name
是sysfs
文件系統(tǒng)中的目錄名。通常使用kobject_set_name()
函數(shù)來設置。在同一個目錄下,不能有相同的目錄名。 -
kobject
在sysfs
文件系統(tǒng)中的位置由parent
指針指定。parent
指針指向一個kobejct
結構體,kobject
對應一個目錄。 -
kobj_type
是kobject
的屬性。一個kobject
可以有一個或者多個屬性。屬性用文件來表示,放在kobejct
對應的目錄下。 -
atrribute
表示一個屬性,其具體定義將在下面介紹。 -
sysfs_ops
表示對屬性的操作函數(shù)。一個屬性只有兩種操作,一種是讀操作,一種是寫操作。1.屬性結構體kobj_type
當創(chuàng)建kobject
結構體的時候,會給kobject
一些默認的屬性。這些屬性保存在kobj_type
結構體中,該結構體定義如下:
struct kobj_type{
void (*release)(struct kobject *kobj); /*釋放kobject和其占用資源的函數(shù)*/
struct sysfs_ops *sysfs_ops; /*操作下一個屬性數(shù)組的方法*/
struct atrribute **default_attrs; /*屬性數(shù)組*/
}
kobj_type
的default_atrrs
成員保存了屬性數(shù)組,每一個kobject
對象可以有一個或者多個屬性。屬性結構體如下:
struct attribute{
const char *name; /*屬性的名稱*/
struct module *owner; /*指向擁有該屬性的模塊,已經(jīng)不常使用*/
mode_t mode; /*屬性的讀寫權限*/
}
在這個結構體中,name
是屬性的名字,對應某個目錄下的一個文件的名字。owner
指向實現(xiàn)這個屬性的模塊指針,就是驅動模塊的指針。在x86平臺上,已經(jīng)不推薦使用了。mode
是屬性的讀寫權限,也就是sysfs
中文件的學些權限。這些權限在<include/linux/stat.h>
文件中定義。S_IRUGO
表示屬性可讀;S_IWUGO
表示屬性可寫。2.操作結構體sysfs_ops
kobj_type
結構的字段default_attrs
數(shù)組說明了一個kobject
都有那些屬性,但是并沒有說明如何操作這些屬性。這個任務要使用kobj_type->sysfs_ops
成員來完成,sysfs_ops
結構體的定義如下:
struct sysfs_ops{
ssize_t (*show)(struct kobject *, struct attribute *, char *);
/*讀屬性操作函數(shù)*/
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
/*寫屬性操作函數(shù)*/
};
-
show()
函數(shù)用于讀取一個屬性到用戶空間。函數(shù)的第1個參數(shù)是要讀取的kobject
的指針,它對應要讀的目錄;第2個參數(shù)是要讀的屬性;第3個參數(shù)是存放讀到的屬性的緩存區(qū)。當函數(shù)調(diào)用成功后,會返回實際讀取的數(shù)據(jù)長度,這個長度不能超過PAGE_SIZE
個自己的大小。 -
store()
函數(shù)將屬性寫入內(nèi)核。函數(shù)的第一個參數(shù)是與寫相關的kobject
的指針,它對應要寫的目錄;第2個參數(shù)是要寫的屬性;第3個參數(shù)是要寫入的數(shù)據(jù);第4個參數(shù)是要寫入的參數(shù)長度。這個長度不能超過PAGE_SIZE
個字節(jié)大小。只有當擁有屬性有寫權限時,才能調(diào)用store()
函數(shù)。說明
:sysfs文件系統(tǒng)約定一個屬性不能太長,一般一至兩行左右,如果太長,需要把它分為多個屬性。
這兩個函數(shù)比較復雜,下面舉一個關于這兩個函數(shù)的例子。代碼如下:
/*該函數(shù)用來讀取一個屬性的名字*/
ssize_t kobject_test_show(struct kobject *kobject, struct attribute *attr, char *buf )
{
printk("call kobject_test_show().\n"); /*調(diào)試信息*/
printk("attrname:%s.\n", attr->name); /*打印屬性的名字*/
sprintf(buf,"%s\n",attr->name); /*將屬性名字存放在buf中,返回用戶空間*/
return strlen(attr->name + 2);
}
/*該函數(shù)用來寫入一個屬性的值*/
ssize_t kobject_test_store(struct kobject *kobject , struct attribute *attr, const char *buf, size_t count)
{
printk("call kobject_test_store().\n"); /*調(diào)試信息*/
printk("write: %s\n",buf); /*輸出要存入的信息*/
/*省略要寫入attr中的數(shù)據(jù)代碼,根據(jù)具體的邏輯定義*/
return count;
}
kobject_test_show()
函數(shù)將kobject
的名字賦給buf
,并返回給用戶空間。例如在用戶空間使用cat
命令查看屬性文件時,會調(diào)用kobejct_test_show()
函數(shù),并顯示kobject()
的名字。kobject_test_store()
函數(shù)用于將來自用戶空間的buf
數(shù)據(jù)寫入內(nèi)核,此處并沒有實際的寫入操作,可以根據(jù)具體的情況寫入一些需要的數(shù)據(jù)。3.kobj_type結構體的release()函數(shù)
在上面討論kobj_type
的過程中,遺留了一個重要的函數(shù),就是release()
函數(shù)。該函數(shù)表示當kobject
的引用計數(shù)為0時,將對kobject
采取什么樣的操作。對kobject_put()
函數(shù)的講解中,已經(jīng)對該函數(shù)做了鋪墊,該函數(shù)的原型如下:
void (*release)(struct kobject *kobj);
該函數(shù)的存在至少有兩個原因:第一,每一個kobject
對象在釋放時,可能都有一些不同的操作,所以并沒有統(tǒng)一的函數(shù)對kobject
及其包含的結構進行釋放操作。第二,創(chuàng)建kobject
的代碼并不知道什么時候釋放kobject
對象。所以kobject
維護了一個引用計數(shù),當計數(shù)為0時,則在合適的時候系統(tǒng)會自動調(diào)用自定義的release()
函數(shù)來釋放kobject
對象。一個release()
函數(shù)的模塊如下:
void kobject_test_release(struct kobject *kobject)
{
printk("kobject_test: kobject_test_release().\n");
struct my_object *myobject = container_of(kobject, struct my_object, kobj);
/*獲得my_object對象*/
kfree(myobject); /*釋放自定義的my_object對象,其中包含kobject對象*/
}
kobject
一般包含在一個更大的自定義結構中,這里就是my_object
對象。在驅動程序中,為了完成驅動的一些功能,該對象在系統(tǒng)中申請了一些資源,這些資源的釋放就在自定義的kobject_test_release()
中完成。
需要注意的是;每一個kobject
對象都有一個release()
方法,此方法會自動在引用計數(shù)為0時,被內(nèi)核調(diào)用,不需要程序員來調(diào)用。如果在引用計數(shù)不為0時調(diào)用,就會出現(xiàn)錯誤。4.非默認屬性
在許多的情況下,kobject
類型的default_attrs
成員定義了kobject
擁有的所有默認屬性。但是在特殊情況下,也可以對kobject
添加一些非默認的屬性,用來控制kobejct
代表的總線、設備和驅動的行為。例如為驅動的kobject
結構體添加一個屬性文件switch
,用來選擇驅動的功能。假設驅動有功能A和B,如果switch為A,那么選擇驅動A的功能,寫switch為B,則選擇驅動的B功能。添加非默認屬性的函數(shù)原型如下:
int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
如果函數(shù)執(zhí)行成功,則使用attribute
結構中的名字創(chuàng)建一個屬性文件,并返回0,否則返回一個負的錯誤碼。這里舉一個創(chuàng)建switch
屬性的例子,其代碼如下:
struct attribute switch_attr = {
.name = "switch", /*屬性名*/
.mode = S_IRWXUGO, /*屬性為可讀可寫*/
};
err = sysfs_create_file(kobj, switch_attr); /*創(chuàng)建一個屬性文件*/
if(err) /*返回非0,則出錯*/
printk(KERN_ERR "sysfs_create_file error");
內(nèi)核提供了sysfs_remove_file()
函數(shù)來刪除屬性,其函數(shù)原型如下:
void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr);
調(diào)用該函數(shù)成功,將在sysfs
文件系統(tǒng)中刪除attr
屬性指定的文件。當屬性文件刪除后,如果用戶空間的某一個程序仍然擁有該屬性文件的文件描述符,那么利用該文件描述符對屬性的操作會出現(xiàn)錯誤,需要引起開發(fā)者的注意。
注冊kobject到sysfs中的實例
為了對kobject對象有一個清晰的認識,這里將盡快給讀者展示一個完整的實例代碼。在講解這個實例代碼之前,需要重點講解一些到目前為止,我們需要知道的設備驅動結構。
設備驅動模型結構
在Linux設備驅動模型中,設備驅動模型在內(nèi)核中的關系用kobject
結構體來表示。在用戶空間的關系用sysfs
文件系統(tǒng)的結構來表示。如下圖,左邊是bus
子系統(tǒng)在內(nèi)核中的關系,使用kobject
結構體來組織。右邊是sysfs
文件系統(tǒng)的結構關系,使用目錄和文件來表示。左邊的kobject
和右邊的目錄或者文件是一一對應的關系,如果左邊有一個kobject
對象,那么右邊就對應一個目錄。文件表示該kobject
的屬性,并不與kobejct
相對應。
kset集合
kobject
通過kset
組織成層次化的結構。kset
是具有相同類型的kobejct
集合,像驅動程序一樣放在/sys/drivers
目錄下,目錄drivers
是一個kset
對象,包含系統(tǒng)中的驅動程序對應的目錄,驅動程序的目錄由kobject
表示。1.kset集合
kset結構體的定義如下:
struct kset{
struct list_head list; /*連接所包含的kobject對象的鏈表首部*/
spinlock_t list_lock; /*維護list鏈表的自旋鎖*/
struct kobject kobj; /*內(nèi)嵌的kobject結構體,說明kset本身也是一個目錄*/
struct kset_uevent_ops *uevent_ops; /*熱插拔事件*/
}
- 1行表示一個鏈表。包含在
kset
中的所有kobject
對象被組織成一個雙向循環(huán)鏈表,list
就是這個鏈表的頭部。 - 3行是用來從
list
中添加或者刪除kobejct
的自旋鎖 - 4行是一個內(nèi)嵌的
kobject
對象。所有屬于這個kset
集合的kobejct
對象的parent
指針,均指向這個內(nèi)嵌的kobject
對象。另外kset
的引用計數(shù)就是內(nèi)嵌的kobject
對象的引用計數(shù)。 - 5行是支持熱茶事件的函數(shù)集。
2.熱插拔事件kset_uevent_ops
一個熱插拔事件是從內(nèi)核空間發(fā)送到用戶空間的通知,表明系統(tǒng)某些部門的配置已經(jīng)發(fā)生變化。用戶空間接收到內(nèi)核空間的通知后,會調(diào)用相應的程序,處理配置的變化。例如,當U盤插入到USB系統(tǒng)時,會產(chǎn)生一個熱插拔事件,內(nèi)核會捕獲這個熱插拔事件,并調(diào)用用戶空間的/sbin/hotplug
程序,該程序通過加載驅動程序來響應U盤插入的動作。
在早期的系統(tǒng)中,如果要加入一個新設備,必須要關閉計算機,插入設備,然后再重啟,這是一個非常繁瑣的過程?,F(xiàn)在計算機系統(tǒng)的硬軟件已經(jīng)有能力支持設備的熱插拔,這種特性帶來的好處是,設備可以即插即用,節(jié)省用戶的時間。
內(nèi)核將在什么時候產(chǎn)生熱插拔時間呢?當驅動程序將kobject
注冊到設備驅動模型時,會產(chǎn)生這些事件。也就是當內(nèi)核調(diào)用kobject_add()
和kobject_del()
函數(shù)時,會產(chǎn)生熱插拔事件。熱插拔事件產(chǎn)生時,內(nèi)核會根據(jù)kobject
的kset
指針找到所屬的kset
結構體,執(zhí)行kset
結構體中uevent_ops
包含的熱插拔函數(shù)。這些函數(shù)的定義如下:
struct kset_uevent_ops{
int (*filter)(struct kset *kset, struct kobject *kobj);
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
- 2行的
filter()
函數(shù)是一個過濾函數(shù)。通過filter()
函數(shù),內(nèi)核可以決定是否向用戶空間發(fā)送事件產(chǎn)生信號。如果filter()
返回0,表示不產(chǎn)生事件;如果filter()
返回1,表示產(chǎn)生事件。例如,在塊設備子系統(tǒng)中可以使用該函數(shù)決定那些事件應該發(fā)送給用戶空間。在塊設備子系統(tǒng)中至少存在3種類型的kobject
結構體:磁盤、分區(qū)和請求隊列。用戶空間需要對磁盤和分區(qū)的改變產(chǎn)生響應,但一般不需要對請求隊列的變化產(chǎn)生響應。在把事件發(fā)送給用戶空間時,可以使用filter()
函數(shù)過濾不需要產(chǎn)生的事件。塊設備子系統(tǒng)的過濾函數(shù)如下:
static int dev_uevent_filter(struct kset *kset, struct kobject *kobj)
{
int ret;
struct kobj_type *ktype = get_ktype(kobj); /*得到kobject屬性的類型*/
ret =(ktype == &ktype_block) || (ktype == &ktype_part); /*判斷是否磁盤或分區(qū)事件*/
return ret; /*0表示過濾,非0表示不過濾*/
}
- 3行的
name()
函數(shù)在用戶空間的熱插拔程序需要知道子系統(tǒng)的名字時被調(diào)用。該函數(shù)將返回給用戶空間程序一個字符串數(shù)據(jù)。該函數(shù)的一個例子是dev_uevent_name()
函數(shù),代碼如下:
static const char *dev_uevent_name(struct kset *kset, struct kbject *kobj)
{
struct device *dev = to_dev(kobj);
if(dev->bus)
return dev->bus->name;
if(dev->class)
return dev->class->name;
return NULL;
}
該函數(shù)先由kobj
獲得device
類型的dev
指針。如果該設備的總線存在,則返回總線的名字,否則返回設備類的名字。
- 任何熱插拔程序需要的信息可以通過環(huán)境變量來傳遞。
uevent()
函數(shù)可以在熱插拔程序執(zhí)行前,向環(huán)境變量中寫入值。
kset與kobject的關系
kset
是kobject
的一個集合,用來與kobject
建立層次關系。內(nèi)核可以將相似的kobject
結構連接在kset
集合中,這些相似的kobject
可能有相似的屬性,使用統(tǒng)一的kset
來表示。下圖顯示了kset
集合和kobject
之間的關系。
-
kset
集合包含了屬于其的kobject
結構體,kset.list
鏈表用來連接第一個和最后一個kobject
對象。第一個kobject
使用entry
連接kset
集合和第二個kobejct
對象。第二個kobject
對象使用entry
連接第一個kobject
對象和第三個kobject
對象,依次類推,最終形成一個kobject
對象的鏈表。 - 所有
kobject
結構的parent
指針指向kset
包含的kobejct
對象,構成一個父子層次關系。 -
kobject
的所有kset
指針指向包含它的kset
集合,所以通過kobject
對象很容易就能找到kset
集合。 -
kobject
的kobj_type
指針指向自身的kobj_type
,每一個kobject
都有一個單獨的kobj_type
結構。另外在kset
集合中也有一個kobject
結構體,該結構的xxx也指向一個kobj_type
結構體。從前文知道,kobj_type
中定義了一組屬性和操作屬性的方法。這里需要注意的是,kset
和kobj_type
的優(yōu)先級要高于kobject
對象中的kobj_type
的優(yōu)先級。如果兩個kobj_type
都存在,那么優(yōu)先調(diào)用kset
中的函數(shù)。如果kset
中的kobj_type
為空,才調(diào)用各個kobject
結構體自身對應的kobj_type
中的函數(shù)。 -
kset
中的kobj
也負責對kset
的引用計數(shù)。
kset相關的操作函數(shù)
kset
相關的操作函數(shù)與kobject
的函數(shù)相似,也有初始化、注冊和注銷扽函數(shù)。下面對這些函數(shù)進行介紹。1.初始化函數(shù)kset_init()
kset_init()
函數(shù)用來初始化kset
對象的成員,其中最重要的是初始化kset.kobj
成員,使用上面介紹過的kobject_init_internal()
函數(shù)。
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj); /*初始化kset.kobj成員*/
INIT_LIST_HEAD(&k->list); /*初始化連接kobject的鏈表*/
spin_lock_init(&k->list_lock); /*初始化自旋鎖,該鎖用于對kobject的添加、刪除等操作*/
}
2.注冊函數(shù)kset_register()
kset_register()
函數(shù)用來完成系統(tǒng)對kset
的注冊,函數(shù)原型如下:
int kset_register(struct kset *k);
3.注銷函數(shù)kset_unregister()
kset_unregister()
函數(shù)用來完成系統(tǒng)對kset
的注銷,函數(shù)的原型如下:
void kset_unregister(struct kset *k);
4.kset的引用計數(shù)
kset
也有引用計數(shù),該引用計數(shù)由kset
的kobj
成員來維護??梢允褂?code>kset_get()函數(shù)增加引用計數(shù),使用kset_put()
函數(shù)減少引用計數(shù)。這兩個函數(shù)的原型如下:
static inline struct kset *kset_get(struct kset *k);
static inline void kset_put(struct kset *k);
注冊kobject到sysfs中的實例
對kobject
和kset
有所了解后,本節(jié)將講解一個實例程序,以使讀者對這些概念有更清楚的認識。這個實例程序的功能是:在/sys
目錄下添加一個名為kobject_test
的目錄名,并在該目錄下添加一個名為kobject_test_attr
的文件,這個文件就是屬性文件。本實例可以通過kobject_test_show()
函數(shù)實現(xiàn)顯示屬性的值;也可以通過kobejct_test_store()
函數(shù)向屬性中寫入一個值。這里實例的完整代碼如下:
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
/*釋放kobejct結構體的函數(shù)*/
void kobject_test_release(struct kobject *kobject);
/*讀屬性的函數(shù)*/
ssize_t kobject_test_show(struct kobject *kobject, struct attribute *attr, char *buf);
/*寫屬性的函數(shù)*/
ssize_t kobject_test_store(struct kobject *kobject, struct attribute *attr,const char *buf, size_t count);
/*定義了一個名為kobject_test,可讀可寫的屬性*/
struct attribute test_attr = {
.name = "kobject_test", /*屬性名*/
.mode = S_IRWXUGO, /*屬性為可讀可寫*/
};
/*該kobject只有一個屬性*/
static struct attribute *def_attrs[]={
&test_attr,
NULL,
};
struct sysfs_ops obj_test_sysops = {
.show = kobject_test_show, /*屬性讀函數(shù)*/
.store = kobject_test_store, /*屬性寫函數(shù)*/
};
struct kobj_type ktype={
.release = kobject_test_release, /*釋放函數(shù)*/
.sysfs_ops = &obj_test_sysfs , /*屬性操作函數(shù)*/
.default_attr = def_attrs, /*默認屬性*/
};
void kobject_test_release(struct kobject *kobject)
{
/*這只是一個示例,實際代碼要復雜很多*/
printk("kobject_test: kobject_test_release().\n");
}
/*該函數(shù)用來讀取一個屬性的名字*/
ssize_t kobject_test_show(struct kobject *kobject, struct attribute *attr, char *buf)
{
printk("call kobject_test_show().\n"); /*調(diào)試信息*/
printk("attrname:%s.\n",attr->name); /*打印屬性名*/
sprintf(buf, "%s\n",attr->name); /*將屬性名字存放在buf中,返回用戶空間*/
return strlen(attr->name)+2;
}
/*該函數(shù)用來寫入一個值*/
ssize_t kobject_test_store(struct kobject *kobject, struct attribute *attr, const char *buf, size_t count)
{
printk("call kobject_test_store().\n"); /*調(diào)試信息*/
printk("write:%s\n",buf); /*輸出要存儲的信息*/
strcpy(attr->name, buf); /*寫一個屬性*/
return count;
}
struct kobject kobj; /*要添加的kobject結構*/
static int kobject_test_init()
{
printk("kobject test_init().\n");
kobject_init_and_add(&kobj, &ktype, NULL, "kobject_test"); /*初始化并添加kobject到內(nèi)核*/
return 0;
}
static int kobject_test_exit()
{
printk("kobject test exit.\n");
kobject_del(&kobj); /*刪除kobject*/
return 0;
}
module_init(kobject_test_init);
module_exit(kobject_test_exit);
MODULE_AUTHOR("xxxx");
MODULE_LICENSE("Dual BSD/GPL")
下面對示例的一些擴展知識進行簡要介紹。1.kobject_init_and_add()
函數(shù)
加載函數(shù)kobject_test_init()
調(diào)用kobject_init_and_add()
函數(shù)來初始化和添加kobject
到內(nèi)核中。函數(shù)調(diào)用成功后將在/sys
目錄下新建一個kobject_test
的目錄,這樣就構建了kobject
的設備層次模型。這個函數(shù)主要完成了如下兩個功能:
- 調(diào)用
kobject_init()
函數(shù)對kobject
進行初始化,并將kobject
與kobj_type
關聯(lián)起來。 - 調(diào)用
kobject_add_varg()
函數(shù)將對kobject
加入設備驅動層次模型中,并設置一個名字。kobject_init_and_add()
函數(shù)的代碼如下:
int kobject_init_and_add(struct kobject *kobj, struct kobj_ktype *ktype, struct kobject *parent, const char *fmt, ...)
{
va_list args; /*參數(shù)列表*/
int retval; /*返回值*/
kobject_init(kobj, ktype); /*初始化kobject結構體*/
va_start(args, fmt); /*開始解析可變參數(shù)列表*/
retval = kobject_add_varg(kobj, parent, fmt, args); /*給kobj添加一些參數(shù)*/
va_end(args); /*結束解析參數(shù)列表*/
return retval;
}
- 參數(shù)說明:第一個參數(shù)kobj是指向要初始化的
kobject
結構體;第2個參數(shù)ktype
是指向要與kobj
聯(lián)系的kobj_type
。第3個參數(shù)指定kobj
的父kobject
結構體;第4,5個參數(shù)是XXXX - 6行的
kobject_init()
函數(shù)已經(jīng)在前面介紹過了。 - 8行調(diào)用
kobject_add_varg()
函數(shù)向設備驅動模型添加一個kobject
結構體。這個函數(shù)比較復雜,將在后面介紹。2.將kobject加入設備驅動模型中的函數(shù)kobject_add_varg()
kobject_add_varg()
函數(shù)將kobject
加入驅動設備模型中。函數(shù)的第1個參數(shù)kobj
是要加入設備驅動模型中的kobject
結構體指針;第2個參數(shù)是該kobject
結構體的父結構體,該值為NULL,表示在/sys
目錄下創(chuàng)建一個目錄,本實例就是這種情況;第3,4個參數(shù)與printf()
函數(shù)的參數(shù)相同,接收一個可變參數(shù),這里用來設置kobject
的名字。kobject_add_vag()
函數(shù)的代碼如下:
static int kobject_add_vag(struct kobject *kobj, struct kobject *parent, const char *fmt, va_list vargs)
{
int retval; /*返回值*/
retval = kobject_set_name_vargs(kobj, fmt, vargs); /*給kobject賦新的名字*/
if(retval){
printf(KERN_ERR "kobect: can not set name properly!\n");
return retval;
}
kobj->parent = parent; /*設置kobject的父kobject結構體*/
return kobject_add_internal(kobj);
}
- 5~9行,設置將要加入
sysyfs
文件系統(tǒng)中的kobject
的名字。本實例的名字是kobject_test
。將在sysfs
文件系統(tǒng)中加入一個kobject_test
的目錄。 - 10行設置
kobject
的父kobject
結構體。也就是kobject_test
的父目錄,如果parent
為NULL,那么將在sysfs
文件系統(tǒng)頂層目錄中加入kobject_test
目錄。表示沒有父目錄。 - 11行調(diào)用
kobject_add_internal()
函數(shù)向設備驅動模型中添加kobject
結構體。3.kobject添加函數(shù)kobject_add_internal()
kobject_add_internal()
函數(shù)負責向設備驅動模型中添加kobject
結構體,并在sysfs
文件系統(tǒng)中創(chuàng)建一個目錄。該函數(shù)的代碼如下:
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if(!obj) /*為空,則失敗,表示沒有需要添加的kobject*/
return -ENOENT;
if(!kobj->name | !kobj->name[0]) {
/*kobject沒有名字,不能注冊到設備驅動模型中*/
WARN(1, "kobject: (%p): attempted to be registered with empty"
"name!\n", kobj);
return -EINVAL;
}
parent = kobject_get(kobj->parent); /*增加父目錄的引用計數(shù)*/
if(kobj->kset){ /*是否屬于一個kset集合*/
if(!parent) /*如果kobject本身沒有父kobject,
則使用kset的kobject作為kobject的父親*/
parent = kobject_get(&kobj->kset->kobj); /*增加引用計數(shù)*/
kobj_kset_join(kobj);
kobj->parent = parent; /*設置父kobject結構*/
}
/*打印調(diào)試信息:kobject名字、對象地址、該函數(shù)名;父kobject名字;kset集合名字*/
pr_debug("kobject:'%s' (%p): %s : parent: %s , set :'%s'\n",
kobject_name(kobj), kobj, __func__,
parent? kobject_name(parent): "<NULL>");
error = create_dir(kobj); /*創(chuàng)建一個sysfs目錄,該目錄名字為kobj_name*/
if(error){ /*以下為創(chuàng)建目錄失敗的函數(shù)*/
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/*be noisy on error issues*/
if(error == -EEXIST)
peintk(KERNEL_ERR "%s failed for %s with"
"-EEXIST, don't try to register things with"
"the same name in the same directory.\n",
__func__, kobject_name(kobj));
else
printk(KERN_ERR "%s failed for %s (%d)\n",
__func__, kobject_name(kobj), error);
dump_stack();
}else
kobj->state_in_sysfs = 1; /*創(chuàng)建成功,表示kobject在sysfs中*/
return error;
}
4.刪除kobject對象的kobject_del()函數(shù)
kobject_del()
函數(shù)用來從設備驅動模型中刪除一個kobject
對象,本實例中該函數(shù)在卸載函數(shù)kobject_test_exit()
中調(diào)用。具體來說,kobject_del()
函數(shù)主要完成以下3個工作:
- 從
sysfs
文件系統(tǒng)中刪除kobject
對應的目錄,并設置kobject
的狀態(tài)為沒有在sysfs
中。 - 如果
kobject
屬于一個kset
集合,則從kset
中刪除。 - 減少
kobject
的相關引用計數(shù)。kobject_del()
函數(shù)代碼如下:
void kobject_del(struct kobject *kobj)
{
if(!kobj) /*為空,則退出*/
return;
sysfs_remove_dir(kobj); /*從sysfs文件系統(tǒng)中刪除kobj對象*/
kobj->state_in_sysfs = 0; /*表示該kobj沒有在sysfs中*/
kobj_kset_leave(kobj); /*如果kobj對象屬于一個kset集合,則從集合中刪除*/
kobject_put(kobj->parent); /*減少父目錄的引用計數(shù)*/
kobj->parent = NULL ; /*將父目錄設為NULL*/
}
5.釋放函數(shù)kobject_test_release()
前面已經(jīng)說過每一個kobject
都有自己的釋放函數(shù),本例的釋放函數(shù)是kobject_test_release()
,該函數(shù)除打印一條信息之外,什么也沒有做。因為這個例子并不需要做其他工作,在實際的項目中該函數(shù)可能較為復雜。6.讀寫屬性函數(shù)
本例有一個test_attr
的屬性,該屬性的讀寫函數(shù)分別是kobject_test_show()
和kobject_test_strore()
。分別用來向屬性test_attr
中讀出和寫入屬性名。
實例測試
使用make
命令編譯kobject_test.c
文件,得到kobject_test.ko
模塊,然后使用insmod
命令加載該模塊。當模塊加載后會在/sys
目錄中增加一個kobject_test
的目錄,如下所示。
進入kobject_test
目錄,在該目錄下有一個名為kobject_test_attr
屬性文件,如下所示。
使用echo
命令和cat
命令可以對這個屬性文件進行讀寫,讀寫時,內(nèi)核里調(diào)用的分別是kobject_test_show()
和kobject_test_store()
函數(shù)。這兩個函數(shù)分別用來顯示和設置屬性的名字,測試過程如下:
設備驅動模型的三大組件
設備驅動模型有三大重要組件,分別是總線(bus_type
)、設備(device
)和驅動(driver
)。下面對這三個重要的組件進行分別介紹。
總線
從硬件結構上來講,物理總線有數(shù)據(jù)總線和地址總線
。物理總線是處理器與一個或者多個設備之間的通道。在設備驅動模型中,所有設備都通過總線連接,此處的總線與物理總線不同,總線是物理總線的一個抽象,同時還包含一些硬件中不存在的虛擬地址總線。在設備驅動模型中,驅動程序是附屬在總線上的。下面將首先介紹總線、設備和驅動之間的關系。1.總線、設備、驅動關系
在設備驅動模型中,總線、設備和驅動三者之間緊密聯(lián)系。如下圖所示,在/sys
目錄下,有一個bus
目錄,所有的總線都在bus
目錄下有一個新的子目錄。一般一個總線目錄有一個設備目錄、一個驅動目錄和一些總線屬性文件。設備目錄中包含掛接在該總線上的設備,驅動目錄包含掛接在總線上的驅動程序。設備和驅動程序之間通過指針互相聯(lián)系。
如上圖所示,總線上的設備鏈表有3個設備,設備1、設備2和設備3??偩€上的驅動鏈表也有3個驅動程序,驅動1、驅動2和驅動3.其中虛線箭頭表示設備與驅動的綁定關系,這個綁定是在總線枚舉設備時設置的。這里,設備1與驅動2綁定,設備2與驅動1綁定,設備3與驅動3綁定。2.總線數(shù)據(jù)結構bus_type
在Linux設備模型中,總線用bus_type
表示。內(nèi)核支持的每一條總線都由一個bus_type
對象來描述。
struct bus_type{
const char *name; /*總線支持的名稱*/
struct bus_attribute *bus_attrs; /*總線屬性和導出到sysfs的方法*/
struct device_attribute *dev_attrs; /*設備屬性和導出到sysfs的方法*/
struct driver_attribute *drv_attrs; /*驅動程序屬性和導出到sysfs中的方法*/
/*匹配函數(shù),檢驗參數(shù)2中的驅動是都支持參數(shù)1中的設備*/
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent) (struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev); /*探測設備*/
int (*remove)(struct device *dev); /*移除設備*/
int (*shutdown)(struct device *dev); /*關閉函數(shù)*/
int (*suspend)(struct device *dev, pm_message_t state);
/*改變設備供電狀態(tài),使其節(jié)能*/
int (*suspend_late)(struct device *dev, pm_message_t state); /*掛起函數(shù)*/
int (*resume_early)(struct device *dev); /*喚醒函數(shù)*/
int (*resume)(struct device *dev); /*恢復供電狀態(tài),使設備正常工作的方法*/
struct dev_pm_ops *pm; /*關于電源管理的操作符*/
struct bus_type_private *p; /*總線私有數(shù)據(jù)*/
};
- 2行的
name
成員是總線的名字,例如PCI。 - 3~5行分別是三個屬性,與kobject對應的屬性類似。設備驅動模型的每一個層次都有一個屬性。
- 6~15行是總線匹配、探測、電源管理等相關的函數(shù)。在具體用到時,將詳細解釋。
- 16行是
dev_pm_ops
是與電源管理相關的函數(shù)集合 - 17行的是
bus_type_private
表示的是總線的私有數(shù)據(jù)。3.bus_tye
聲明實例
在Linux中,總線不僅是物理總線的抽象,還代表一些虛擬的總線。例如,平臺設備總線(platform
)就是虛擬總線。值得注意的是bus_type
中的很少成員需要自己定義,內(nèi)核復雜完成大部分的功能。例如ac97
聲卡的總線定義就非常簡單,如果去掉電源管理的函數(shù),那么ac97
總線就只有match()
函數(shù)的定義,其總線代碼如下:
struct bud_type ac97_bus_type = {
.name = "ac97",
.match = ac97_bus_match,
#ifdef_ CONFIG_PM
.suspend = ac97_bus_suspend,
.resume = ac97_bus_resume,
#endif /*OCNFIG_PM*/
};
4.總線私有數(shù)據(jù)bus_type_private
總線私有數(shù)據(jù)結構bus_type_private
包含3個主要的成員。一個kset
的類型的subsys
容器,表示一條總線的主要部分;一個總線上的驅動程序容器drivers_kset
;一個總線上的設備容器devices_kset
。
struct bus_type_private{
struct kset subsys; /*代表該bus子系統(tǒng),里面的kobj是該bus的主kobj,也就是最頂層*/
struct kset *drivers_kset; /*掛接到該總線上的所有驅動集合*/
struct kset *device_kset; /*掛接到總線上的所有設備集合*/
struct klist klist_devices; /*所有設備的列表,與devices_kset中的list相同*/
struct klist klist_drivers; /*所有驅動程序的列表,與drivers_kset中的list相同*/
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1; /*設置是否在驅動注冊時,自動探測(probe)設備*/
struct bus_type *bus; /*回指包含自己的總線*/
};
5.總線注冊bus_register()
如果為驅動程序定義了一條新的總線,那么需要調(diào)用bus_register()
函數(shù)進行注冊。這個函數(shù)有可能會調(diào)用失敗,所以有必要檢測它的返回值。如果函數(shù)調(diào)用成功,那么一條新的總線將被添加到系統(tǒng)中??梢栽?code>sysyfs文件系統(tǒng)的/sys/bus
目錄下看到它。該函數(shù)的代碼如下:
int bus_register(struct bus_type *bus)
{
int retval; /*返回值*/
struct bus_type_private *priv; /*總線私有數(shù)據(jù)*/
priv = kzmalloc(sizeof(struct bus_type_private), GFP_KERNEL);
/*申請一個總線私有數(shù)據(jù)*/
if(!priv) /*內(nèi)存不足*/
return -EIOMEM;
priv->bus = bus; /*總線私有數(shù)據(jù)結構回指的總線*/
bus->p = priv; /*總線私有數(shù)據(jù)*/
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifer); /*初始化通知鏈表*/
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
/*設置總線的名字,例如PCI*/
if(retval) /*失敗則返回*/
goto out;
priv->subsys.kobj.kset = bus_kset;
/*指向其父kset,bus_kset在buses_init()例程中添加*/
priv->subsys.kobj.ktype = &bus_type; /*設置讀取總線屬性文件的默認方法*/
priv->drivers_autoprobe = 1; /*驅動程序注冊時,可以探測(probe)設備*/
retval = kset_register(&priv->subsys); /*注冊總線容器priv->subsys*/
if(retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);
/*建立uevent屬性文件*/
if(retval)
goto bus_uevnt_fail;
/*創(chuàng)建一個devices_kset容器。也就是在新的總線目錄下創(chuàng)建一個devices的目錄,其父
目錄就是priv->subsys.kobj對應的總線目錄*/
priv->device_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);
if(!priv->devices_kset){
retval = -ENOMEN;
goto bus_devices_fail;
}
/*創(chuàng)建一個drivers_kset容器。也就是在新的總線目錄下創(chuàng)建一個drivers的目錄,其父
目錄就是priv->subsys.kobj對應的總線目錄*/
priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);
if(!priv->drivers_kset){
retval = -ENOMEN;
goto bus_drivers_fail;
}
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
/*初始化設備鏈表*/
klist_init(&priv->klist_drivers, NULL, NULL);/*初始化驅動程序鏈表*/
retval = add_probe_file(bus); /*與熱插拔相關的探測文件*/
if(retval)
goto bus_probe_file_fail;
retval = bus_add_attrs(bus); /*為總線創(chuàng)建一些屬性文件*/
if(retval)
goto bus_attrs_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
/*錯誤處理*/
bus_attrs_fail:
kset_unregister(bus->p->drivers_kset)
bus_probe_files_fail:
remove_probe_file(bus);
bus_drivers_fail:
kset_unregister(bus->p->device_kset);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
kfree(bus->p)
out:
return retval;
bus_register()
函數(shù)對bus_type
進行注冊,當從系統(tǒng)中刪除一條總線時,應該使用bus_unregister()
函數(shù),該函數(shù)原型如下:
void bus_unregister(struct bus_type *bus)
總線屬性和總線方法
bus_type
中還包含表示總線屬性和總線方法的成員。屬性使用成員bus_attrs
表示,相對該成員介紹如下:1.總線的屬性bus_attribute
在Linux設備驅動模型中,幾乎每一層都有添加屬性的函數(shù),bus_type
也不例外??偩€屬性用bus_attribute
表示,由bus_type
的bus_attr
指針指向。bus_atrribute
屬性如以下代碼所示:
struct bus_attribute{
struct attribute attr; /*總線屬性*/
ssize_t (*show) (struct bus_type *bus, char *buf); /*屬性讀函數(shù)*/
/*屬性寫函數(shù)*/
ssize_t (*store) (struct bus_type *bus, const char *buf, size_t count);
};
bus_attribute
中的attribute
屬性與kobject
中的屬性結構體是一樣的。bus_attribute
總線屬性也包含兩個顯示和設置屬性值的函數(shù),分別是show()
和store()
函數(shù)??梢允褂?code>BUS_ATTR宏來初始化一個bus_attribute
結構體,該宏的定義如下:
#define BUS_ATTR(name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name= __ATTR(_name, _mode, _show, _store)
此宏有4個參數(shù),分別是屬性名、屬性讀寫模式、顯示屬性和存儲屬性。例如定義了一個名為bus_attr_config_time
的屬性,可以寫成如下形式:
static BUS_ATTR(config_time, 0644, ap_config_time_show, ap_config_time_store);
對該宏進行擴展,就能得到bus_attr_config_time
屬性如下的定義:
struct bus_attribute bus_attr_config_time = {
.attr = {.name = config_time, .mode = 0644},
.show = ap_config_time_show,
.store = ap_config_time_store,
}
2.創(chuàng)建和刪除總線屬性
創(chuàng)建總線屬性,需要調(diào)用bus_create_file()
函數(shù),該函數(shù)的原型如下:
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);
當不需要某個屬性時,可以使用gbus_remove_file()
函數(shù)刪除該屬性,該函數(shù)的原型如下:
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);
3.總線上的方法
在bus_type
結構體中,定義了許多方法。這些方法都是與總線相關的,例如電源管理,新設備與驅動匹配的方法。這里主要介紹match()
函數(shù)和uevent()
函數(shù),其他函數(shù)在驅動中幾乎不需要使用。match()
函數(shù)的原型如下:
int (*match)(struct device *dev, struct device_driver *drv);
當一條總線上的新設備或者新驅動被添加時,會依次或多次調(diào)用該函數(shù)。如果指定的驅動程序能夠適用于指定的設備,那么該函數(shù)返回非0值,否則,返回0。當定義一種新總線時,必須實現(xiàn)該函數(shù),以使內(nèi)核知道怎樣匹配設備和驅動程序。一個match()
函數(shù)的例子如下:
static bbtv_sub_bus_match(struct device *dev, struct device_driver *drv)
{
struct bttv_sub_driver *sub = to_bttv_sub_drv(drv);/*轉換為自定義驅動*/
int len = strlen(sub->wanted); /*取驅動能支持的設備名長度*/
if(0 == strncmp(dev_name(dev), sub->wanted, len))
/*新添加的設備名是否與驅動支持的設備名相同*/
/*如果總線上的驅動支持該設備,則返回1,否則返回0*/
return 1;
return 0;
}
當用戶空間產(chǎn)生熱插拔事件前,可能需要內(nèi)核傳遞一些參數(shù)給用戶空間程序,這里只能使用環(huán)境變量來傳遞參數(shù)。傳遞環(huán)境變量的函數(shù)由uevent()
實現(xiàn)。該函數(shù)的原型入下:
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
該函數(shù)只有在內(nèi)核支持熱插拔事件(CONFIG_HOTPLUG
)時,才有用,否則該函數(shù)被定義為了NULL值。以amba_uevent()
函數(shù)為例,該函數(shù)只有在支持熱插拔時,才被定義。函數(shù)體中調(diào)用了add_uevent_var()
函數(shù)添加了一個新的環(huán)境變量,代碼如下:
#ifdef CONFIG_HOTPLUG
static int amba_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct amba_device *pcdev = to_amba_device(dev);
/*由device轉換為自定義的設備結構*/
int retval = 0;
/*向env中添加一個新的變量AMBA_ID*/
retval = add_uevent_var(env, "AMBA_ID=%08x", pcdev->perophid);
return retval;
}
#else
#define amba_uevent NULL /*不支持熱插拔事件*/
#endif
設備
在Linux設備驅動模型中,每一個設備都由一個device
結構體來描述。device
結構體包含了設備所具有的一些通用信息。對于驅動開發(fā)人員來說,當遇到新設備時,需要定義一個新的設備結構體,將device
作為新結構體成員。這樣就可以在新結構體中定義新設備的一些信息,而設備通用的信息就使用device
結構體來表示。使用device
結構體的另一個好處是,可以通過device
輕松地將新設備加入設備驅動模型的管理中。下面對device結構體進行簡要的介紹。1.device結構體
device
中的大多函數(shù)被內(nèi)核使用,驅動開發(fā)人員不需要關注,這里只對該結構體主要成員進行介紹。該結構體的主要成員如下:
struct device {
struct klist klist_children; /*連接子設備的鏈表*/
struct device *parent; /*指向父設備的指針*/
struct kobject kobj; /*內(nèi)嵌的kobject結構體*/
char bus_id[BUS_ID_SIZE]; /*連接到總線上的位置*/
unsigned uevent_suppress:1; /*是否支持熱插拔事件*/
const char *init_name; /*設備的初始化名字*/
struct device_type *type; /*設備相關的特殊處理函數(shù)*/
struct bus_type *bus; /*指向連接的總線指針*/
struct device_driver *driver; /*指向該設備的驅動程序*/
void *driver_data; /*指向驅動程序私有數(shù)據(jù)的指針*/
struct dev_pm_info power; /*電源管理信息*/
dev_t devt; /*設備號*/
struct clss *class; /*指向設備所屬類*/
struct attribute_group **groups; /*設備的組屬性*/
void (*release)(struct device *dev); /*釋放設備描述符的回調(diào)函數(shù)*/
};
- 3行指向父設備,設備的父子關系表示,子設備離開了父設備就不能工作。
- 5行的
bus_id
字段,表示總線上一個設備的名字。例如PCI設備使用了標準的PCI ID格式,其格式為:域編號、總線編號、設備編號和功能編號。 - 8行的
device_type
結構中包含了一個用來對設備操作的函數(shù) - 9行的
bus
指針指向設備所屬的總線。 - 10行的
driver
指針指向設備的驅動程序。 - 16行的
release
函數(shù)。當指向設備的最后一個引用被刪除時,內(nèi)核會調(diào)用該方法。所有向內(nèi)核注冊的device
結構都必須有一個release()
方法,否則內(nèi)核就會打印出錯信息。2.設備注冊和注銷
設備必須注冊后,才能使用。在注冊device
結構之前,至少要設置parent、bus_id、bus和release
成員。常用的注冊和注銷函數(shù)如下代碼所示:
int device_register(struct device *dev);
void device_unregister(struct device *dev);
為了使讀者對設備注冊有一個清楚的認識,下面的代碼完成一個簡單的設備注冊。
static void test_device_release(struct device *dev) /*釋放device的函數(shù)*/
{
printk(KERN_DEBUG"test_device release().\n");
}
/*設備結構體*/
struct device test_device ={
.bus_id ="test_device"
.release = test_device_release,
.parent=NULL
};
int ret;
ret = device_register(&test_device); /*注冊設備結構體*/
if(ret)
printk(KERN_DEBUG"register is error");
這段代碼完成一個設備注冊,其parent
和bus
成員都是NULL。設備的名字是test_device
。釋放函數(shù)是test_device_release()
并不做任何實質(zhì)工作。這段代碼調(diào)用成功后,會在sysfs
文件系統(tǒng)的/sys/device
目錄中,看到一個新的目錄test_device
,該目錄就對應這里注冊的設備。
設備的注銷函數(shù)是device_unregister()
,該函數(shù)的原型如下:
voif device_unregister(struct device *dev);
3.設備屬性
每一個設備都可以有一些屬性,在sysfs
文件系統(tǒng)中以文件的形式來表示。設備屬性的定義如下:
struct device_attribute{
struct attribute attr; /*屬性*/
/*顯示屬性的方法*/
ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf);
/*設置屬性的方法*/
ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf,
size_t count);
};
在寫程序時,可以使用宏DEVICE_ATTR
定義attribute
結構,這個宏的定義如下:
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode. _show, _store)
該宏使用dev_attr_
作為前綴構造屬性名,并傳遞屬性的讀寫模式,讀函數(shù)和寫函數(shù)。另外可以使用下面兩個函數(shù)對屬性文件進行實際的處理。
int device_create_file(struct device *device, struct device_attribute *entry);
void device_remove_file(struct device *dev, struct device_attribute *attr);
device_create_file()
函數(shù)用來在device
所在的目錄下創(chuàng)建一個屬性文件;device_remove_file()
函數(shù)用來在device
所在的目錄下刪除一個屬性文件。
驅動
在設備驅動模型中,記錄了注冊到系統(tǒng)中的所有設備。有些設備可以使用,有些設備不可以使用,原因是設備需要與對應的驅動程序綁定才能使用,本節(jié)將重點介紹設備驅動程序。1.設備驅動device_driver
一個設備對應一個最合適的設備驅動程序。但是,一個設備驅動程序就有可能適用多個設備。設備驅動模型自動地探測新設備的產(chǎn)生,并為其分配最合適的設備驅動程序,這樣新設備就能夠適用了。驅動程序由以下結構體定義:
struct device_driver{
const char *name; /*設備驅動程序的名字*/
struct bus_type *bus; /*指向驅動屬于的總線,總線上有很多的設備*/
struct module *owner; /*設備驅動自身模塊*/
const char *mod_name; /*驅動模塊的名字*/
/*探測設備的方法,并檢測設備驅動可以控制那些設備*/
int (*probe)(struct device *dev);
int (*remove)(struct device *dev); /*移除設備時調(diào)用該方法*/
void (*shutdown)(struct device *dev); /*設備關閉時調(diào)用的方法*/
int (*suspend)(struct device *dev, pm_message_t state);
/*設備置于低功率狀態(tài)時所調(diào)用的方法*/
int (*resume)(struct device *dev); /*設備恢復正常狀態(tài)時所調(diào)用的方法*/
struct attribute_group **group; /*屬性組*/
struct dev_pm_ops *pm; /*用于電源管理*/
struct driver_private *p; /*設備驅動的私有數(shù)據(jù)*/
};
- 3行的
bus
指針指向驅動所屬的總線。 - 7行的
probe()
函數(shù)用來探測設備。也就是當總線設備驅動發(fā)現(xiàn)一個可能由它處理的設備時,會自動調(diào)用probe()
方法。在這個方法中會執(zhí)行一些硬件初始化工作。 - 8行的
remove()
函數(shù)在移除設備時調(diào)用。同時,如果驅動程序本身被卸載,那么它所管理的每一個設別都會調(diào)用remove()
方法。 - 9~11行是當內(nèi)核改變設備供電狀態(tài)時,內(nèi)核自動調(diào)用的函數(shù)。
- 12行時驅動所屬的屬性組,屬性組定義了一組驅動共用的屬性。
- 14行表示驅動的私有數(shù)據(jù),可以用來存儲與驅動相關的其他信息。
driver_private
結構體定義如下:
struct driver_private{
struct kobject kobj; /*內(nèi)嵌的kobject結構,用來構建設備驅動模型的結構*/
struct klist klist_devices; /*該驅動支持的所有設備鏈表*/
struct klist_node knode_bus; /*該驅動所屬總線*/
struct module_kobject *mkobj; /*驅動的模塊*/
struct device_driver *driver; /*指向驅動本身*/
}
2.驅動舉例
在聲明一個device_driver
時,一般需要probe()、remove()、name、bus()、suspend()和resume()
等成員。下面是一個PCI的例子
static struct device_driver aulx00_pcmcia_driver = {
.probe = aulx00_drv_pcmcia_probe,
.remove = aulx00_drv_pcmcia_remove,
.name = "aulx00-pcmcia",
.bus = &platform_bus_type,
.suspend = pcmcia_socket_dev_suspend,
.resume = pcmcia_socket_dev_resume,
};
該驅動程序被掛接在平臺總線(platform_bus_type
)上,這是一個很簡單的例子。但是在現(xiàn)實中,大多數(shù)驅動程序會帶有自己特定的設備信息,這些信息不是device_driver
可以全部包含。比較經(jīng)典的例子是pci_driver
.
struct pci_driver{
struct list_head node;
char *name;
const struct pci_device_id *id_table;
...
struct device_driver driver;
struct pci_dynids dynids;
}
pci_driver
是由device_driver
衍生出來的,pci_driver
中包含了PCI設備特有的信息。3.驅動程序注冊和注銷
設備驅動的注冊與注銷函數(shù)如下所示。
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
driver_register()
函數(shù)的功能是向設備驅動程序模型中加入一個新的device_driver
對象。但注冊成功后,會在sysfs
文件系統(tǒng)下創(chuàng)建一個新的目錄。該函數(shù)的代碼如下:
int driver_register(struct device_driver *drv)
{
int ret; /*返回值*/
struct device_driver *other;
/*drv和drv所屬的bus中主要有一個提供該函數(shù)即可,否則也只能調(diào)用bus的函數(shù),
而不例會drv的函數(shù)。這種方式已經(jīng)過時,推薦使用bus_type中方法*/
if((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING"Driver %s needs updating -please use"
"bus_tye methods\n",drv->name);
other = driver_find(drv->name, drv->bus);/*總線中是否已經(jīng)存在該驅動*/
if(other){
put_driver(other); /*減少驅動引用*/
printk(KERN_ERR "Error: Driver '%s' is already registerd,"
"aborting...\n",drv->name);
return -EEXIST;
}
ret = bus_add_driver(drv); /*將本drv驅動注冊等級到drv->bus所在的總線*/
if(ret)
return ret;
ret = driver_add_groups(drv, drv->groups); /*將驅動加到所屬組中*/
if(ret)
bus_remove_driver(drv);/*從總線中移除驅動程序*/
return ret;
}
driver_unregister()
函數(shù)用來注銷驅動程序。該函數(shù)首先從驅動組中刪除該驅動,然后再從總線中移除該驅動程序,代碼如下:
void driver_unregister(struct device_driver *drv)
{
driver_remove_groups(drv, drv->groups); /*從組中移除該驅動*/
bus_remove_driver(drv);
}
4.驅動的屬性
驅動的屬性可以使用driver_attribute
表示,該結構體的定義如下:
struct driver_attribute{
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count);
};
使用下面的函數(shù)可以再驅動所屬目錄創(chuàng)建和刪除一個屬性文件。屬性文件中的內(nèi)容可以用來控制驅動的某些特性,這兩個函數(shù)是:文章來源:http://www.zghlxwxcb.cn/news/detail-604452.html
int driver_create_file(struct device_driver *drv, struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv, struct driver_attribute *attr);
小結
設備驅動模型是編寫Linux驅動程序需要了解的重要知識。設備驅動模型中主要包含三大組件,分別是總線、設備和驅動。這三種結構之間的關系非常復雜,為了使驅動程序對用戶進程來說可見的,內(nèi)核提供了sysfs文件系統(tǒng)來映射設備驅動模型各組件的關系。通過本章的學習會對后面的學習有很大的幫助。文章來源地址http://www.zghlxwxcb.cn/news/detail-604452.html
到了這里,關于Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!