- 存儲(chǔ)技術(shù)原理分析
- 敖青云著
- 12438字
- 2018-12-27 02:38:21
2.6 驅(qū)動(dòng)模型對(duì)象
在上面介紹了內(nèi)核對(duì)象和syfs文件系統(tǒng),本節(jié)分析Linux驅(qū)動(dòng)模型。Linux驅(qū)動(dòng)模型適用于Linux各種子系統(tǒng),它描述了總線類(lèi)型、設(shè)備、驅(qū)動(dòng)以及類(lèi)和接口之間的關(guān)系。每個(gè)子系統(tǒng)都有屬于自己的唯一的總線類(lèi)型,它上面鏈接了注冊(cè)到這一總線類(lèi)型上的多個(gè)設(shè)備,另外它還將注冊(cè)到這個(gè)總線類(lèi)型的多個(gè)驅(qū)動(dòng)鏈接在一起。每個(gè)設(shè)備可能暫時(shí)還未綁定到驅(qū)動(dòng),或者可能被綁定到最多一個(gè)驅(qū)動(dòng)。而驅(qū)動(dòng)則可以暫時(shí)未綁定任何設(shè)備,也可能已經(jīng)綁定了一個(gè)或多個(gè)設(shè)備。
總線類(lèi)型、設(shè)備、驅(qū)動(dòng)這三者的關(guān)系,使得子系統(tǒng)支持熱插拔變得非常容易。早期的計(jì)算機(jī)系統(tǒng)不支持熱插拔,驅(qū)動(dòng)必須在設(shè)備被發(fā)現(xiàn)之前已經(jīng)被加載。而支持熱插拔的系統(tǒng)還可以允許在驅(qū)動(dòng)已經(jīng)加載的情況下安裝設(shè)備。事實(shí)上,Linux驅(qū)動(dòng)模型的處理方式是,在設(shè)備被發(fā)現(xiàn)時(shí),它被加入所注冊(cè)總線類(lèi)型的設(shè)備鏈表,然后在該總線類(lèi)型的驅(qū)動(dòng)鏈表中查找可以處理這個(gè)設(shè)備的驅(qū)動(dòng)并綁定。類(lèi)似地,在驅(qū)動(dòng)被加載時(shí),被加入到它所注冊(cè)的總線類(lèi)型的驅(qū)動(dòng)鏈表,同時(shí)在該總線類(lèi)型的設(shè)備鏈表中查找并綁定可以處理的設(shè)備。顯然,在這樣的實(shí)現(xiàn)思路下,設(shè)備發(fā)現(xiàn)和驅(qū)動(dòng)加載的順序不再那么重要了。
從上面的描述我們看到,設(shè)備最多可以屬于一個(gè)驅(qū)動(dòng)。在某些情況下,這個(gè)限制過(guò)于嚴(yán)苛。比如,SCSI磁盤(pán),它作為一個(gè)磁盤(pán)類(lèi)設(shè)備,被唯一地綁定到SCSI磁盤(pán)驅(qū)動(dòng)。但是,有時(shí)我們希望把它作為一個(gè)普通的SCSI設(shè)備,向它發(fā)送INQUIRY等SCSI命令。Linux驅(qū)動(dòng)模型對(duì)此的解決方案是,引入類(lèi)和接口。每個(gè)設(shè)備還可以唯一屬于某個(gè)類(lèi),設(shè)備被鏈入到類(lèi)的設(shè)備鏈表。類(lèi)還有另一個(gè)接口鏈表,用于鏈接注冊(cè)到該類(lèi)的所有接口。在設(shè)備被發(fā)現(xiàn),或接口被添加時(shí),無(wú)論何種順序,只要它們屬于同一個(gè)類(lèi),那么接口注冊(cè)的回調(diào)函數(shù)都會(huì)被作用在設(shè)備之上。
Linux驅(qū)動(dòng)模型實(shí)現(xiàn)的對(duì)象關(guān)系如圖2-11所示,圖中各種關(guān)系的功能如下。

