国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型

這篇具有很好參考價值的文章主要介紹了Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。


前言

在早期的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)的關系。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)

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í)行如下:
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
當設備啟動時,設備驅動模型會注冊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目錄的結構信息,如下所示:
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
ls命令列出了注冊到系統(tǒng)中的總線,其中每個目錄中的結構都大同小異。這里以usb目錄為例,分析其目錄的結構關系。使用cd usb命令,進入usb目錄,然后使用ls命令列出usb目錄中包含的目錄和文件,如下所示:
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
usb目錄中包含了devices和drivers目錄。devices目錄包含了USB總線下所有設備的列表,這些列表實際上是指向設備目錄中相應設備的符號鏈接。使用ls命令查看如下所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
其中1-0:1.02-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)等。這些類如下所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
類對象只包含一些設備的總稱,例如網(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設備,則這些設備的關系如下圖所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
sysfs文件系統(tǒng),這些設備使用樹形目錄來表示,如下所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
樹形結構中每個目錄與一個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行,檢查kobjktype是否合法,它們都不應該是一個空指針。
  • 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,連接kobjetcentry鏈表等。
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ù)的路徑如下圖所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
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的指針,如下圖所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
結合上圖解釋幾個重要的問題。

  • kobject始終代表sysfs文件系統(tǒng)中的一個目錄,而不是文件。對kobject_add()函數(shù)的調(diào)用將在sysfs文件系統(tǒng)中創(chuàng)建一個目錄。最底層目錄對應于系統(tǒng)中的一個設備、驅動或者其他內(nèi)容。通常一個目錄中包含一個或者多個屬性,以文件的方式表示,屬性由ktype指向。
  • kobject對象的成員namesysfs文件系統(tǒng)中的目錄名。通常使用kobject_set_name()函數(shù)來設置。在同一個目錄下,不能有相同的目錄名。
  • kobjectsysfs文件系統(tǒng)中的位置由parent指針指定。parent指針指向一個kobejct結構體,kobject對應一個目錄。
  • kobj_typekobject的屬性。一個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_typedefault_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相對應。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)

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ù)kobjectkset指針找到所屬的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的關系

ksetkobject的一個集合,用來與kobject建立層次關系。內(nèi)核可以將相似的kobject結構連接在kset集合中,這些相似的kobject可能有相似的屬性,使用統(tǒng)一的kset來表示。下圖顯示了kset集合和kobject之間的關系。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)

  • kset集合包含了屬于其的kobject結構體,kset.list鏈表用來連接第一個和最后一個kobject對象。第一個kobject使用entry連接kset集合和第二個kobejct對象。第二個kobject對象使用entry連接第一個kobject對象和第三個kobject對象,依次類推,最終形成一個kobject對象的鏈表。
  • 所有kobject結構的parent指針指向kset包含的kobejct對象,構成一個父子層次關系。
  • kobject的所有kset指針指向包含它的kset集合,所以通過kobject對象很容易就能找到kset集合。
  • kobjectkobj_type指針指向自身的kobj_type,每一個kobject都有一個單獨的kobj_type結構。另外在kset集合中也有一個kobject結構體,該結構的xxx也指向一個kobj_type結構體。從前文知道,kobj_type中定義了一組屬性和操作屬性的方法。這里需要注意的是,ksetkobj_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ù)由ksetkobj成員來維護??梢允褂?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中的實例

kobjectkset有所了解后,本節(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進行初始化,并將kobjectkobj_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的目錄,如下所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
進入kobject_test目錄,在該目錄下有一個名為kobject_test_attr屬性文件,如下所示。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
使用echo命令和cat命令可以對這個屬性文件進行讀寫,讀寫時,內(nèi)核里調(diào)用的分別是kobject_test_show()kobject_test_store()函數(shù)。這兩個函數(shù)分別用來顯示和設置屬性的名字,測試過程如下:
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)

設備驅動模型的三大組件

