盡管LDD3中說(shuō)對(duì)多數(shù)程序員掌握設(shè)備驅(qū)動(dòng)模型不是必要的,但對(duì)于嵌入式Linux的底層程序員而言,對(duì)設(shè)備驅(qū)動(dòng)模型的學(xué)習(xí)非常重要。
Linux設(shè)備模型的目的:為內(nèi)核建立一個(gè)統(tǒng)一的設(shè)備模型,從而又一個(gè)對(duì)系統(tǒng)結(jié)構(gòu)的一般性抽象描述。換句話說(shuō),Linux設(shè)備模型提取了設(shè)備操作的共同屬性,進(jìn)行抽象,并將這部分共同的屬性在內(nèi)核中實(shí)現(xiàn),而為需要新添加設(shè)備或驅(qū)動(dòng)提供一般性的統(tǒng)一接口,這使得驅(qū)動(dòng)程序的開(kāi)發(fā)變得更簡(jiǎn)單了,而程序員只需要去學(xué)習(xí)接口就行了。
在正式進(jìn)入設(shè)備驅(qū)動(dòng)模型的學(xué)習(xí)之前,有必要把documentation/filesystems/sysfs.txt讀一遍(不能偷懶)。sysfs.txt主要描述/sys目錄的創(chuàng)建及其屬性,sys目錄描述了設(shè)備驅(qū)動(dòng)模型的層次關(guān)系,我們可以簡(jiǎn)略看一下/sys目錄,
block:所有塊設(shè)備
devices:系統(tǒng)所有設(shè)備(塊設(shè)備特殊),對(duì)應(yīng)struct device的層次結(jié)構(gòu)
bus:系統(tǒng)中所有總線類型(指總線類型而不是總線設(shè)備,總線設(shè)備在devices下),bus的每個(gè)子目錄都包含
--devices:包含到devices目錄中設(shè)備的軟鏈接
--drivers:與bus類型匹配的驅(qū)動(dòng)程序
class:系統(tǒng)中設(shè)備類型(如聲卡、網(wǎng)卡、顯卡等)
fs:一些文件系統(tǒng),具體可參考filesystems /fuse.txt中例子
dev:包含2個(gè)子目錄
--char:字符設(shè)備鏈接,鏈接到devices目錄,以:命名
--block:塊設(shè)備鏈接
Linux設(shè)備模型學(xué)習(xí)分為:Linux設(shè)備底層模型,描述設(shè)備的底層層次實(shí)現(xiàn)(kobject);Linux上層容器,包括總線類型(bus_type)、設(shè)備(device)和驅(qū)動(dòng)(device_driver)。
====??Linux設(shè)備底層模型 ====
謹(jǐn)記:像上面看到的一樣,設(shè)備模型是層次的結(jié)構(gòu),層次的每一個(gè)節(jié)點(diǎn)都是通過(guò)kobject實(shí)現(xiàn)的。在文件上則體現(xiàn)在sysfs文件系統(tǒng)。
kobject結(jié)構(gòu)
內(nèi)核中存在struct kobject數(shù)據(jù)結(jié)構(gòu),每個(gè)加載到系統(tǒng)中的kobject都唯一對(duì)應(yīng)/sys或者子目錄中的一個(gè)文件夾??梢赃@樣說(shuō),許多kobject結(jié)構(gòu)就構(gòu)成設(shè)備模型的層次結(jié)構(gòu)。每個(gè)kobject對(duì)應(yīng)一個(gè)或多個(gè)struct attribute描述屬性的結(jié)構(gòu)。
點(diǎn)擊(此處)折疊或打開(kāi)
struct kobject?{
const?char?*name;?/*?對(duì)應(yīng)sysfs的目錄名?*/
struct list_head entry;?/*?kobjetct雙向鏈表?*/
struct kobject?*parent;?/*?指向kset中的kobject,相當(dāng)于指向父目錄?*/
struct kset?*kset;?/*指向所屬的kset?*/
struct kobj_type?*ktype;?/*負(fù)責(zé)對(duì)kobject結(jié)構(gòu)跟蹤*/
struct sysfs_dirent?*sd;
struct kref kref;?/*kobject引用計(jì)數(shù)*/
unsigned?int?state_initialized:1;
unsigned?int?state_in_sysfs:1;
unsigned?int?state_add_uevent_sent:1;
unsigned?int?state_remove_uevent_sent:1;
unsigned?int?uevent_suppress:1;
};
kobject結(jié)構(gòu)是組成設(shè)備模型的基本結(jié)構(gòu),最初kobject設(shè)計(jì)只用來(lái)跟蹤模塊引用計(jì)數(shù),現(xiàn)已增加支持,
——?sysfs表述:在sysfs中的每個(gè)對(duì)象都有對(duì)應(yīng)的kobject
—— 數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián):通過(guò)鏈接將不同的層次數(shù)據(jù)關(guān)聯(lián)
—— 熱插拔事件處理:kobject子系統(tǒng)將產(chǎn)生的熱插拔事件通知用戶空間
kobject一般不單獨(dú)使用,而是嵌入到上層結(jié)構(gòu)(比如struct device,struct device_driver)當(dāng)中使用。kobject的創(chuàng)建者需要直接或間接設(shè)置的成員有:ktype、kset和parent。kset我們后面再說(shuō),parent設(shè)置為NULL時(shí),kobject默認(rèn)創(chuàng)建到/sys頂層目錄下,否則創(chuàng)建到對(duì)應(yīng)的kobject目錄中。重點(diǎn)來(lái)分析ktype成員的類型,
點(diǎn)擊(此處)折疊或打開(kāi)
#include?
struct kobj_type?{
void?(*release)(struct kobject?*kobj);?/*?釋放?*/
const?struct sysfs_ops?*sysfs_ops;?/*?默認(rèn)屬性實(shí)現(xiàn)?*/
struct attribute?**default_attrs;?/*?默認(rèn)屬性?*/
const?struct kobj_ns_type_operations?*(*child_ns_type)(struct kobject?*kobj);
const?void?*(*namespace)(struct kobject?*kobj);
};
ktype包含了釋放設(shè)備、默認(rèn)屬性以及屬性的實(shí)現(xiàn)方法幾個(gè)重要成員。每個(gè)kobject必須有一個(gè)release方法,并且kobject在該方法被調(diào)用之前必須保持不變(處于穩(wěn)定狀態(tài))。默認(rèn)屬性的結(jié)構(gòu)如下,
點(diǎn)擊(此處)折疊或打開(kāi)
#include?
struct attribute?{
const?char?*name;?/*?屬性名稱?*/
mode_t mode;?/*?屬性保護(hù):只讀設(shè)為S_IRUGO,可寫設(shè)為S_IWUSR?*/
}
kobj_type中的default_attrs為二級(jí)結(jié)構(gòu)指針,可以對(duì)每個(gè)kobject使用多個(gè)默認(rèn)屬性,最后一個(gè)屬性使用NULL填充。struct sysfs_ops結(jié)構(gòu)則如下,
點(diǎn)擊(此處)折疊或打開(kāi)
struct sysfs_ops?{
ssize_t?(*show)(struct kobject?*,?struct attribute?*,char?*);
ssize_t?(*store)(struct kobject?*,struct attribute?*,const?char?*,?size_t);
};
show方法用于將傳入的指定屬性編碼后放到char *類型的buffer中,store則執(zhí)行相反功能:將buffer中的編碼信息解碼后傳遞給struct attribute類型變量。兩者都是返回實(shí)際的屬性長(zhǎng)度。
一個(gè)使用kobject的簡(jiǎn)單例子如下,
點(diǎn)擊(此處)折疊或打開(kāi)
#include?
#include?
#include?
#include?
#include?
#include?
MODULE_AUTHOR("xhzuoxin");
MODULE_LICENSE("Dual BSD/GPL");
void my_obj_release(struct kobject?*kobj)
{
printk("release ok.n");
}
ssize_t my_sysfs_show(struct kobject?*kobj,?struct attribute?*attr,?char?*buf)
{
printk("my_sysfs_show.n");
printk("attrname:%s.n",?attr->name);
sprintf(buf,?"%s",?attr->name);
return strlen(attr->name)?+?1;
}
ssize_t my_sysfs_store(struct kobject?*kobj,?struct attribute?*attr,?const?char?*buf,
size_t count)
{
printk("my_sysfs_store.n");
printk("write:%sn",?buf);
return count;
}
struct sysfs_ops my_sysfs_ops?=?{
.show?=?my_sysfs_show,
.store?=?my_sysfs_store,
};
struct attribute my_attrs?=?{
.name?=?"zx_kobj",
.mode?=?S_IRWXUGO,
};
struct attribute?*my_attrs_def[]?=?{
&my_attrs,
NULL,
};
struct kobj_type my_ktype?=?{
.release?=?my_obj_release,
.sysfs_ops?=?&my_sysfs_ops,
.default_attrs?=?my_attrs_def,
};
struct kobject my_kobj?;
int?__init kobj_test_init(void)
{
printk("kobj_test init.n");
kobject_init_and_add(&my_kobj,?&my_ktype,?NULL,?"zx");
return 0;
}
void __exit kobj_test_exit(void)
{
printk("kobj_test exit.n");
kobject_del(&my_kobj);
}
module_init(kobj_test_init);
module_exit(kobj_test_exit);
例子中有兩個(gè)函數(shù),用于初始化添加和刪除kobject結(jié)構(gòu),
點(diǎn)擊(此處)折疊或打開(kāi)
int?kobject_init_and_add(struct kobject?*kobj,?struct kobj_type?*ktype,
struct kobject?*parent,?const?char?*fmt,?...);?/*?fmt指定kobject名稱?*/
void kobject_del(struct kobject?*kobj);
加載模塊后,在/sys目錄下增加了一個(gè)叫zx達(dá)到目錄,zx目錄下創(chuàng)建了一個(gè)屬性文件zx_kobj,使用tree /sys/zx查看。
內(nèi)核提供了許多與kobject結(jié)構(gòu)相關(guān)的函數(shù),如下:
點(diǎn)擊(此處)折疊或打開(kāi)
//?kobject初始化函數(shù)
void kobject_init(struct kobject?*?kobj);
//?設(shè)置指定kobject的名稱
int?kobject_set_name(struct kobject?*kobj,?const?char?*format,?...);
//?將kobj 對(duì)象的引用計(jì)數(shù)加,同時(shí)返回該對(duì)象的指針
struct kobject?*kobject_get(struct kobject?*kobj);
//?將kobj對(duì)象的引用計(jì)數(shù)減,如果引用計(jì)數(shù)降為,則調(diào)用kobject release()釋放該kobject對(duì)象
void kobject_put(struct kobject?*?kobj);
//?將kobj對(duì)象加入Linux設(shè)備層次。掛接該kobject對(duì)象到kset的list鏈中,增加父目錄各級(jí)kobject的引//?用計(jì)數(shù),在其parent指向的目錄下創(chuàng)建文件節(jié)點(diǎn),并啟動(dòng)該類型內(nèi)核對(duì)象的hotplug函數(shù)
int?kobject_add(struct kobject?*?kobj);
//?kobject注冊(cè)函數(shù),調(diào)用kobject init()初始化kobj,再調(diào)用kobject_add()完成該內(nèi)核對(duì)象的注冊(cè)
int?kobject_register(struct kobject?*?kobj);
//?從Linux設(shè)備層次(hierarchy)中刪除kobj對(duì)象
void kobject_del(struct kobject?*?kobj);
//?kobject注銷函數(shù).?與kobject register()相反,它首先調(diào)用kobject del從設(shè)備層次中刪除該對(duì)象,再調(diào)//?用kobject put()減少該對(duì)象的引用計(jì)數(shù),如果引用計(jì)數(shù)降為,則釋放kobject對(duì)象
void kobject_unregister(struct kobject?*?kobj);
kset結(jié)構(gòu)
我們先看上圖,kobject通過(guò)kset組織成層次化的結(jié)構(gòu),kset將一系列相同類型的kobject使用(雙向)鏈表連接起來(lái),可以這樣 認(rèn)為,kset充當(dāng)鏈表頭作用,kset內(nèi)部?jī)?nèi)嵌了一個(gè)kobject結(jié)構(gòu)。內(nèi)核中用kset數(shù)據(jù)結(jié)構(gòu)表示為:
點(diǎn)擊(此處)折疊或打開(kāi)
#include?
struct kset?{
struct list_head list;?/*?用于連接kset中所有kobject的鏈表頭?*/
spinlock_t list_lock;?/*?掃描kobject組成的鏈表時(shí)使用的鎖?*/
struct kobject kobj;?/*?嵌入的kobject?*/
const?struct kset_uevent_ops?*uevent_ops;?/*?kset的uevent操作?*/
};
與kobject?相似,kset_init()完成指定kset的初始化,kset_get()和kset_put()分別增加和減少kset對(duì)象的引用計(jì)數(shù)。Kset_add()和kset_del()函數(shù)分別實(shí)現(xiàn)將指定keset對(duì)象加入設(shè)備層次和從其中刪除;kset_register()函數(shù)完成kset的注冊(cè)而kset_unregister()函數(shù)則完成kset的注銷。
==== 設(shè)備模型上層容器 ====
這里要描述的上層容器包括總線類型(bus_type)、設(shè)備(device)和驅(qū)動(dòng)(device_driver),這3個(gè)模型環(huán)環(huán)相扣,參考圖9-2。為何稱為容器?因?yàn)閎us_type/device/device_driver結(jié)構(gòu)都內(nèi)嵌了Linux設(shè)備的底層模型(kobject結(jié)構(gòu))。為什么稱為上層而不是頂層?因?yàn)閷?shí)際的驅(qū)動(dòng)設(shè)備結(jié)構(gòu)往往內(nèi)嵌bus_type/device/device_driver這些結(jié)構(gòu),比如pci,usb等。
總線類型、設(shè)備、驅(qū)動(dòng)3者之間關(guān)系:
在繼續(xù)之前,自我感覺(jué)需要區(qū)分2個(gè)概念:總線設(shè)備與總線類型??偩€設(shè)備本質(zhì)上是一種設(shè)備,也需要像設(shè)備一樣進(jìn)行初始化,但位于設(shè)備的最頂層,總線類型是一種在設(shè)備和驅(qū)動(dòng)數(shù)據(jù)結(jié)構(gòu)中都包含的的抽象的描述(如圖9-2),總線類型在/sys/bus目錄下對(duì)應(yīng)實(shí)體,總線設(shè)備在/devices目錄下對(duì)應(yīng)實(shí)體。
總線類型bus_type
內(nèi)核對(duì)總線類型的描述如下:
點(diǎn)擊(此處)折疊或打開(kāi)
struct bus_type?{
const?char?*name;?/*?總線類型名?*/
struct bus_attribute?*bus_attrs;?/*?總線的屬性?*/
struct device_attribute?*dev_attrs;?/*?設(shè)備屬性,為每個(gè)加入總線的設(shè)備建立屬性鏈表?*/
struct driver_attribute?*drv_attrs;?/*?驅(qū)動(dòng)屬性,為每個(gè)加入總線的驅(qū)動(dòng)建立屬性鏈表?*/
/*?驅(qū)動(dòng)與設(shè)備匹配函數(shù):當(dāng)一個(gè)新設(shè)備或者驅(qū)動(dòng)被添加到這個(gè)總線時(shí),這個(gè)方法會(huì)被調(diào)用一次或多次,若指定的驅(qū)動(dòng)程序能夠處理指定的設(shè)備,則返回非零值。必須在總線層使用這個(gè)函數(shù),?因?yàn)槟抢锎嬖谡_的邏輯,核心內(nèi)核不知道如何為每個(gè)總線類型匹配設(shè)備和驅(qū)動(dòng)程序?*/
int?(*match)(struct device?*dev,?struct device_driver?*drv);
/*在為用戶空間產(chǎn)生熱插拔事件之前,這個(gè)方法允許總線添加環(huán)境變量(參數(shù)和 kset 的uevent方法相同)*/
int?(*uevent)(struct device?*dev,?struct kobj_uevent_env?*env);
int?(*probe)(struct device?*dev);?/*?*/
int?(*remove)(struct device?*dev);?/*?設(shè)備移除調(diào)用操作?*/
void?(*shutdown)(struct device?*dev);
int?(*suspend)(struct device?*dev,?pm_message_t state);
int?(*resume)(struct device?*dev);
const?struct dev_pm_ops?*pm;
struct subsys_private?*p;?/*?一個(gè)很重要的域,包含了device鏈表和drivers鏈表?*/
};
接著對(duì)bus_type中比較關(guān)注的幾個(gè)成員進(jìn)行簡(jiǎn)述,
[1] struct bus_attribute結(jié)構(gòu),device_attribute與driver_attribute將分別在設(shè)備和驅(qū)動(dòng)分析過(guò)程中看到,
點(diǎn)擊(此處)折疊或打開(kāi)
struct bus_attribute?{
struct attribute attr;
ssize_t?(*show)(struct bus_type?*bus,?char?*buf);
ssize_t?(*store)(struct bus_type?*bus,?const?char?*buf,?size_t count);
};
[2] subsys_private中包含了對(duì)加入總線的設(shè)備的鏈表描述和驅(qū)動(dòng)程序的鏈表描述,省略的部分結(jié)構(gòu)如下
點(diǎn)擊(此處)折疊或打開(kāi)
struct subsys_private?{
struct kset subsys;
struct kset?*devices_kset;?/*?使用kset構(gòu)建關(guān)聯(lián)的devices鏈表頭?*/
struct kset?*drivers_kset;?/*?使用kset構(gòu)建關(guān)聯(lián)的drivers鏈表頭?*/
struct klist klist_devices;?/*?通過(guò)循環(huán)可訪問(wèn)devices_kset的鏈表?*/
struct klist klist_drivers;?/*?通過(guò)循環(huán)可訪問(wèn)drivers_kset的鏈表?*/
struct bus_type?*bus;?/*?反指向關(guān)聯(lián)的bus_type結(jié)構(gòu)?*/
......
};
bus_type通過(guò)掃描設(shè)備鏈表和驅(qū)動(dòng)鏈表,使用mach方法查找匹配的設(shè)備和驅(qū)動(dòng),然后將struct device中的*driver設(shè)置為匹配的驅(qū)動(dòng),將struct device_driver中的device設(shè)置為匹配的設(shè)備,這就完成了將總線、設(shè)備和驅(qū)動(dòng)3者之間的關(guān)聯(lián)。
bus_type只有很少的成員必須提供初始化,大部分由設(shè)備模型核心控制。內(nèi)核提供許多函數(shù)實(shí)現(xiàn)bus_type的注冊(cè)注銷等操作,新注冊(cè)的總線可以再/sys/bus目錄下看到。
點(diǎn)擊(此處)折疊或打開(kāi)
struct bus_type ldd_bus_type?=?{?/*?bus_type初始化?*/
.name?=?"ldd",
.match?=?ldd_match,?/*?方法實(shí)現(xiàn)參見(jiàn)實(shí)例?*/
.uevent?=?ldd_uevent,?/*?方法實(shí)現(xiàn)參見(jiàn)實(shí)例?*/
};
ret?=?bus_register(&ldd_bus_type);?/*?注冊(cè),成功返回0?*/
if?(ret)
return ret;
void bus_unregister(struct bus_type?*bus);?/*?注銷?*/
設(shè)備device
設(shè)備通過(guò)device結(jié)構(gòu)描述,
點(diǎn)擊(此處)折疊或打開(kāi)
struct device?{
struct device?*parent;?/*?父設(shè)備,總線設(shè)備指定為NULL?*/
struct device_private?*p;?/*?包含設(shè)備鏈表,driver_data(驅(qū)動(dòng)程序要使用數(shù)據(jù))等信息?*/
struct kobject kobj;
const?char?*init_name;?/*?初始默認(rèn)的設(shè)備名,但@device_add調(diào)用之后又重新設(shè)為NULL?*/
struct device_type?*type;
struct mutex mutex;?/*?mutex?to?synchronize calls?to?its driver?*/
struct bus_type?*bus;?/*?type of bus device?is?on?*/
struct device_driver?*driver;?/*?which driver has allocated this device?*/
void?*platform_data;?/*?Platform specific data,?device core doesn't touch it?*/
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int?numa_node;?/*?NUMA node this device?is?close?to?*/
#endif
u64?*dma_mask;?/*?dma mask?(if?dma'able device)?*/
u64 coherent_dma_mask;/*?Like dma_mask,?but?for
alloc_coherent mappings as
not?all hardware supports
64 bit addresses?for?consistent
allocations such descriptors.?*/
struct device_dma_parameters?*dma_parms;
struct list_head dma_pools;?/*?dma pools?(if?dma'ble)?*/
struct dma_coherent_mem?*dma_mem;?/*?internal?for?coherent mem override?*/
/*?arch specific additions?*/
struct dev_archdata archdata;
#ifdef CONFIG_OF
struct device_node?*of_node;
#endif
dev_t devt;?/*?dev_t,?creates the sysfs?"dev"?設(shè)備號(hào)?*/
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct?class?*class;
const?struct attribute_group?**groups;?/*?optional groups?*/
void?(*release)(struct device?*dev);
};
設(shè)備在sysfs文件系統(tǒng)中的入口可以有屬性,這通過(guò)struct device_attribute單獨(dú)描述,提供device_create_file類型函數(shù)添加屬性。
點(diǎn)擊(此處)折疊或打開(kāi)
/*?interface?for?exporting device attributes?*/
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宏可以方便地再編譯時(shí)構(gòu)建設(shè)備屬性,構(gòu)建好屬性之后就必須將屬性添加到設(shè)備。
點(diǎn)擊(此處)折疊或打開(kāi)
/*?最終生成變量dev_attr_##_name描述屬性,
*?比如DEVICE_ATTR(zx,S_IRUGO,show_method,NULL);
*?則create_file中entry傳入實(shí)參為dev_attr_zx?*/
DEVICE_ATTR(_name,_mode,_show,_store);
/*屬性文件的添加與刪除使用以下函數(shù)?*/
int?device_create_file(struct device?*device,?struct device_attribute?*?entry);
void device_remove_file(struct device?*?dev,?struct device_attribute?*?attr);
總線設(shè)備的注冊(cè):總線設(shè)備與一般設(shè)備一樣,需要單獨(dú)注冊(cè),與一般設(shè)備不同,總線設(shè)備的parent與bus域設(shè)為NULL。一般設(shè)備注冊(cè)注銷函數(shù)為
點(diǎn)擊(此處)折疊或打開(kāi)
int?device_register(struct device?*dev);?/*?成功返回0,需要檢查返回值?*/
void device_unregister(struct device?*dev);
實(shí)際創(chuàng)建新設(shè)備時(shí),不是直接使用device結(jié)構(gòu),而是將device結(jié)構(gòu)嵌入到具體的設(shè)備結(jié)構(gòu)當(dāng)中,比如
點(diǎn)擊(此處)折疊或打開(kāi)
struct ldd_device?{
char?*name;?/*?設(shè)備名稱?*/
struct ldd_driver?*driver;?/*?ldd設(shè)備關(guān)聯(lián)的驅(qū)動(dòng)?*/
struct device dev;?/*?嵌入的device結(jié)構(gòu)?*/
};
/*?同時(shí)提供根據(jù)device結(jié)構(gòu)獲取ldd_device結(jié)構(gòu)的宏定義?*/
#define to_ldd_device(dev)?container_of(dev,?struct ldd_device,?dev);
驅(qū)動(dòng)device_driver
驅(qū)動(dòng)結(jié)構(gòu)描述,
點(diǎn)擊(此處)折疊或打開(kāi)
struct device_driver?{
const?char?*name;?/*?驅(qū)動(dòng)名稱,在sysfs中以文件夾名出現(xiàn)?*/
struct bus_type?*bus;?/*?驅(qū)動(dòng)關(guān)聯(lián)的總線類型?*/
struct module?*owner;
const?char?*mod_name;?/*?used?for?built-in?modules?*/
bool suppress_bind_attrs;?/*?disables bind/unbind via sysfs?*/
#if?defined(CONFIG_OF)
const?struct of_device_id?*of_match_table;
#endif
int?(*probe)?(struct device?*dev);
int?(*remove)?(struct device?*dev);
void?(*shutdown)?(struct device?*dev);
int?(*suspend)?(struct device?*dev,?pm_message_t state);
int?(*resume)?(struct device?*dev);
const?struct attribute_group?**groups;
const?struct dev_pm_ops?*pm;
struct driver_private?*p;
};
struct driver_private?{?/*?定義device_driver中的私有數(shù)據(jù)類型?*/
struct kobject kobj;?/*?內(nèi)建kobject?*/
struct klist klist_devices;?/*?驅(qū)動(dòng)關(guān)聯(lián)的設(shè)備鏈表,一個(gè)驅(qū)動(dòng)可以關(guān)聯(lián)多個(gè)設(shè)備?*/
struct klist_node knode_bus;
struct module_kobject?*mkobj;
struct device_driver?*driver;?/*?連接到的驅(qū)動(dòng)鏈表?*/
};
#define to_driver(obj)?container_of(obj,?struct driver_private,?kobj)
與設(shè)備和總線類似,驅(qū)動(dòng)可以有屬性,需要單獨(dú)定義并添加。
點(diǎn)擊(此處)折疊或打開(kāi)
/*?sysfs interface?for?exporting driver attributes?*/
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);
};
DRIVER_ATTR(_name,_mode,_show,_store);?/*?最終創(chuàng)建變量driver_attr_##_name描述屬性?*/
/*屬性文件創(chuàng)建的方法:*/
int?driver_create_file(struct device_driver?*?drv,?struct driver_attribute?*?attr);
void driver_remove_file(struct device_driver?*?drv,?struct driver_attribute?*?attr);
驅(qū)動(dòng)的注冊(cè)與注銷
點(diǎn)擊(此處)折疊或打開(kāi)
/*注冊(cè)device_driver 結(jié)構(gòu)的函數(shù)?*/
int?driver_register(struct device_driver?*drv);
void driver_unregister(struct device_driver?*drv);
與設(shè)備結(jié)構(gòu)一樣,在編寫新設(shè)備的驅(qū)動(dòng)程序時(shí),常常將device_driver結(jié)構(gòu)嵌入到新設(shè)備結(jié)構(gòu)當(dāng)中使用。
====?實(shí)例分析 ====
實(shí)例源代碼主要來(lái)自LDD3提供的示例代碼,因?yàn)長(zhǎng)DD3的代碼是linux-2.6.10版本,因此需要對(duì)源代碼做一些修改。所有源代碼參見(jiàn):device_model.zip。因?yàn)閮蓚€(gè)模塊關(guān)聯(lián),我們這使用一個(gè)Makefile文件同時(shí)編譯2個(gè)模塊,如下
點(diǎn)擊(此處)折疊或打開(kāi)
obj-m?:=?lddbus.o sculld.o
lddbus模塊分析
包括2個(gè)文件,lddbus.c(example/lddbus/)與lddbus.h(example/include/)。lddbus.h中使用extern申明了將要使用EXPORT_SYMBOL導(dǎo)出的變量ldd_bus_type,lddbus.c中創(chuàng)建了總線類型ldd_bus_type以及總線設(shè)備ldd_bus。
lddbus.h
-> extern ldd_bus_type
lddbus.c
-> ldd_bus_type (EXPORT_SYMBOL)
-> ldd_bus
由于版本變遷,對(duì)源代碼做了修改,(i)熱插拔不再使用hotplug函數(shù),因此將該操作去掉了;(ii)dev->bus_id[]改成了使用dev_set_name()設(shè)置設(shè)備名稱,使用init_name也可以設(shè)置,但后來(lái)發(fā)現(xiàn)init_name會(huì)在調(diào)用device_add之后就被賦值為NULL,這導(dǎo)致一個(gè)重大內(nèi)核錯(cuò)誤(kernel panic),將在后面詳述。
分析源代碼:作者定義了ldd_device與ldd_driver,兩個(gè)變量分別內(nèi)嵌device與device_driver結(jié)構(gòu),然后分別為ldd_device定義了注冊(cè)函數(shù)register_ldd_device和注銷函數(shù)unregister_ldd_device,對(duì)ldd_driver也做了類似的工作。還宏定義了to_ldd_driver和to_ldd_device來(lái)使用內(nèi)嵌結(jié)構(gòu)(device/device_driver)訪問(wèn)更上層的容器ldd_device和ldd_driver。但是不用著急,實(shí)際模塊裝載時(shí)沒(méi)有使用ldd_device或者ldd_driver,而是將它們和相關(guān)的注冊(cè)注銷等操作使用EXPORT_SYMBOL導(dǎo)出到其它模塊使用(這將在實(shí)例sculld模塊中看到)。
struct ldd_device/register_ldd_device/unregister_ldd_device
-> struct device/ device_register/device_unregister
-> to_ldd_device
struct ldd_driver也類似
LDD3的Makefile中普遍使用了CFLAGS變量,但在新的內(nèi)核版本中,該變量與內(nèi)核Makefile的CFLAGS變量沖突,因此將所有的Makefile的CFLAGS變量替換成了EXTRA_CFLAGS。
裝載模塊后,查看/sys/bus目錄下,增加了ldd文件夾,/sys/devices目錄下增加了ldd0文件夾。
sculld模塊分析
sculld模塊是接著lddbus在加載lddbus基礎(chǔ)上進(jìn)行的,sculld使用了lddbus中導(dǎo)出的ldd_device和ldd_driver結(jié)構(gòu)。我們大致分析下總體的設(shè)備和驅(qū)動(dòng)注冊(cè)的調(diào)用關(guān)系,
scull_init()
->register_ldd_driver()? //?由lddbus模塊導(dǎo)出
->driver_register()?
->sculld_register_dev()
->register_ldd_dev()? //?由lddbus模塊導(dǎo)出
->device_register()
裝載程序后查看bus/ldd/devices目錄下,bus/ldd/drivers目錄下多了驅(qū)動(dòng)程序,多了4個(gè)設(shè)備,devices/ldd0下也多了4個(gè)設(shè)備。
關(guān)于kernel panic錯(cuò)誤
在修改lddbus與sculld中,裝載sculld模塊時(shí)遇到如下錯(cuò)誤,同時(shí)鍵盤大寫字母指示燈閃爍,操作系統(tǒng)被鎖定,只能強(qiáng)制關(guān)機(jī)?,F(xiàn)在記錄分析及解決錯(cuò)誤的過(guò)程,
從網(wǎng)上找到資料,kernel panic類型錯(cuò)誤要跟蹤信息,還好,使用的虛擬機(jī),把出錯(cuò)的狀態(tài)截屏了。kernnel panic錯(cuò)誤分硬件和軟件,一般是由于指針指向了NULL。硬件有EIP指示出錯(cuò)位置,如上圖有一行
EIP:[] strncmp+0x11/0x38
好了,strncmp就是指示出錯(cuò)位置,然后到源代碼中找到使用該函數(shù)地方,出錯(cuò)前為
!strncmp(dev->init_name,?driver->name,?strlen(driver->name));
前面說(shuō)過(guò),dev->init_name在調(diào)用device_register之后就被設(shè)置為NULL了,好了,就是它了,改成如下(通過(guò)kobj訪問(wèn)設(shè)備名稱)就OK。
!strncmp(dev->kobj.name,?driver->name,?strlen(driver->name));
?
評(píng)論
查看更多