圖2-11 Linux驅(qū)動(dòng)模型對(duì)象之間的關(guān)系
? bus_type和bus_type_private共同描述總線類(lèi)型。
? device_driver和driver_private共同描述驅(qū)動(dòng)。
? device和device_private共同描述設(shè)備。
? class和class_private共同描述類(lèi)。
? class_interface表示的接口。
2.6.1 總線類(lèi)型
Linux驅(qū)動(dòng)模型中的總線類(lèi)型結(jié)構(gòu)使用bus_type和bus_type_private兩個(gè)結(jié)構(gòu)體來(lái)表示。之所以這樣,純粹是從編程的角度來(lái)考慮的,程序員在實(shí)現(xiàn)一個(gè)新的總線類(lèi)型時(shí),只需要定義bus_type結(jié)構(gòu)體,并實(shí)現(xiàn)其中的方法和函數(shù),而不必關(guān)注那些完全被驅(qū)動(dòng)模型核心使用的內(nèi)部域。
但是,需要注意的是,這兩個(gè)結(jié)構(gòu)體是同時(shí)存在的,并且有一一對(duì)應(yīng)關(guān)系。事實(shí)上,兩個(gè)結(jié)構(gòu)體各自有一個(gè)指針域指向?qū)Ψ健us_type結(jié)構(gòu)中的域和bus_type_private結(jié)構(gòu)中的域分別如表2-12和表2-13所示。
表2-12 bus_type結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)
表2-13 bus_type_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)
每個(gè)總線類(lèi)型被注冊(cè)后,將在/sys/bus下創(chuàng)建一個(gè)和其名字對(duì)應(yīng)的子目錄,在該子目錄下又有兩個(gè)子目錄:drivers和devices。和總線類(lèi)型子目錄對(duì)應(yīng)的內(nèi)嵌的kset,即subsys域,而drivers_kset和devices_kset是指向和drivers子目錄以及devices子目錄對(duì)應(yīng)的kset的指針。
總線類(lèi)型有兩個(gè)鏈表:一個(gè)是在該總線類(lèi)型上發(fā)現(xiàn)的設(shè)備鏈表,另一個(gè)是在該總線類(lèi)型上加載的驅(qū)動(dòng)鏈表。注意這里用作鏈表的表頭和連接件結(jié)構(gòu)分別為klist和klist_node,它們實(shí)際上也是Linux內(nèi)核雙循環(huán)鏈表結(jié)構(gòu)list_head的封裝,具體實(shí)現(xiàn)的分析留給讀者。
某些子系統(tǒng)(或模塊)會(huì)定義自己的總線類(lèi)型,這時(shí)初始化的一個(gè)主要工作就是向Linux內(nèi)核注冊(cè)其總線類(lèi)型,對(duì)于某些子系統(tǒng)(或模塊),它甚至是初始化的全部工作。
Linux驅(qū)動(dòng)模型核心提供了bus_register函數(shù)(其代碼如程序2-12所示),被用來(lái)注冊(cè)總線類(lèi)型。子系統(tǒng)(或模塊)調(diào)用該函數(shù)前,必須已經(jīng)聲明了一個(gè)bus_type類(lèi)型的變量,調(diào)用時(shí)將其指針作為參數(shù)傳入。
程序2-12 函數(shù)bus_register()代碼(摘自文件drivers/base/bus.c)
bus_register() 880int bus_register(struct bus_type *bus) 881{ 882 int retval; 883 struct bus_type_private *priv; 884 885 priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); 886 if (!priv) 887 return -ENOMEM; 888 889 priv->bus = bus; 890 bus->p = priv; 891 892 BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); 893 894 retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); 895 if (retval) 896 goto out; 897 898 priv->subsys.kobj.kset = bus_kset; 899 priv->subsys.kobj.ktype = &bus_ktype; 900 priv->drivers_autoprobe = 1; 901 902 retval = kset_register(&priv->subsys); 903 if (retval) 904 goto out; 905 906 retval = bus_create_file(bus, &bus_attr_uevent); 907 if (retval) 908 goto bus_uevent_fail; 909 910 priv->devices_kset = kset_create_and_add("devices", NULL, 911 &priv->subsys.kobj); 912 if (!priv->devices_kset) { 913 retval = -ENOMEM; 914 goto bus_devices_fail; 915 } 916 917 priv->drivers_kset = kset_create_and_add("drivers", NULL, 918 &priv->subsys.kobj); 919 if (!priv->drivers_kset) { 920 retval = -ENOMEM; 921 goto bus_drivers_fail; 922 } 923 924 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); 925 klist_init(&priv->klist_drivers, NULL, NULL); 926 927 retval = add_probe_files(bus); 928 if (retval) 929 goto bus_probe_files_fail; 930 931 retval = bus_add_attrs(bus); 932 if (retval) 933 goto bus_attrs_fail; 934 935 pr_debug("bus: '%s': registered\n", bus->name); 936 return 0; 937 938bus_attrs_fail: 939 remove_probe_files(bus); 940bus_probe_files_fail: 941 kset_unregister(bus->p->drivers_kset); 942bus_drivers_fail: 943 kset_unregister(bus->p->devices_kset); 944bus_devices_fail: 945 bus_remove_file(bus, &bus_attr_uevent); 946bus_uevent_fail: 947 kset_unregister(&bus->p->subsys); 948 kfree(bus->p); 949out: 950 bus->p = NULL; 951 return retval; 952}
總線類(lèi)型注冊(cè)的主要任務(wù)是分配和初始化相關(guān)的結(jié)構(gòu),以及在sysfs文件系統(tǒng)中創(chuàng)建相應(yīng)的子目錄樹(shù)。
我們說(shuō)過(guò),在調(diào)用這個(gè)函數(shù)前,用戶已經(jīng)聲明了一個(gè)bus_type類(lèi)型的變量,但是bus_type_private結(jié)構(gòu)的空間還沒(méi)有分配,第885行分配之。第889和890行讓兩個(gè)結(jié)構(gòu)相互關(guān)聯(lián)起來(lái)。
接下來(lái)對(duì)總線類(lèi)型私有數(shù)據(jù)結(jié)構(gòu)的內(nèi)嵌kset的內(nèi)嵌kobject進(jìn)行初始化,包括如下內(nèi)容。
? 設(shè)置其名字為總線類(lèi)型名字(第894行)。
? 設(shè)置其kset為bus_kset,而后者在系統(tǒng)初始化過(guò)程的buses_init函數(shù)(見(jiàn)文件drivers/base/bus.c)中調(diào)用kset_create_and_add創(chuàng)建,它在sysfs文件系統(tǒng)中對(duì)應(yīng)的目錄為sys/bus,它的kset_uevent_ops為bus_uevent_ops(第895行)。
? 設(shè)置其ktype為bus_ktype(第896行)。
做過(guò)上面的設(shè)置后,調(diào)用kset_register一次性初始化總線類(lèi)型私有數(shù)據(jù)結(jié)構(gòu)的內(nèi)嵌kset,并將它添加到sysfs文件系統(tǒng)。這將在sys/bus/下創(chuàng)建一個(gè)以總線類(lèi)型名字(為敘述方便,假設(shè)為###)為名字的子目錄,即sys/bus/###/。
第906~908行調(diào)用bus_create_file在該子目錄下添加uevent屬性文件,即sys/bus/###/uevent。這是一個(gè)只寫(xiě)的屬性文件,這用于手動(dòng)觸發(fā)熱插拔。熱插拔機(jī)制需要用戶空間服務(wù)程序(例如udevd)的配合,該程序偵聽(tīng)內(nèi)核空間發(fā)出的uevent事件,匹配規(guī)則做相應(yīng)的動(dòng)作。在系統(tǒng)啟動(dòng)后內(nèi)核注冊(cè)了很多設(shè)備,但用戶空間還沒(méi)有啟動(dòng),所以這些事件沒(méi)有機(jī)會(huì)被處理。為此,在每個(gè)注冊(cè)的設(shè)備文件夾下生成一個(gè)uevent屬性文件,當(dāng)服務(wù)程序啟動(dòng)后,可以掃描sysfs文件系統(tǒng)中所有uevent屬性文件,向其寫(xiě)入add命令,從而觸發(fā)uevent事件,這樣服務(wù)程序就有機(jī)會(huì)處理這些事件了。
第910~915行在該子目錄下添加devices子目錄(即sys/bus/###/devices),對(duì)應(yīng)的kset被保存在devices_kset域。以便將來(lái)在該總線類(lèi)型下發(fā)現(xiàn)設(shè)備的時(shí)候,向?qū)?yīng)的子目錄中添加設(shè)備的符號(hào)鏈接。
第917~922行在該子目錄下添加drivers子目錄(即sys/bus/###/drivers),對(duì)應(yīng)的kset被保存在drivers_kset域。以便將來(lái)在該總線類(lèi)型下加載驅(qū)動(dòng)的時(shí)候,向?qū)?yīng)的子目錄中添加驅(qū)動(dòng)的子目錄。
第924行和第925行分別初始化總線類(lèi)型的設(shè)備鏈表和驅(qū)動(dòng)鏈表。
第927~929行調(diào)用add_probe_files在該總線類(lèi)型的子目錄下添加drivers_probe和drivers_autoprobe兩個(gè)文件,即sys/bus/###/drivers_probe和sys/bus/###/drivers_autoprobe。后者用來(lái)顯示和修改總線類(lèi)型描述符的drivers_ autoprobe域,而前者是一個(gè)只寫(xiě)的屬性文件,向其中寫(xiě)任何值都會(huì)觸發(fā)對(duì)總線類(lèi)型對(duì)它的設(shè)備重新掃描。
最后,第931~933行調(diào)用bus_add_attrs在總線類(lèi)型子目錄下為聲明bus_type時(shí)給出的總線類(lèi)型屬性添加文件。例如對(duì)于PCI總線,只定義了一個(gè)這樣的屬性:rescan。
和bus_register對(duì)應(yīng)的函數(shù)是bus_unregister,用于總線類(lèi)型注銷(xiāo)時(shí),它執(zhí)行如下一些相反的操作。
? 調(diào)用bus_remove_attrs為總線類(lèi)型刪除公有屬性文件,這些屬性由bus_type類(lèi)型變量的bus_attrs域指針?biāo)赶颉?/p>
? 調(diào)用remove_probe_files從總線類(lèi)型下刪除probe和autoprobe兩個(gè)屬性文件。
? 調(diào)用kset_unregister從子樹(shù)中刪除drivers子目錄。
? 調(diào)用kset_unregister從子樹(shù)中刪除devices子目錄。
? 調(diào)用bus_remove_file從總線類(lèi)型中刪除uevent屬性文件。
? 調(diào)用kset_unregister從系統(tǒng)中注銷(xiāo)總線類(lèi)型內(nèi)嵌的kset。
Linux驅(qū)動(dòng)模型核心提供了遍歷每個(gè)鏈表的輔助函數(shù)。
? int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *))
? int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));
這兩個(gè)輔助函數(shù)的使用會(huì)在后面看到,它們都在成功時(shí)返回0,“錯(cuò)誤”時(shí)返回非零值。
bus_for_each_dev函數(shù)遍歷總線類(lèi)型的設(shè)備鏈表,bus_for_each_drv函數(shù)遍歷總線類(lèi)型的驅(qū)動(dòng)鏈表,對(duì)于鏈表中每個(gè)設(shè)備或驅(qū)動(dòng),調(diào)用傳入的回調(diào)函數(shù)fn。回調(diào)函數(shù)返回零就繼續(xù)處理鏈表的后一個(gè)設(shè)備或驅(qū)動(dòng),返回非零值則退出循環(huán)。這主要用在設(shè)備和驅(qū)動(dòng)的匹配,即在設(shè)備鏈表中查找驅(qū)動(dòng)可以綁定的設(shè)備,或者在驅(qū)動(dòng)鏈表中查找可以綁定設(shè)備的驅(qū)動(dòng)。
我們有必要對(duì)“錯(cuò)誤”的引號(hào)做一下說(shuō)明。記住設(shè)備只能被綁定到最多一個(gè)驅(qū)動(dòng)。用設(shè)備在總線類(lèi)型的驅(qū)動(dòng)鏈表上尋求匹配的時(shí)候,無(wú)論是否找到匹配項(xiàng),都是正常的。因?yàn)橐欢ㄒ獏^(qū)分,我們將匹配作為一種“錯(cuò)誤”,出現(xiàn)時(shí)就退出循環(huán),不需要繼續(xù)處理鏈表中余下的驅(qū)動(dòng)了。
對(duì)比地看,驅(qū)動(dòng)可以綁定一個(gè)或多個(gè)設(shè)備。用驅(qū)動(dòng)在總線類(lèi)型的設(shè)備鏈表上進(jìn)行匹配的時(shí)候,應(yīng)該處理完所有的設(shè)備,除非出現(xiàn)真正意義上的錯(cuò)誤。而且對(duì)每個(gè)設(shè)備,無(wú)論是否匹配,都是正常的。這里的錯(cuò)誤用法采用的是正常的思維。
更具體的解釋請(qǐng)參看后面關(guān)于device_attach和driver_attach函數(shù)的跟蹤。
2.6.2 設(shè)備
Linux驅(qū)動(dòng)模型中的設(shè)備,我們有時(shí)簡(jiǎn)稱為驅(qū)動(dòng)模型設(shè)備,使用device和device_private兩個(gè)結(jié)構(gòu)體來(lái)表示。兩個(gè)結(jié)構(gòu)體相互關(guān)聯(lián),各自有一個(gè)域指向?qū)Ψ健_@兩個(gè)結(jié)構(gòu)體中的域分別如表2-14和表2-15所示。
表2-14 device結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)
表2-15 device_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)
在Linux驅(qū)動(dòng)模型下,每個(gè)設(shè)備被歸到一種總線類(lèi)型,通過(guò)bus域指向它。設(shè)備也可以屬于唯一的類(lèi),由class域指向。如果設(shè)備被綁定,則通過(guò)driver域指向?qū)?yīng)的驅(qū)動(dòng)。同內(nèi)核對(duì)象一樣,設(shè)備也被組織成層次結(jié)構(gòu),通過(guò)parent域指向父親設(shè)備。對(duì)應(yīng)地,設(shè)備被加入到如下四個(gè)鏈表。
? 所屬總線類(lèi)型的設(shè)備鏈表,總線類(lèi)型的klist_devices域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_bus域?yàn)殒溔氪随湵淼倪B接件。
? 所屬類(lèi)的設(shè)備鏈表,類(lèi)的class_devices域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_class域?yàn)殒溔氪随湵淼倪B接件。
? 綁定它的驅(qū)動(dòng)的設(shè)備鏈表,驅(qū)動(dòng)的klist_devices域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_driver域?yàn)殒溔氪随湵淼倪B接件。
? 父設(shè)備的孩子鏈表,父設(shè)備的klist_children域?yàn)檫@個(gè)鏈表的表頭,設(shè)備的knode_parent域?yàn)殒溔氪随湵淼倪B接件。
在實(shí)際使用中,驅(qū)動(dòng)模型設(shè)備被內(nèi)嵌到業(yè)務(wù)子系統(tǒng)的設(shè)備結(jié)構(gòu)中。后者通過(guò)驅(qū)動(dòng)模型設(shè)備被鏈接到業(yè)務(wù)子系統(tǒng)的總線類(lèi)型實(shí)例,或者被鏈接到業(yè)務(wù)子系統(tǒng)的類(lèi)實(shí)例,兩者取其一,也就是說(shuō)每個(gè)驅(qū)動(dòng)模型設(shè)備只會(huì)使用knode_bus和knode_class兩個(gè)域中的一個(gè)。如果業(yè)務(wù)子系統(tǒng)的設(shè)備結(jié)構(gòu)需要同時(shí)被鏈接到總線類(lèi)型實(shí)例和類(lèi)實(shí)例,則需要包含兩個(gè)內(nèi)嵌的驅(qū)動(dòng)模型設(shè)備,用于鏈接到總線類(lèi)型實(shí)例的驅(qū)動(dòng)模型設(shè)備被稱為通用設(shè)備,而用于鏈接到類(lèi)實(shí)例的驅(qū)動(dòng)模型設(shè)備被稱為類(lèi)設(shè)備。
業(yè)務(wù)子系統(tǒng)可以借助于驅(qū)動(dòng)模型中的鏈表實(shí)現(xiàn)對(duì)設(shè)備的遍歷,在其總線類(lèi)型實(shí)例或類(lèi)實(shí)例中查找滿足某些條件的業(yè)務(wù)子系統(tǒng)設(shè)備。例如PCI子系統(tǒng)中的for_each_pci_dev宏,還有SCSI子系統(tǒng)中的scsi_host_lookup函數(shù)。
同樣,類(lèi)似于kobject,每個(gè)device都屬于一種設(shè)備類(lèi)型,其結(jié)構(gòu)為device_type(該結(jié)構(gòu)中的域如表2-16所示),定義了這種類(lèi)型設(shè)備的公共方法和成員。
表2-16 device_type結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)
設(shè)備被發(fā)現(xiàn)后,需要向Linux驅(qū)動(dòng)模型注冊(cè)。設(shè)備注冊(cè)的函數(shù)為device_register(其代碼如程序2-13所示),它有唯一的參數(shù),即指向device描述符的指針。調(diào)用者已經(jīng)為這個(gè)device描述符分配好了空間,實(shí)際上,device描述符往往被嵌入其他結(jié)構(gòu)中,它的空間隨著其他結(jié)構(gòu)分配。
程序2-13 函數(shù)device_register()代碼(摘自文件drivers/base/core.c)
device_register() 1043int device_register(struct device *dev) 1044{ 1045 device_initialize(dev); 1046 return device_add(dev); 1047}
設(shè)備注冊(cè)分配兩個(gè)步驟:初始化設(shè)備,以及將設(shè)備添加到sysfs。分別調(diào)用device_initialize(代碼如程序2-14所示)和device_add函數(shù)(代碼如程序2-15所示)。
程序2-14 函數(shù)device_initialize()代碼(摘自文件drivers/base/core.c)
device_register()→device_initialize() 557void device_initialize(struct device *dev) 558{ 559 dev->kobj.kset = devices_kset; 560 kobject_init(&dev->kobj, &device_ktype); 561 INIT_LIST_HEAD(&dev->dma_pools); 562 init_MUTEX(&dev->sem); 563 spin_lock_init(&dev->devres_lock); 564 INIT_LIST_HEAD(&dev->devres_head); 565 device_init_wakeup(dev, 0); 566 device_pm_init(dev); 567 set_dev_node(dev, -1); 568}
device_initializate是設(shè)備注冊(cè)的第一步。我們只關(guān)注前面兩行代碼。第559行將設(shè)備內(nèi)嵌的kobject關(guān)聯(lián)到內(nèi)核對(duì)象集devices_kset,而后者在系統(tǒng)初始化過(guò)程的devices_init函數(shù)(見(jiàn)文件drivers/base/core.c)中調(diào)用kset_create_and_add創(chuàng)建,它在sysfs文件系統(tǒng)中對(duì)應(yīng)的目錄為sys/devices。此外,devices_kset的uevent操作表并賦值為device_uevent_ops。如前所述,這將控制內(nèi)核中設(shè)備狀態(tài)發(fā)生變化時(shí)向用戶空間發(fā)送uevent的方式。
第560行將設(shè)備的內(nèi)嵌對(duì)象的對(duì)象類(lèi)型設(shè)置為device_ktype,其中定義了設(shè)備的公共屬性操作表,以及釋放方法。后者確保在對(duì)設(shè)備的引用計(jì)數(shù)減到0時(shí),銷(xiāo)毀整個(gè)device結(jié)構(gòu)。
程序2-15 函數(shù)device_add()代碼(摘自文件drivers/base/core.c)
device_register()→device_add() 890int device_add(struct device *dev) 891{ 892 struct device *parent = NULL; 893 struct class_interface *class_intf; 894 int error = -EINVAL; 895 896 dev = get_device(dev); 897 if (!dev) 898 goto done; 899 900 if (!dev->p) { 901 error = device_private_init(dev); 902 if (error) 903 goto done; 904 } 905 906 /* 907 * for statically allocated devices, which should all be converted 908 * some day, we need to initialize the name. We prevent reading back 909 * the name, and force the use of dev_name() 910 */ 911 if (dev->init_name) { 912 dev_set_name(dev, "%s", dev->init_name); 913 dev->init_name = NULL; 914 } 915 916 if (!dev_name(dev)) { 917 error = -EINVAL; 918 goto name_error; 919 } 920 921 pr_debug("device: '%s': %s\n", dev_name(dev), __func__); 922 923 parent = get_device(dev->parent); 924 setup_parent(dev, parent); 925 926 /* use parent numa_node */ 927 if (parent) 928 set_dev_node(dev, dev_to_node(parent)); 929 930 /* first, register with generic layer. */ 931 /* we require the name to be set before, and pass NULL */ 932 error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); 933 if (error) 934 goto Error; 935 936 /* notify platform of device entry */ 937 if (platform_notify) 938 platform_notify(dev); 939 940 error = device_create_file(dev, &uevent_attr); 941 if (error) 942 goto attrError; 943 944 if (MAJOR(dev->devt)) { 945 error = device_create_file(dev, &devt_attr); 946 if (error) 947 goto ueventattrError; 948 949 error = device_create_sys_dev_entry(dev); 950 if (error) 951 goto devtattrError; 952 953 devtmpfs_create_node(dev); 954 } 955 956 error = device_add_class_symlinks(dev); 957 if (error) 958 goto SymlinkError; 959 error = device_add_attrs(dev); 960 if (error) 961 goto AttrsError; 962 error = bus_add_device(dev); 963 if (error) 964 goto BusError; 965 error = dpm_sysfs_add(dev); 966 if (error) 967 goto DPMError; 968 device_pm_add(dev); 969 970 /* Notify clients of device addition. This call must come 971 * after dpm_sysf_add() and before kobject_uevent(). 972 */ 973 if (dev->bus) 974 blocking_notifier_call_chain(&dev->bus->p->bus_notifier, 975 BUS_NOTIFY_ADD_DEVICE, dev); 976 977 kobject_uevent(&dev->kobj, KOBJ_ADD); 978 bus_probe_device(dev); 979 if (parent) 980 klist_add_tail(&dev->p->knode_parent, 981 &parent->p->klist_children); 982 983 if (dev->class) { 984 mutex_lock(&dev->class->p->class_mutex); 985 /* tie the class to the device */ 986 klist_add_tail(&dev->knode_class, 987 &dev->class->p->class_devices); 988 989 /* notify any interfaces that the device is here */ 990 list_for_each_entry(class_intf, 991 &dev->class->p->class_interfaces, node) 992 if (class_intf->add_dev) 993 class_intf->add_dev(dev, class_intf); 994 mutex_unlock(&dev->class->p->class_mutex); 995 } 996done: 997 put_device(dev); 998 return error; 999 DPMError: 1000 bus_remove_device(dev); 1001 BusError: 1002 device_remove_attrs(dev); 1003 AttrsError: 1004 device_remove_class_symlinks(dev); 1005 SymlinkError: 1006 if (MAJOR(dev->devt)) 1007 devtmpfs_delete_node(dev); 1008 if (MAJOR(dev->devt)) 1009 device_remove_sys_dev_entry(dev); 1010 devtattrError: 1011 if (MAJOR(dev->devt)) 1012 device_remove_file(dev, &devt_attr); 1013 ueventattrError: 1014 device_remove_file(dev, &uevent_attr); 1015 attrError: 1016 kobject_uevent(&dev->kobj, KOBJ_REMOVE); 1017 kobject_del(&dev->kobj); 1018 Error: 1019 cleanup_device_parent(dev); 1020 if (parent) 1021 put_device(parent); 1022name_error: 1023 kfree(dev->p); 1024 dev->p = NULL; 1025 goto done; 1026}
device_add是設(shè)備注冊(cè)的第二步,負(fù)責(zé)將設(shè)備添加到系統(tǒng)。
設(shè)備的私有數(shù)據(jù)結(jié)構(gòu)可能還沒(méi)有被分配,在第900~904行分配。在第911~919行設(shè)置并驗(yàn)證設(shè)備的名字。然后,調(diào)用setup_parent設(shè)置設(shè)備的父親,同時(shí)確定了設(shè)備在sysfs中的位置。這是一個(gè)比較復(fù)雜的過(guò)程,我們后面會(huì)專門(mén)討論它。
第932~934行,調(diào)用kobject_add將設(shè)備的內(nèi)嵌kobject添加到系統(tǒng),這將在sys/devices/目錄下添加一個(gè)以設(shè)備名為名字的子目錄;
然后在sysfs文件系統(tǒng)中創(chuàng)建屬性文件:第940~942行創(chuàng)建uevent屬性對(duì)應(yīng)的文件,這是一個(gè)只寫(xiě)的屬性文件,可以從用戶空間寫(xiě)事件到這個(gè)文件,從而手動(dòng)產(chǎn)生事件。
第944~954行,如果設(shè)備有設(shè)備號(hào):
? 在設(shè)備目錄下創(chuàng)建dev屬性對(duì)應(yīng)的文件,這是一個(gè)只讀的屬性文件,用戶空間讀取這個(gè)文件時(shí),將返回設(shè)備的設(shè)備號(hào),以##:##的形式;
? 調(diào)用device_create_sys_dev_entry函數(shù)(文件drivers/base/core.c)在sys/block/或sys/char/下創(chuàng)建一個(gè)到設(shè)備所在目錄的符號(hào)鏈接,鏈接名為##:##的形式的設(shè)備號(hào)。具體選擇目錄取決于設(shè)備所屬的類(lèi)。如果屬于block_class,即為塊設(shè)備,將在sys/block下創(chuàng)建;如果屬于其他類(lèi),或者不屬于任何類(lèi),都會(huì)在sys/char/下創(chuàng)建。
第956~958行,調(diào)用device_add_class_symlinks,如果設(shè)備屬于某個(gè)類(lèi),則為設(shè)備創(chuàng)建和類(lèi)相關(guān)的符號(hào)鏈接。這又包括下面的步驟:
? 在設(shè)備目錄下,創(chuàng)建到所屬類(lèi)的符號(hào)鏈接,鏈接名為subsystem;
? 在所屬類(lèi)的目錄下,創(chuàng)建到設(shè)備的符號(hào)鏈接,鏈接名為設(shè)備名;
? 如果設(shè)備有父設(shè)備,并且設(shè)備不為分區(qū),在設(shè)備目錄下,創(chuàng)建到其父設(shè)備的符號(hào)鏈接,鏈接名為device。
第959~961行,調(diào)用device_add_attrs為設(shè)備的默認(rèn)屬性添加對(duì)應(yīng)的文件。
第962~964行,調(diào)用bus_add_device,如果設(shè)備有指定總線類(lèi)型,則會(huì)將設(shè)備添加到該總線類(lèi)型。這又包括下面的步驟:
? 為設(shè)備添加該總線類(lèi)型下設(shè)備的公有屬性文件,這些屬性由設(shè)備所屬bus_type類(lèi)型變量的dev_attrs域指針?biāo)赶颍?/p>
? 在總線類(lèi)型子目錄的devices目錄下創(chuàng)建到該設(shè)備的符號(hào)鏈接,鏈接名為設(shè)備名;
? 在設(shè)備的目錄下創(chuàng)建到總線類(lèi)型的符號(hào)鏈接,鏈接名為subsystem;
? 將設(shè)備連接到總線類(lèi)型的設(shè)備鏈表。
在bus_add_device函數(shù)過(guò)程中,還會(huì)調(diào)用make_deprecated_bus_links函數(shù),后者被早期的版本用于在設(shè)備目錄下創(chuàng)建另外一個(gè)到總線類(lèi)型的符號(hào)鏈接,鏈接名為bus。如果內(nèi)核編譯未指定CONFIG_SYSFS_DEPRECATED選項(xiàng),那么這個(gè)函數(shù)為空,什么也不做。
至此,設(shè)備自身的添加工作已經(jīng)完成,第977行調(diào)用kobject_uevent向用戶空間報(bào)告KOBJ_ADD事件。
然后調(diào)用bus_probe_device試圖將設(shè)備綁定到某個(gè)驅(qū)動(dòng),這個(gè)過(guò)程我們也在后面專門(mén)討論。
第979~981行,如果指定了parent,則將設(shè)備添加到它的孩子鏈表中。
此外,第983~995行,如果設(shè)備設(shè)置了class域,則將設(shè)備添加到類(lèi)的設(shè)備鏈表。而且,對(duì)于注冊(cè)到該類(lèi)的每個(gè)接口,如果定義了add_dev回調(diào)函數(shù),則調(diào)用它。這個(gè)動(dòng)作是系統(tǒng)對(duì)設(shè)備熱插處理在類(lèi)上的體現(xiàn)。
到現(xiàn)在,設(shè)備注冊(cè)過(guò)程已經(jīng)比較完整了,除了第924行的setup_parent函數(shù)和第978行的bus_probe_device函數(shù)。它們的作用都很重要,并且過(guò)程也比較復(fù)雜,下面用專門(mén)的篇幅來(lái)討論。
1.設(shè)備在sysfs的位置
setup_parent函數(shù)的根本目的就是確定設(shè)備在sysfs文件系統(tǒng)中的位置。之所以說(shuō)它重要,是因?yàn)槔斫鈨?nèi)核對(duì)象之間的層次關(guān)系就是理解Linux驅(qū)動(dòng)模型的一個(gè)關(guān)鍵。
分析device的結(jié)構(gòu),可以看到有兩個(gè)parent域:
? device描述符的parent域,指向設(shè)備的父設(shè)備;
? device描述符內(nèi)嵌的kobject的parent域,決定了設(shè)備在sysfs文件系統(tǒng)中的位置。
setup_parent函數(shù)(其代碼如程序2-16所示)的實(shí)質(zhì)就是為后者賦值。它傳入兩個(gè)參數(shù):第一個(gè)為指向設(shè)備本身的device描述符的指針;第二個(gè)為指向其父設(shè)備的device描述符的指針。
程序2-16 函數(shù)setup_parent()代碼(摘自文件drivers/base/core.c)
device_register()→device_add()→setup_parent() 676static void setup_parent(struct device *dev, struct device *parent) 677{ 678 struct kobject *kobj; 679 kobj = get_device_parent(dev, parent); 680 if (kobj) 681 dev->kobj.parent = kobj; 682}
setup_parent函數(shù)將主要工作交給get_device_parent函數(shù)(其代碼如程序2-17所示)。若后者的返回值非NULL,則保存在設(shè)備的內(nèi)嵌對(duì)象的parent域。
程序2-17 函數(shù)get_device_parent()代碼(摘自文件drivers/base/core.c)
device_register()→device_add()→setup_parent()→get_device_parent() 599static struct kobject *get_device_parent(struct device *dev, 600 struct device *parent) 601{ 602 int retval; 603 604 if (dev->class) { 605 static DEFINE_MUTEX(gdp_mutex); 606 struct kobject *kobj = NULL; 607 struct kobject *parent_kobj; 608 struct kobject *k; 609 610 /* 611 * If we have no parent, we live in "virtual". 612 * Class-devices with a non class-device as parent, live 613 * in a "glue" directory to prevent namespace collisions. 614 */ 615 if (parent == NULL) 616 parent_kobj = virtual_device_parent(dev); 617 else if (parent->class) 618 return &parent->kobj; 619 else 620 parent_kobj = &parent->kobj; 621 622 mutex_lock(&gdp_mutex); 623 624 /* find our class-directory at the parent and reference it */ 625 spin_lock(&dev->class->p->class_dirs.list_lock); 626 list_for_each_entry(k, &dev->class->p->class_dirs.list, entry) 627 if (k->parent == parent_kobj) { 628 kobj = kobject_get(k); 629 break; 630 } 631 spin_unlock(&dev->class->p->class_dirs.list_lock); 632 if (kobj) { 633 mutex_unlock(&gdp_mutex); 634 return kobj; 635 } 636 637 /* or create a new class-directory at the parent device */ 638 k = kobject_create(); 639 if (!k) { 640 mutex_unlock(&gdp_mutex); 641 return NULL; 642 } 643 k->kset = &dev->class->p->class_dirs; 644 retval = kobject_add(k, parent_kobj, "%s", dev->class->name); 645 if (retval < 0) { 646 mutex_unlock(&gdp_mutex); 647 kobject_put(k); 648 return NULL; 649 } 650 /* do not emit an uevent for this simple "glue" directory */ 651 mutex_unlock(&gdp_mutex); 652 return k; 653 } 654 655 if (parent) 656 return &parent->kobj; 657 return NULL; 658}
決定device描述符的內(nèi)嵌kobject的父kobject的因素有如下三個(gè):
? 設(shè)備本身device描述符的parent域;
? 設(shè)備本身device描述符的class域;
? 父設(shè)備device描述符的class域(如果父設(shè)備存在的話)。
Linux當(dāng)前版本的處理方法和以前版本稍有不同。我們只考慮當(dāng)前版本的處理規(guī)則,即代碼中未定義CONFIG_SYS_DEPRECATED的部分。設(shè)置設(shè)備內(nèi)嵌內(nèi)核對(duì)象的父對(duì)象的情況有6種,如圖2-12所示,具體說(shuō)明如下。
? device有class,有parent,且parent有class,不管parent->class和device->class是否相同,parent_kobj都為parent內(nèi)嵌的kobject;這分別對(duì)應(yīng)圖中的情況①和②。
? device有class,有parent,且parent沒(méi)有class,則parent_obj為新創(chuàng)建的一個(gè)kobject,保存在class的class_dirs(這是個(gè)kset)里。這個(gè)新創(chuàng)建的kobject的parent為parent內(nèi)嵌的kobject,名稱為device的class_name,也就是說(shuō),它對(duì)應(yīng)的sysfs目錄為sys/devices/.../parent_name/ class_name/。這對(duì)應(yīng)圖中的情況③。