設備驅動模型有三大重要組件,分別是總線(bus_type)、設備(device)和驅動(driver)。下面對這三個重要的組件進行分別介紹。

總線

從硬件結構上來講,物理總線有數(shù)據(jù)總線和地址總線。物理總線是處理器與一個或者多個設備之間的通道。在設備驅動模型中,所有設備都通過總線連接,此處的總線與物理總線不同,總線是物理總線的一個抽象,同時還包含一些硬件中不存在的虛擬地址總線。在設備驅動模型中,驅動程序是附屬在總線上的。下面將首先介紹總線、設備和驅動之間的關系。
1.總線、設備、驅動關系
在設備驅動模型中,總線、設備和驅動三者之間緊密聯(lián)系。如下圖所示,在/sys目錄下,有一個bus目錄,所有的總線都在bus目錄下有一個新的子目錄。一般一個總線目錄有一個設備目錄、一個驅動目錄和一些總線屬性文件。設備目錄中包含掛接在該總線上的設備,驅動目錄包含掛接在總線上的驅動程序。設備和驅動程序之間通過指針互相聯(lián)系。
Linux驅動開發(fā)實戰(zhàn)(一)——設備驅動模型,Linux驅動開發(fā),linux,驅動開發(fā)
如上圖所示,總線上的設備鏈表有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_typebus_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");