圖2-12 設(shè)置設(shè)備內(nèi)嵌內(nèi)核對(duì)象的父對(duì)象
? device有class,沒(méi)有parent,則parent_kobj為新創(chuàng)建的一個(gè)kobject,保存在class的class_dirs(這是個(gè)kset)里。這個(gè)新創(chuàng)建的kobject的parent為sys/devices/virtual/目錄對(duì)應(yīng)的kobject,名稱為device的class_name,也就是說(shuō),它對(duì)應(yīng)的sys目錄為sys/devices/virtual/class_name。這對(duì)應(yīng)圖中的情況④。
? device無(wú)class,有parent,則device->kobj.parent = parent.kobj。這對(duì)應(yīng)圖中的情況⑤。
? device無(wú)class,無(wú)parent,則device->kobj.parent為空。這對(duì)應(yīng)圖中的情況⑥。應(yīng)該說(shuō)這是一種理論上的可能,在Linux代碼中沒(méi)有找到這樣的例子(萬(wàn)一出現(xiàn),設(shè)備目錄將直接建在sys/之下)。我們將它列舉在這里,純粹出于完整性的考慮。
對(duì)于情況③和④的補(bǔ)充說(shuō)明:class_dirs是用于保存那些為了將其父設(shè)備為空,或其父設(shè)備的class為空的設(shè)備掛入sys/devices/子樹(shù),而新創(chuàng)建的內(nèi)核對(duì)象,它將這些內(nèi)核對(duì)象鏈接在一起。這些內(nèi)核對(duì)象將作為前述設(shè)備內(nèi)嵌內(nèi)核對(duì)象的父對(duì)象。
綜合上述情況,deivce在sysfs中的目錄有以下幾種形式:
? sys/devices/.../parent_name/device_name;
? sys/devices/.../parent_name/class_name/device_name;
? sys/devices/virtual/class_name/device_name。
如果采用新規(guī)則,所有的設(shè)備都放在sys/devices/目錄下(這不同于舊規(guī)則將設(shè)備分散在sys/devices/和sys/class/兩個(gè)目錄中),這個(gè)系統(tǒng)的設(shè)備樹(shù)統(tǒng)一在sys/devices/一個(gè)目錄中。
2.將設(shè)備綁定到驅(qū)動(dòng)
將設(shè)備添加到系統(tǒng),還需要嘗試將設(shè)備綁定到驅(qū)動(dòng)。我們現(xiàn)在來(lái)看對(duì)bus_probe_device函數(shù)(其代碼如程序2-18所示)的調(diào)用。它只有一個(gè)參數(shù),即指向設(shè)備本身的device描述符的指針;
程序2-18 函數(shù)bus_probe_device()代碼(摘自文件drivers/base/bus.c)
device_register()→device_add()→bus_probe_device() 509void bus_probe_device(struct device *dev) 510{ 511 struct bus_type *bus = dev->bus; 512 int ret; 513 514 if (bus && bus->p->drivers_autoprobe) { 515 ret = device_attach(dev); 516 WARN_ON(ret < 0); 517 } 518}
從字面上理解,這個(gè)函數(shù)的功能是自動(dòng)探測(cè)設(shè)備,探測(cè)的主體是總線類(lèi)型上的驅(qū)動(dòng)。探測(cè)需要設(shè)備所在總線類(lèi)型的支持。如果設(shè)備不屬于任何總線類(lèi)型,或者總線類(lèi)型沒(méi)有自動(dòng)探測(cè)能力(即driver_autoprobe域?yàn)?),則什么也不需要做;否則調(diào)用device_attach函數(shù),其代碼如程序2-19所示。
程序2-19 函數(shù)device_attach()代碼(摘自文件drivers/base/dd.c)
device_register()→device_add()→bus_probe_device()→device_attach() 238int device_attach(struct device *dev) 239{ 240 int ret = 0; 241 242 device_lock(dev); 243 if (dev->driver) { 244 ret = device_bind_driver(dev); 245 if (ret == 0) 246 ret = 1; 247 else { 248 dev->driver = NULL; 249 ret = 0; 250 } 251 } else { 252 pm_runtime_get_noresume(dev); 253 ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach); 254 pm_runtime_put_sync(dev); 255 } 256 device_unlock(dev); 257 return ret; 258
device_attach函數(shù)的目的是試圖將設(shè)備綁定到某個(gè)驅(qū)動(dòng),返回1表示設(shè)備被綁定到驅(qū)動(dòng),返回0表示沒(méi)有找到匹配的驅(qū)動(dòng)。在調(diào)用device_attach時(shí),存在兩種情況:
? 已經(jīng)為設(shè)備指定了驅(qū)動(dòng),即設(shè)備的driver域已經(jīng)設(shè)置,就直接調(diào)用device_bind_driver函數(shù)。如果它返回錯(cuò)誤,說(shuō)明設(shè)備沒(méi)辦法被綁定到這個(gè)驅(qū)動(dòng),那么就清零設(shè)備的driver域,將錯(cuò)誤報(bào)告給調(diào)用者;
? 否則遍歷總線類(lèi)型的驅(qū)動(dòng)鏈表,調(diào)用__device_attach函數(shù)(其代碼如程序2-20所示)嘗試將設(shè)備綁定到這些驅(qū)動(dòng)。如果設(shè)備不能被綁定到任何驅(qū)動(dòng),那么bus_for_each_drv宏最終會(huì)返回0,我們直接回傳給調(diào)用者,報(bào)告錯(cuò)誤。
我們只跟蹤后面一種情況的執(zhí)行流程。事實(shí)上,前面一種情況,所做的工作只是其中的一部分。
程序2-20 函數(shù)__device_attach()代碼(摘自文件drivers/base/dd.c)
device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach() 214static int __device_attach(struct device_driver *drv, void *data) 215{ 216 struct device *dev = data; 217 218 if (!driver_match_device(drv, dev)) 219 return 0; 220 221 return driver_probe_device(drv, dev); 222}
記住,__device_attach函數(shù)試圖綁定具體驅(qū)動(dòng)和具體設(shè)備。同樣,它返回1表示設(shè)備被綁定到驅(qū)動(dòng),返回0表示沒(méi)有找到匹配的驅(qū)動(dòng)。這個(gè)返回值被bus_for_each_drv宏進(jìn)行了非正常思維的解釋:返回1被當(dāng)成一種“錯(cuò)誤”,遇到即退出循環(huán),放棄處理驅(qū)動(dòng)鏈表中余下的驅(qū)動(dòng)。
它調(diào)用driver_match_device函數(shù)(其代碼如程序2-21所示)檢查是否匹配,匹配失敗,返回0。若匹配成功,則調(diào)用driver_probe_device函數(shù)(其代碼如程序2-22所示)進(jìn)一步探測(cè),并向調(diào)用者傳遞它的返回值:成功時(shí)為1;否則為0。
程序2-21 函數(shù)driver_match_device()代碼(摘自文件drivers/base/base.h)
device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach() →driver_match_device() 120static inline int driver_match_device(struct device_driver *drv, 121 struct device *dev) 122{ 123 return drv->bus->match ? drv->bus->match(dev, drv) : 1; 124}
驅(qū)動(dòng)和設(shè)備的匹配是子系統(tǒng)特定的,Linux驅(qū)動(dòng)模型必須通過(guò)回調(diào)函數(shù)來(lái)實(shí)現(xiàn)。子系統(tǒng)在聲明總線類(lèi)型時(shí),可以給出其match方法。它以device和device_driver作為其參數(shù),在具體實(shí)現(xiàn)中,需要先轉(zhuǎn)換為對(duì)應(yīng)的設(shè)備和驅(qū)動(dòng)描述符(例如pci_dev和pci_driver)。match回調(diào)函數(shù)判斷驅(qū)動(dòng)是否能“匹配”設(shè)備。若是,返回1;否則返回0。如果沒(méi)定義match回調(diào)函數(shù),我們直接返回1。認(rèn)為是總線類(lèi)型自動(dòng)匹配了驅(qū)動(dòng)和設(shè)備。
程序2-22 函數(shù)driver_probe_device()代碼(摘自文件drivers/base/dd.c)
device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach() →driver_probe_device() 196int driver_probe_device(struct device_driver *drv, struct device *dev) 197{ 198 int ret = 0; 199 200 if (!device_is_registered(dev)) 201 return -ENODEV; 202 203 pr_debug("bus: '%s': %s: matched device %s with driver %s\n", 204 drv->bus->name, __func__, dev_name(dev), drv->name); 205 206 pm_runtime_get_noresume(dev); 207 pm_runtime_barrier(dev); 208 ret = really_probe(dev, drv); 209 pm_runtime_put_sync(dev); 210 211 return ret; 212}
進(jìn)一步探測(cè)設(shè)備要求設(shè)備已經(jīng)注冊(cè)。如果設(shè)備被注冊(cè),對(duì)本驅(qū)動(dòng)以及后續(xù)驅(qū)動(dòng)的匹配都沒(méi)有意義,返回負(fù)的錯(cuò)誤碼,這是一種真正意義上的錯(cuò)誤。
這個(gè)函數(shù)調(diào)用了多個(gè)與電源管理有關(guān)的函數(shù),因?yàn)殡娫垂芾聿皇潜緯?shū)的重點(diǎn),我們跳過(guò)它們,直接跟蹤第208行的really_probe函數(shù),其代碼如程序2-23所示。
程序2-23 函數(shù)really_probe()代碼(摘自文件drivers/base/dd.c)
device_register()→device_add()→bus_probe_device()→device_attach()→__device_attach() →driver_probe_device()→really_probe() 104static int really_probe(struct device *dev, struct device_driver *drv) 105{ 106 int ret = 0; 107 108 atomic_inc(&probe_count); 109 pr_debug("bus: '%s': %s: probing driver %s with device %s\n", 110 drv->bus->name, __func__, drv->name, dev_name(dev)); 111 WARN_ON(!list_empty(&dev->devres_head)); 112 113 dev->driver = drv; 114 if (driver_sysfs_add(dev)) { 115 printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n", 116 __func__, dev_name(dev)); 117 goto probe_failed; 118 } 119 120 if (dev->bus->probe) { 121 ret = dev->bus->probe(dev); 122 if (ret) 123 goto probe_failed; 124 } else if (drv->probe) { 125 ret = drv->probe(dev); 126 if (ret) 127 goto probe_failed; 128 } 129 130 driver_bound(dev); 131 ret = 1; 132 pr_debug("bus: '%s': %s: bound device %s to driver %s\n", 133 drv->bus->name, __func__, dev_name(dev), drv->name); 134 goto done; 135 136probe_failed: 137 devres_release_all(dev); 138 driver_sysfs_remove(dev); 139 dev->driver = NULL; 140 141 if (ret != -ENODEV && ret != -ENXIO) { 142 /* driver matched but the probe failed */ 143 printk(KERN_WARNING 144 "%s: probe of %s failed with error %d\n", 145 drv->name, dev_name(dev), ret); 146 } 147 /* 148 * Ignore errors returned by ->probe so that the next driver can try 149 * its luck. 150 */ 151 ret = 0; 152done: 153 atomic_dec(&probe_count); 154 wake_up(&probe_waitqueue); 155 return ret; 156}
最終,如果設(shè)備可以綁定到驅(qū)動(dòng),則需要執(zhí)行下列操作:
? 第113行將設(shè)備的driver域指向驅(qū)動(dòng);
? 第114~118行,調(diào)用driver_sysfs_add在sysfs文件系統(tǒng)中“綁定”,即在驅(qū)動(dòng)對(duì)應(yīng)的目錄下創(chuàng)建目標(biāo)為設(shè)備的符號(hào)鏈接,鏈接名使用設(shè)備名,同時(shí)在設(shè)備對(duì)應(yīng)的目錄下創(chuàng)建目標(biāo)為驅(qū)動(dòng)的符號(hào)鏈接,鏈接名為“driver”;
? 第120~128行,如果設(shè)備的總線類(lèi)型給出了probe方法,則調(diào)用之;否則如果設(shè)備的驅(qū)動(dòng)給出了probe方法,調(diào)用之。probe函數(shù)返回0表示成功;否則返回錯(cuò)誤碼;
? 第130行調(diào)用driver_bound將設(shè)備鏈入驅(qū)動(dòng)的設(shè)備鏈表。
如果一切正常,really_probe函數(shù)返回1,表示匹配成功。如果中間出現(xiàn)問(wèn)題,回滾已經(jīng)做過(guò)的操作,向調(diào)用者返回0。
我們?cè)倏匆幌驴偩€類(lèi)型和驅(qū)動(dòng)的probe回調(diào)函數(shù)(如圖2-13所示)。這兩個(gè)函數(shù)都是在設(shè)備可以被綁定到驅(qū)動(dòng)的情況下,用來(lái)對(duì)設(shè)備進(jìn)行初始化。若成功,返回0;否則返回錯(cuò)誤碼。
對(duì)于PCI子系統(tǒng),總線類(lèi)型的probe回調(diào)函數(shù)是pci_device_probe,驅(qū)動(dòng)的probe回調(diào)函數(shù)為NULL。PCI設(shè)備的初始化是具體HBA特定的,比如PCI-SCSI適配器要在這時(shí)開(kāi)始SCSI設(shè)備掃描,因此pci_device_probe進(jìn)而會(huì)調(diào)用PCI驅(qū)動(dòng)的probe回調(diào)函數(shù),即具體HBA驅(qū)動(dòng)的探測(cè)方法實(shí)現(xiàn)。注意,這里驅(qū)動(dòng)的probe回調(diào)函數(shù)是指device_driver結(jié)構(gòu)的probe域;而PCI驅(qū)動(dòng)的probe回調(diào)函數(shù)是指pci_driver結(jié)構(gòu)的probe域。
對(duì)于SCSI子系統(tǒng),總線類(lèi)型的probe回調(diào)函數(shù)為NULL,驅(qū)動(dòng)的probe回調(diào)函數(shù)隨具體SCSI驅(qū)動(dòng)的定義提供。例如對(duì)于SCSI磁盤(pán)驅(qū)動(dòng),該回調(diào)函數(shù)被定義為sd_probe,它將為這個(gè)SCSI磁盤(pán)設(shè)備分配通用磁盤(pán)描述符。
總結(jié)一下,Linux驅(qū)動(dòng)模型會(huì)為設(shè)備在sysfs文件系統(tǒng)中創(chuàng)建的符號(hào)鏈接列舉如下。相應(yīng)代碼分散在前面的函數(shù)里,集中整理于此。
類(lèi)相關(guān)的符號(hào)鏈接:
? device對(duì)應(yīng)的目錄下,名稱為subsystem,指向device->class對(duì)應(yīng)的目錄;

圖2-13 PCI驅(qū)動(dòng)和SCSI驅(qū)動(dòng)的probe回調(diào)函數(shù)
? device->class對(duì)應(yīng)的目錄下,名稱為device_name,指向device對(duì)應(yīng)的目錄,僅當(dāng)device對(duì)應(yīng)的目錄不在device->class對(duì)應(yīng)的目錄下,并且device不是塊設(shè)備分區(qū)的情況下才創(chuàng)建;
? device對(duì)應(yīng)的目錄下,名稱為device,指向device->parent對(duì)應(yīng)的目錄,僅當(dāng)device有parent并且device不是塊設(shè)備分區(qū)的情況下才創(chuàng)建。
總線類(lèi)型相關(guān)的符號(hào)鏈接:
? device->bus的devices_kset對(duì)應(yīng)的目錄下,名稱為device_name,指向device對(duì)應(yīng)的目錄;
? device對(duì)應(yīng)的目錄下,名稱為subsystem,指向device->bus對(duì)應(yīng)的目錄。
驅(qū)動(dòng)相關(guān)的符號(hào)鏈接:
? device->driver對(duì)應(yīng)的目錄下,名稱為device_name,指向device對(duì)應(yīng)的目錄;
? device對(duì)應(yīng)的目錄下,名稱為driver,指向device->driver對(duì)應(yīng)的目錄。
注意:在device對(duì)應(yīng)的目錄下,名稱為subsystem的符號(hào)鏈接,可能指向device->class或是device->bus對(duì)應(yīng)的目錄。這里并不會(huì)出現(xiàn)沖突的情況,即便對(duì)應(yīng)SCSI設(shè)備(可能屬于總線類(lèi)型scsi_bus_type或類(lèi)sg_sysfs_class)。它在scsi_device結(jié)構(gòu)中定義了兩個(gè)內(nèi)嵌對(duì)象域(內(nèi)嵌通用對(duì)象域和內(nèi)嵌類(lèi)對(duì)象域)。
和device_register對(duì)應(yīng)的函數(shù)是device_unregister,用于從系統(tǒng)注銷(xiāo)設(shè)備。
2.6.3 驅(qū)動(dòng)
同總線類(lèi)型和設(shè)備一樣,在Linux驅(qū)動(dòng)模型中表示一個(gè)驅(qū)動(dòng)也需要兩個(gè)結(jié)構(gòu)體:device_driver和driver_private。這兩個(gè)結(jié)構(gòu)體同時(shí)存在,并且一一對(duì)應(yīng)。程序員在遵循驅(qū)動(dòng)模型編程時(shí)只需要實(shí)現(xiàn)device_driver,其結(jié)構(gòu)中的域如表2-17所示,而結(jié)構(gòu)體driver_private中的域如表2-18所示。
表2-17 device_driver結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)
表2-18 driver_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)
驅(qū)動(dòng)和總線類(lèi)型與設(shè)備的關(guān)系是:驅(qū)動(dòng)的bus域指向所屬的總線類(lèi)型,以knode_bus域?yàn)檫B接件鏈入到它以klist_drivers域?yàn)楸眍^的驅(qū)動(dòng)鏈表。驅(qū)動(dòng)還有一個(gè)綁定到它的設(shè)備鏈表,以klist_devices為表頭,設(shè)備鏈入此鏈表的連接件為knode_driver域。
在驅(qū)動(dòng)被加載時(shí),需要向Linux驅(qū)動(dòng)模型注冊(cè),為此調(diào)用它提供的公共函數(shù)driver_register(其代碼如程序2-24所示)。這個(gè)函數(shù)具有唯一的參數(shù),指向device_driver描述符的指針。
程序2-24 函數(shù)driver_register()代碼(摘自文件drivers/base/driver.c)
driver_register() 222int driver_register(struct device_driver *drv) 223{ 224 int ret; 225 struct device_driver *other; 226 227 BUG_ON(!drv->bus->p); 228 229 if ((drv->bus->probe && drv->probe) || 230 (drv->bus->remove && drv->remove) || 231 (drv->bus->shutdown && drv->shutdown)) 232 printk(KERN_WARNING "Driver '%s' needs updating - please use " 233 "bus_type methods\n", drv->name); 234 235 other = driver_find(drv->name, drv->bus); 236 if (other) { 237 put_driver(other); 238 printk(KERN_ERR "Error: Driver '%s' is already registered, " 239 "aborting...\n", drv->name); 240 return -EBUSY; 241 } 242 243 ret = bus_add_driver(drv); 244 if (ret) 245 return ret; 246 ret = driver_add_groups(drv, drv->groups); 247 if (ret) 248 bus_remove_driver(drv); 249 return ret; 250}
在注冊(cè)驅(qū)動(dòng)前,必須為驅(qū)動(dòng)指定了總線類(lèi)型,并且該總線類(lèi)型已經(jīng)注冊(cè)到Linux驅(qū)動(dòng)模型,第227行確保這一點(diǎn)。
第229~233行給出一個(gè)警告消息,如果總線類(lèi)型和驅(qū)動(dòng)都定義了相同名字的函數(shù)。實(shí)際上,Linux驅(qū)動(dòng)模型只會(huì)使用其中的一個(gè)。前面,在really_probe函數(shù)中,我們已經(jīng)看到過(guò)了。
第235~241行調(diào)用driver_find在總線類(lèi)型的驅(qū)動(dòng)鏈表中查找該驅(qū)動(dòng)名,如果找到,表明這個(gè)驅(qū)動(dòng)名已經(jīng)在使用中,我們只好返回錯(cuò)誤。
否則繼續(xù),在第243行調(diào)用bus_add_driver添加驅(qū)動(dòng)到系統(tǒng),我們馬上會(huì)詳細(xì)分析它。
如果一切正常,第246行添加驅(qū)動(dòng)的屬性組文件并結(jié)束驅(qū)動(dòng)注冊(cè)過(guò)程。
就驅(qū)動(dòng)注冊(cè)來(lái)講,函數(shù)bus_add_driver(該函數(shù)的代碼如程序2-25所示)無(wú)疑是最主要的函數(shù),因?yàn)榇蟛糠止ぷ鬟€是要在總線類(lèi)型上做的。這個(gè)函數(shù)返回0表示成功,否則表示錯(cuò)誤。
程序2-25 函數(shù)bus_add_driver代碼(摘自文件drivers/base/bus.c)
driver_register()→bus_add_driver() 648int bus_add_driver(struct device_driver *drv) 649{ 650 struct bus_type *bus; 651 struct driver_private *priv; 652 int error = 0; 653 654 bus = bus_get(drv->bus); 655 if (!bus) 656 return -EINVAL; 657 658 pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name); 659 660 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 661 if (!priv) { 662 error = -ENOMEM; 663 goto out_put_bus; 664 } 665 klist_init(&priv->klist_devices, NULL, NULL); 666 priv->driver = drv; 667 drv->p = priv; 668 priv->kobj.kset = bus->p->drivers_kset; 669 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, 670 "%s", drv->name); 671 if (error) 672 goto out_unregister; 673 674 if (drv->bus->p->drivers_autoprobe) { 675 error = driver_attach(drv); 676 if (error) 677 goto out_unregister; 678 } 679 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); 680 module_add_driver(drv->owner, drv); 681 682 error = driver_create_file(drv, &driver_attr_uevent); 683 if (error) { 684 printk(KERN_ERR "%s: uevent attr (%s) failed\n", 685 __func__, drv->name); 686 } 687 error = driver_add_attrs(bus, drv); 688 if (error) { 689 /* How the hell do we get out of this pickle? Give up */ 690 printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n", 691 __func__, drv->name); 692 } 693 694 if (!drv->suppress_bind_attrs) { 695 error = add_bind_files(drv); 696 if (error) { 697 /* Ditto */ 698 printk(KERN_ERR "%s: add_bind_files(%s) failed\n", 699 __func__, drv->name); 700 } 701 } 702 703 kobject_uevent(&priv->kobj, KOBJ_ADD); 704 return 0; 705 706out_unregister: 707 kobject_put(&priv->kobj); 708 kfree(drv->p); 709 drv->p = NULL; 710out_put_bus: 711 bus_put(bus); 712 return error; 713}
因?yàn)橐羊?qū)動(dòng)添加到總線類(lèi)型,自然驅(qū)動(dòng)描述符的bus域應(yīng)該已經(jīng)設(shè)置。第654~656行的代碼確保這一點(diǎn)。
一般來(lái)說(shuō),驅(qū)動(dòng)的私有數(shù)據(jù)結(jié)構(gòu)應(yīng)該還沒(méi)有被分配,在第660~664行進(jìn)行分配,并在第666和667行將它和驅(qū)動(dòng)結(jié)構(gòu)相互關(guān)聯(lián)在一起。
然后,第668行,將驅(qū)動(dòng)通過(guò)私有數(shù)據(jù)結(jié)構(gòu)的內(nèi)嵌kobj關(guān)聯(lián)到總線類(lèi)型的drivers_kset,接下來(lái),第669行執(zhí)行kobject_init_and_add會(huì)添加驅(qū)動(dòng)到sysfs文件系統(tǒng),將它放在總線類(lèi)型所對(duì)應(yīng)目錄的drivers子目錄下。
第673~678行,如果總線類(lèi)型支持自動(dòng)探測(cè),我們調(diào)用driver_attach。這個(gè)函數(shù)(其代碼如程序2-26所示)在后面分析。
第679行將驅(qū)動(dòng)添加到總線類(lèi)型的驅(qū)動(dòng)鏈表。
接下來(lái),在驅(qū)動(dòng)目錄下創(chuàng)建各種屬性文件:
? 第682~685行,調(diào)用driver_create_file在驅(qū)動(dòng)子目錄下添加uevent屬性文件,即sys/bus/###/drivers/###/ uevent。這是一個(gè)只寫(xiě)的屬性文件,可以從用戶空間寫(xiě)事件到這個(gè)文件,從而手動(dòng)產(chǎn)生事件;
? 第687~692行,調(diào)用driver_add_attrs為驅(qū)動(dòng)的默認(rèn)屬性添加對(duì)應(yīng)的文件;
? 第694~701行,如果允許通過(guò)sysfs文件系統(tǒng)從用戶空間綁定/松綁,則調(diào)用add_bind_files,這會(huì)在驅(qū)動(dòng)目錄下創(chuàng)建bind和unbind兩個(gè)屬性文件。這兩個(gè)文件都是只寫(xiě)的,被用來(lái)手動(dòng)綁定某個(gè)設(shè)備,或者松綁某個(gè)設(shè)備。
至此,驅(qū)動(dòng)自身的添加工作已經(jīng)完成,第703行調(diào)用kobject_uevent函數(shù)向用戶空間報(bào)告KOBJ_ADD事件。
程序2-26 函數(shù)driver_attach()代碼(摘自文件drivers/base/dd.c)
driver_register()→bus_add_driver()→driver_attach() 299int driver_attach(struct device_driver *drv) 300{ 301 return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); 302}
driver_attach函數(shù)的目的是試圖將驅(qū)動(dòng)去綁定所有可能的設(shè)備,成功返回0,否則返回非零值。
它調(diào)用bus_for_each_dev遍歷總線類(lèi)型的設(shè)備鏈表,調(diào)用是傳入__driver_attach作為回調(diào)函數(shù)(其代碼如程序2-27所示)。
程序2-27 函數(shù)__driver_attach()代碼(摘自文件drivers/base/dd.c)
driver_register()→bus_add_driver()→driver_attach()→__driver_attach() 261static int __driver_attach(struct device *dev, void *data) 262{ 263 struct device_driver *drv = data; 264 265 /* 266 * Lock device and try to bind to it. We drop the error 267 * here and always return 0, because we need to keep trying 268 * to bind to devices and some drivers will return an error 269 * simply if it didn't support the device. 270 * 271 * driver_probe_device() will spit a warning if there 272 * is an error. 273 */ 274 275 if (!driver_match_device(drv, dev)) 276 return 0; 277 278 if (dev->parent) /* Needed for USB */ 279 device_lock(dev->parent); 280 device_lock(dev); 281 if (!dev->driver) 282 driver_probe_device(drv, dev); 283 device_unlock(dev); 284 if (dev->parent) 285 device_unlock(dev->parent); 286 287 return 0; 288}
到了__driver_attach函數(shù),我們又碰到了具體驅(qū)動(dòng)和具體設(shè)備的綁定問(wèn)題。除了為某些USB設(shè)備所加入的特定鎖代碼,大體流程和__device_attach是相同的。這里也調(diào)用了driver_match_device和driver_probe_device兩個(gè)函數(shù)。需要注意,這個(gè)函數(shù)只返回0,因?yàn)楫?dāng)前沒(méi)有一種錯(cuò)誤可以阻止我們讓驅(qū)動(dòng)繼續(xù)嘗試匹配設(shè)備鏈表的下一個(gè)設(shè)備。
關(guān)于device_attach和driver_attach函數(shù),做一個(gè)小結(jié):
? device_attach函數(shù)的目的是試圖將設(shè)備綁定到某個(gè)驅(qū)動(dòng)。它以回調(diào)函數(shù)__device_attach調(diào)用bus_for_each_drv。device_attach和__device_attach函數(shù)都用返回1表示設(shè)備被綁定到驅(qū)動(dòng),返回0表示沒(méi)有找到匹配的驅(qū)動(dòng);
? driver_attach函數(shù)的目的是試圖將驅(qū)動(dòng)去綁定所有可能的設(shè)備。它以回調(diào)函數(shù)__driver_attach調(diào)用bus_for_each_dev。driver_attach和__driver_attach函數(shù)都用返回0表示成功,返回1表示錯(cuò)誤。但實(shí)際上__driver_attach函數(shù)只會(huì)返回0。
和driver_register對(duì)應(yīng)的函數(shù)是driver_unregister,用于從系統(tǒng)注銷(xiāo)驅(qū)動(dòng)。
2.6.4 類(lèi)
Linux驅(qū)動(dòng)模型中的類(lèi)用相互關(guān)聯(lián)的class和class_private域表示。class結(jié)構(gòu)中的域如表2-19所示,class_private結(jié)構(gòu)中的域如表2-20所示。
表2-19 class結(jié)構(gòu)中的域(來(lái)自文件include/linux/class.h)
表2-20 class_private結(jié)構(gòu)中的域(來(lái)自文件drivers/base/base.h)
每個(gè)類(lèi)有一個(gè)內(nèi)嵌的kset,即class_subsys域,因?yàn)樗陬?lèi)被注冊(cè)后,會(huì)在/sys/class下創(chuàng)建一個(gè)和類(lèi)名對(duì)應(yīng)的子目錄。類(lèi)有兩個(gè)鏈表,一個(gè)鏈接屬于該類(lèi)的所有設(shè)備,另一個(gè)鏈接注冊(cè)到這個(gè)類(lèi)的接口。另外,需要說(shuō)明的是class_dirs域,它的作用在前面已經(jīng)介紹過(guò)。
子系統(tǒng)要注冊(cè)類(lèi),需調(diào)用Linux驅(qū)動(dòng)模型提供的class_register方法。它被定義為一個(gè)宏,在文件include/linux/device.h,是__class_register的簡(jiǎn)單封裝。我們直接看__class_register,其函數(shù)的代碼如程序2-28所示。
程序2-28 函數(shù)__class_register()代碼(摘自文件drivers/base/class.c)
__class_register() 154int __class_register(struct class *cls, struct lock_class_key *key) 155{ 156 struct class_private *cp; 157 int error; 158 159 pr_debug("device class '%s': registering\n", cls->name); 160 161 cp = kzalloc(sizeof(*cp), GFP_KERNEL); 162 if (!cp) 163 return -ENOMEM; 164 klist_init(&cp->class_devices, klist_class_dev_get, klist_class_dev_put); 165 INIT_LIST_HEAD(&cp->class_interfaces); 166 kset_init(&cp->class_dirs); 167 __mutex_init(&cp->class_mutex, "struct class mutex", key); 168 error = kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name); 169 if (error) { 170 kfree(cp); 171 return error; 172 } 173 174 /* set the default /sys/dev directory for devices of this class */ 175 if (!cls->dev_kobj) 176 cls->dev_kobj = sysfs_dev_char_kobj; 177 178#if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK) 179 /* let the block class directory show up in the root of sysfs */ 180 if (cls != &block_class) 181 cp->class_subsys.kobj.kset = class_kset; 182#else 183 cp->class_subsys.kobj.kset = class_kset; 184#endif 185 cp->class_subsys.kobj.ktype = &class_ktype; 186 cp->class = cls; 187 cls->p = cp; 188 189 error = kset_register(&cp->class_subsys); 190 if (error) { 191 kfree(cp); 192 return error; 193 } 194 error = add_class_attrs(class_get(cls)); 195 class_put(cls); 196 return error; 197}
第161~163行分配類(lèi)的私有數(shù)據(jù)結(jié)構(gòu),接著進(jìn)行一些初始化。第168~172行設(shè)置類(lèi)的class_subsys域的內(nèi)嵌kobject的名字為類(lèi)的名字,這就是顯示在/sys/class/下的類(lèi)的目錄名。
早期的類(lèi)被分開(kāi)放置。block類(lèi)位于/sys/目錄下,而其他類(lèi)則位于/sys/class/目錄下,最新版本的內(nèi)核作了統(tǒng)一。所有類(lèi)都被放在/sys/class/目錄下,這就是第178~184行代碼產(chǎn)生的效果。
第186和187行,將類(lèi)結(jié)構(gòu)和類(lèi)私有數(shù)據(jù)結(jié)構(gòu)相互關(guān)聯(lián)起來(lái)。
第189~193行調(diào)用kset_register注冊(cè)類(lèi)的class_subsys,這最終創(chuàng)建類(lèi)在sysfs文件系統(tǒng)中的對(duì)應(yīng)目錄。第194行,調(diào)用add_class_attrs在類(lèi)的目錄下創(chuàng)建默認(rèn)的類(lèi)屬性文件。
和class_register對(duì)應(yīng)的函數(shù)是class_unregister,用于從系統(tǒng)注銷(xiāo)類(lèi)。
2.6.5 接口
相對(duì)于前面的結(jié)構(gòu),接口比較簡(jiǎn)單,它用class_interface(其結(jié)構(gòu)中的域如表2-21所示)來(lái)表示。
表2-21 class_interface結(jié)構(gòu)中的域(來(lái)自文件include/linux/device.h)
每個(gè)接口都是相對(duì)于某個(gè)類(lèi)的,通過(guò)class域指向它。接口被鏈接到類(lèi)的接口鏈表,類(lèi)的class_interfaces域?yàn)楸眍^,接口的node域?yàn)檫B接件。
Linux驅(qū)動(dòng)模型提供用于類(lèi)接口注冊(cè)的公共函數(shù)為class_interface_register,該函數(shù)的代碼如程序2-29所示。
程序2-29 函數(shù)class_interface_register()代碼(摘自文件drivers/base/class.c)
class_interface_register() 447int class_interface_register(struct class_interface *class_intf) 448{ 449 struct class *parent; 450 struct class_dev_iter iter; 451 struct device *dev; 452 453 if (!class_intf || !class_intf->class) 454 return -ENODEV; 455 456 parent = class_get(class_intf->class); 457 if (!parent) 458 return -EINVAL; 459 460 mutex_lock(&parent->p->class_mutex); 461 list_add_tail(&class_intf->node, &parent->p->class_interfaces); 462 if (class_intf->add_dev) { 463 class_dev_iter_init(&iter, parent, NULL, NULL); 464 while ((dev = class_dev_iter_next(&iter))) 465 class_intf->add_dev(dev, class_intf); 466 class_dev_iter_exit(&iter); 467 } 468 mutex_unlock(&parent->p->class_mutex); 469 470 return 0; 471}
函數(shù)要求傳入的類(lèi)接口不為NULL,并且設(shè)置了class域,即被關(guān)聯(lián)到某個(gè)類(lèi)(第453~454行)。
注冊(cè)類(lèi)接口的具體操作只有兩個(gè):
? 將類(lèi)接口添加到類(lèi)的接口鏈表,這是第461行;
? 如果類(lèi)接口提供了add_dev方法,對(duì)類(lèi)的設(shè)備鏈表上的每個(gè)設(shè)備調(diào)用之,這是第462~467行。
需要強(qiáng)調(diào)的是后面一點(diǎn),也是“熱插拔”的一種表現(xiàn)。類(lèi)接口雖然不綁定到設(shè)備,但它可以通過(guò)自定義的回調(diào)方法作用于設(shè)備,并且無(wú)論是先發(fā)現(xiàn)設(shè)備,還是先注冊(cè)接口。
和class_interface_register對(duì)應(yīng)的函數(shù)是class_interface_unregister,用于從系統(tǒng)注銷(xiāo)接口。
- 電氣自動(dòng)化專業(yè)英語(yǔ)(第3版)
- 大數(shù)據(jù)項(xiàng)目管理:從規(guī)劃到實(shí)現(xiàn)
- Drupal 7 Multilingual Sites
- 嵌入式Linux上的C語(yǔ)言編程實(shí)踐
- 大數(shù)據(jù)處理平臺(tái)
- Embedded Programming with Modern C++ Cookbook
- 統(tǒng)計(jì)學(xué)習(xí)理論與方法:R語(yǔ)言版
- Google SketchUp for Game Design:Beginner's Guide
- 會(huì)聲會(huì)影X4中文版從入門(mén)到精通
- 貫通開(kāi)源Web圖形與報(bào)表技術(shù)全集
- 電動(dòng)汽車(chē)驅(qū)動(dòng)與控制技術(shù)
- 計(jì)算機(jī)應(yīng)用基礎(chǔ)實(shí)訓(xùn)(職業(yè)模塊)
- Creating ELearning Games with Unity
- Visual Basic項(xiàng)目開(kāi)發(fā)案例精粹
- 新世紀(jì)Photoshop CS6中文版應(yīng)用教程