這段代碼完成一個設備注冊,其parentbus成員都是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ù)是:

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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 深入探討Linux驅動開發(fā):Linux設備樹

    深入探討Linux驅動開發(fā):Linux設備樹

    設備樹(Device Tree,簡稱 DT)是一種在嵌入式系統(tǒng)中描述硬件設備的一種數(shù)據(jù)結構和編程語言。它用于將硬件設備的配置信息以樹形結構的方式進行描述,以便操作系統(tǒng)(如 Linux)可以根據(jù)這些信息正確地識別、配置和管理硬件設備。 設備樹最初被引入到 Linux 內(nèi)核中,用于解

    2023年04月27日
    瀏覽(24)
  • Linux設備驅動開發(fā) - 虛擬時鐘Clock驅動示例

    Linux設備驅動開發(fā) - 虛擬時鐘Clock驅動示例

    By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜歡的盆友歡迎點贊和訂閱! 你的喜歡就是我寫作的動力! 很多設備里面系統(tǒng)時鐘架構極其復雜,讓學習Clock驅動的盆友頭大。這里我參考S3C2440的clock驅動寫了一個virtual clock,即虛擬時鐘驅動,分別包含clock的provider和

    2023年04月21日
    瀏覽(27)
  • Linux -- 字符設備驅動--LED的驅動開發(fā)(初級框架)

    Linux -- 字符設備驅動--LED的驅動開發(fā)(初級框架)

    看原理圖確定引腳,確定引腳輸出什么電平才能點亮 / 熄滅 LED 看主芯片手冊,確定寄存器操作方法:哪些寄存器?哪些位?地址是? 編寫驅動:先寫框架,再寫硬件操作的代碼 注意 :在芯片手冊中確定的寄存器地址被稱為 物理地址 ,在 Linux 內(nèi)核中無法直接使用。 需要使

    2024年04月28日
    瀏覽(27)
  • 【Linux驅動開發(fā)】設備樹詳解(三)設備樹Kernel解析

    【Linux驅動開發(fā)】設備樹詳解(三)設備樹Kernel解析

    ? ? 活動地址:CSDN21天學習挑戰(zhàn)賽 【Linux驅動開發(fā)】設備樹詳解(一)設備樹基礎介紹 【Linux驅動開發(fā)】設備樹詳解(二)設備樹語法詳解 【Linux驅動開發(fā)】設備樹詳解(三)設備樹Kernel解析 ? 個人主頁:董哥聊技術 我是董哥,嵌入式領域新星創(chuàng)作者 創(chuàng)作理念:專注分享

    2023年04月24日
    瀏覽(14)
  • 嵌入式Linux驅動開發(fā) 04:基于設備樹的驅動開發(fā)

    嵌入式Linux驅動開發(fā) 04:基于設備樹的驅動開發(fā)

    前面文章 《嵌入式Linux驅動開發(fā) 03:平臺(platform)總線驅動模型》 引入了資源和驅動分離的概念,這篇文章將在前面基礎上更進一步,引入設備樹的概念。 在平臺總線驅動模型中資源和驅動已經(jīng)從邏輯上和代碼組織上進行了分離,但每次調(diào)整資源還是會涉及到內(nèi)核,所以現(xiàn)

    2024年02月16日
    瀏覽(27)
  • 新型LINUX驅動開發(fā) DTS設備樹

    新型LINUX驅動開發(fā) DTS設備樹

    1.為什么使用設備樹 linux內(nèi)核3.版本之后才有設備樹。 沒有設備樹之前的板級信息都寫在.c文件里面,導致內(nèi)核臃腫。 因此將板級信息獨立成格式,文件名為dts,一個平臺對應一個dts。 2.dts dtb dtc dts是設備樹源碼文件。 dtb是將設備樹dts編譯以后得到的二進制文件。 dtc是將dt

    2024年02月09日
    瀏覽(19)
  • Linux驅動開發(fā):設備樹dts詳解

    Linux驅動開發(fā):設備樹dts詳解

    前言: 掌握設備樹是 Linux 驅動開發(fā)人員必備的技能!因為在新版本的 Linux 中, ARM 相關的驅動全部采用了設備樹(也有支持老式驅動的,比較少),最新出的 CPU 其驅動開發(fā)也基本都是基于設備樹的,比如 ST 新出的 STM32MP157、NXP 的 I.MX8 系列等。本篇博客核心是系統(tǒng)性的學習設

    2024年02月17日
    瀏覽(22)
  • 嵌入式Linux系統(tǒng)中的設備驅動開發(fā):從設備樹到驅動實現(xiàn)

    嵌入式Linux系統(tǒng)中的設備驅動開發(fā):從設備樹到驅動實現(xiàn)

    大家好,今天給大家介紹 嵌入式Linux系統(tǒng)中的設備驅動開發(fā):從設備樹到驅動實現(xiàn) ,文章末尾附有分享大家一個資料包,差不多150多G。里面學習內(nèi)容、面經(jīng)、項目都比較新也比較全! 可進群免費領取。 在嵌入式Linux系統(tǒng)中,設備驅動是連接硬件設備和操作系統(tǒng)之間的橋梁。

    2024年02月19日
    瀏覽(25)
  • Linux下PCI設備驅動開發(fā)詳解(五)

    Linux下PCI設備驅動開發(fā)詳解(五)

    本章及其以后的幾章,我們將從用戶態(tài)軟件、內(nèi)核態(tài)驅動、FPGA邏輯介紹一個通過PCI Express總線實現(xiàn)CPU和FPGA數(shù)據(jù)通信的簡單框架。 這個框架就是開源界非常有名的RIFFA(reuseable integration framework for FPGA accelerators),它是一個FPGA加速器的一種可重用性集成框架,是一個第三方開源

    2024年02月02日
    瀏覽(19)
  • Linux下PCI設備驅動開發(fā)詳解(二)

    Linux下PCI設備驅動開發(fā)詳解(二)

    根據(jù)上一章的概念,PCI驅動包括PCI通用的驅動,以及根據(jù)實際需要設備本身的驅動。 所謂的編寫設備驅動,其實就是編寫設備本身驅動,因為linux內(nèi)核的PCI驅動是內(nèi)核自帶的。 為了更好的學習PCI設備驅動,我們需要明白內(nèi)核具體做了什么,下面我們研究一下,linux PCI通用的驅

    2024年01月19日
    瀏覽(31)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包