2.6 驅動模型對象
在上面介紹了內核對象和syfs文件系統,本節分析Linux驅動模型。Linux驅動模型適用于Linux各種子系統,它描述了總線類型、設備、驅動以及類和接口之間的關系。每個子系統都有屬于自己的唯一的總線類型,它上面鏈接了注冊到這一總線類型上的多個設備,另外它還將注冊到這個總線類型的多個驅動鏈接在一起。每個設備可能暫時還未綁定到驅動,或者可能被綁定到最多一個驅動。而驅動則可以暫時未綁定任何設備,也可能已經綁定了一個或多個設備。
總線類型、設備、驅動這三者的關系,使得子系統支持熱插拔變得非常容易。早期的計算機系統不支持熱插拔,驅動必須在設備被發現之前已經被加載。而支持熱插拔的系統還可以允許在驅動已經加載的情況下安裝設備。事實上,Linux驅動模型的處理方式是,在設備被發現時,它被加入所注冊總線類型的設備鏈表,然后在該總線類型的驅動鏈表中查找可以處理這個設備的驅動并綁定。類似地,在驅動被加載時,被加入到它所注冊的總線類型的驅動鏈表,同時在該總線類型的設備鏈表中查找并綁定可以處理的設備。顯然,在這樣的實現思路下,設備發現和驅動加載的順序不再那么重要了。
從上面的描述我們看到,設備最多可以屬于一個驅動。在某些情況下,這個限制過于嚴苛。比如,SCSI磁盤,它作為一個磁盤類設備,被唯一地綁定到SCSI磁盤驅動。但是,有時我們希望把它作為一個普通的SCSI設備,向它發送INQUIRY等SCSI命令。Linux驅動模型對此的解決方案是,引入類和接口。每個設備還可以唯一屬于某個類,設備被鏈入到類的設備鏈表。類還有另一個接口鏈表,用于鏈接注冊到該類的所有接口。在設備被發現,或接口被添加時,無論何種順序,只要它們屬于同一個類,那么接口注冊的回調函數都會被作用在設備之上。
Linux驅動模型實現的對象關系如圖2-11所示,圖中各種關系的功能如下。

圖2-11 Linux驅動模型對象之間的關系
? bus_type和bus_type_private共同描述總線類型。
? device_driver和driver_private共同描述驅動。
? device和device_private共同描述設備。
? class和class_private共同描述類。
? class_interface表示的接口。
2.6.1 總線類型
Linux驅動模型中的總線類型結構使用bus_type和bus_type_private兩個結構體來表示。之所以這樣,純粹是從編程的角度來考慮的,程序員在實現一個新的總線類型時,只需要定義bus_type結構體,并實現其中的方法和函數,而不必關注那些完全被驅動模型核心使用的內部域。
但是,需要注意的是,這兩個結構體是同時存在的,并且有一一對應關系。事實上,兩個結構體各自有一個指針域指向對方。bus_type結構中的域和bus_type_private結構中的域分別如表2-12和表2-13所示。
表2-12 bus_type結構中的域(來自文件include/linux/device.h)
表2-13 bus_type_private結構中的域(來自文件drivers/base/base.h)
每個總線類型被注冊后,將在/sys/bus下創建一個和其名字對應的子目錄,在該子目錄下又有兩個子目錄:drivers和devices。和總線類型子目錄對應的內嵌的kset,即subsys域,而drivers_kset和devices_kset是指向和drivers子目錄以及devices子目錄對應的kset的指針。
總線類型有兩個鏈表:一個是在該總線類型上發現的設備鏈表,另一個是在該總線類型上加載的驅動鏈表。注意這里用作鏈表的表頭和連接件結構分別為klist和klist_node,它們實際上也是Linux內核雙循環鏈表結構list_head的封裝,具體實現的分析留給讀者。
某些子系統(或模塊)會定義自己的總線類型,這時初始化的一個主要工作就是向Linux內核注冊其總線類型,對于某些子系統(或模塊),它甚至是初始化的全部工作。
Linux驅動模型核心提供了bus_register函數(其代碼如程序2-12所示),被用來注冊總線類型。子系統(或模塊)調用該函數前,必須已經聲明了一個bus_type類型的變量,調用時將其指針作為參數傳入。
程序2-12 函數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}
總線類型注冊的主要任務是分配和初始化相關的結構,以及在sysfs文件系統中創建相應的子目錄樹。
我們說過,在調用這個函數前,用戶已經聲明了一個bus_type類型的變量,但是bus_type_private結構的空間還沒有分配,第885行分配之。第889和890行讓兩個結構相互關聯起來。
接下來對總線類型私有數據結構的內嵌kset的內嵌kobject進行初始化,包括如下內容。
? 設置其名字為總線類型名字(第894行)。
? 設置其kset為bus_kset,而后者在系統初始化過程的buses_init函數(見文件drivers/base/bus.c)中調用kset_create_and_add創建,它在sysfs文件系統中對應的目錄為sys/bus,它的kset_uevent_ops為bus_uevent_ops(第895行)。
? 設置其ktype為bus_ktype(第896行)。
做過上面的設置后,調用kset_register一次性初始化總線類型私有數據結構的內嵌kset,并將它添加到sysfs文件系統。這將在sys/bus/下創建一個以總線類型名字(為敘述方便,假設為###)為名字的子目錄,即sys/bus/###/。
第906~908行調用bus_create_file在該子目錄下添加uevent屬性文件,即sys/bus/###/uevent。這是一個只寫的屬性文件,這用于手動觸發熱插拔。熱插拔機制需要用戶空間服務程序(例如udevd)的配合,該程序偵聽內核空間發出的uevent事件,匹配規則做相應的動作。在系統啟動后內核注冊了很多設備,但用戶空間還沒有啟動,所以這些事件沒有機會被處理。為此,在每個注冊的設備文件夾下生成一個uevent屬性文件,當服務程序啟動后,可以掃描sysfs文件系統中所有uevent屬性文件,向其寫入add命令,從而觸發uevent事件,這樣服務程序就有機會處理這些事件了。
第910~915行在該子目錄下添加devices子目錄(即sys/bus/###/devices),對應的kset被保存在devices_kset域。以便將來在該總線類型下發現設備的時候,向對應的子目錄中添加設備的符號鏈接。
第917~922行在該子目錄下添加drivers子目錄(即sys/bus/###/drivers),對應的kset被保存在drivers_kset域。以便將來在該總線類型下加載驅動的時候,向對應的子目錄中添加驅動的子目錄。
第924行和第925行分別初始化總線類型的設備鏈表和驅動鏈表。
第927~929行調用add_probe_files在該總線類型的子目錄下添加drivers_probe和drivers_autoprobe兩個文件,即sys/bus/###/drivers_probe和sys/bus/###/drivers_autoprobe。后者用來顯示和修改總線類型描述符的drivers_ autoprobe域,而前者是一個只寫的屬性文件,向其中寫任何值都會觸發對總線類型對它的設備重新掃描。
最后,第931~933行調用bus_add_attrs在總線類型子目錄下為聲明bus_type時給出的總線類型屬性添加文件。例如對于PCI總線,只定義了一個這樣的屬性:rescan。
和bus_register對應的函數是bus_unregister,用于總線類型注銷時,它執行如下一些相反的操作。
? 調用bus_remove_attrs為總線類型刪除公有屬性文件,這些屬性由bus_type類型變量的bus_attrs域指針所指向。
? 調用remove_probe_files從總線類型下刪除probe和autoprobe兩個屬性文件。
? 調用kset_unregister從子樹中刪除drivers子目錄。
? 調用kset_unregister從子樹中刪除devices子目錄。
? 調用bus_remove_file從總線類型中刪除uevent屬性文件。
? 調用kset_unregister從系統中注銷總線類型內嵌的kset。
Linux驅動模型核心提供了遍歷每個鏈表的輔助函數。
? 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 *));
這兩個輔助函數的使用會在后面看到,它們都在成功時返回0,“錯誤”時返回非零值。
bus_for_each_dev函數遍歷總線類型的設備鏈表,bus_for_each_drv函數遍歷總線類型的驅動鏈表,對于鏈表中每個設備或驅動,調用傳入的回調函數fn。回調函數返回零就繼續處理鏈表的后一個設備或驅動,返回非零值則退出循環。這主要用在設備和驅動的匹配,即在設備鏈表中查找驅動可以綁定的設備,或者在驅動鏈表中查找可以綁定設備的驅動。
我們有必要對“錯誤”的引號做一下說明。記住設備只能被綁定到最多一個驅動。用設備在總線類型的驅動鏈表上尋求匹配的時候,無論是否找到匹配項,都是正常的。因為一定要區分,我們將匹配作為一種“錯誤”,出現時就退出循環,不需要繼續處理鏈表中余下的驅動了。
對比地看,驅動可以綁定一個或多個設備。用驅動在總線類型的設備鏈表上進行匹配的時候,應該處理完所有的設備,除非出現真正意義上的錯誤。而且對每個設備,無論是否匹配,都是正常的。這里的錯誤用法采用的是正常的思維。
更具體的解釋請參看后面關于device_attach和driver_attach函數的跟蹤。
2.6.2 設備
Linux驅動模型中的設備,我們有時簡稱為驅動模型設備,使用device和device_private兩個結構體來表示。兩個結構體相互關聯,各自有一個域指向對方。這兩個結構體中的域分別如表2-14和表2-15所示。
表2-14 device結構中的域(來自文件include/linux/device.h)
表2-15 device_private結構中的域(來自文件drivers/base/base.h)
在Linux驅動模型下,每個設備被歸到一種總線類型,通過bus域指向它。設備也可以屬于唯一的類,由class域指向。如果設備被綁定,則通過driver域指向對應的驅動。同內核對象一樣,設備也被組織成層次結構,通過parent域指向父親設備。對應地,設備被加入到如下四個鏈表。
? 所屬總線類型的設備鏈表,總線類型的klist_devices域為這個鏈表的表頭,設備的knode_bus域為鏈入此鏈表的連接件。
? 所屬類的設備鏈表,類的class_devices域為這個鏈表的表頭,設備的knode_class域為鏈入此鏈表的連接件。
? 綁定它的驅動的設備鏈表,驅動的klist_devices域為這個鏈表的表頭,設備的knode_driver域為鏈入此鏈表的連接件。
? 父設備的孩子鏈表,父設備的klist_children域為這個鏈表的表頭,設備的knode_parent域為鏈入此鏈表的連接件。
在實際使用中,驅動模型設備被內嵌到業務子系統的設備結構中。后者通過驅動模型設備被鏈接到業務子系統的總線類型實例,或者被鏈接到業務子系統的類實例,兩者取其一,也就是說每個驅動模型設備只會使用knode_bus和knode_class兩個域中的一個。如果業務子系統的設備結構需要同時被鏈接到總線類型實例和類實例,則需要包含兩個內嵌的驅動模型設備,用于鏈接到總線類型實例的驅動模型設備被稱為通用設備,而用于鏈接到類實例的驅動模型設備被稱為類設備。
業務子系統可以借助于驅動模型中的鏈表實現對設備的遍歷,在其總線類型實例或類實例中查找滿足某些條件的業務子系統設備。例如PCI子系統中的for_each_pci_dev宏,還有SCSI子系統中的scsi_host_lookup函數。
同樣,類似于kobject,每個device都屬于一種設備類型,其結構為device_type(該結構中的域如表2-16所示),定義了這種類型設備的公共方法和成員。
表2-16 device_type結構中的域(來自文件include/linux/device.h)
設備被發現后,需要向Linux驅動模型注冊。設備注冊的函數為device_register(其代碼如程序2-13所示),它有唯一的參數,即指向device描述符的指針。調用者已經為這個device描述符分配好了空間,實際上,device描述符往往被嵌入其他結構中,它的空間隨著其他結構分配。
程序2-13 函數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}
設備注冊分配兩個步驟:初始化設備,以及將設備添加到sysfs。分別調用device_initialize(代碼如程序2-14所示)和device_add函數(代碼如程序2-15所示)。
程序2-14 函數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是設備注冊的第一步。我們只關注前面兩行代碼。第559行將設備內嵌的kobject關聯到內核對象集devices_kset,而后者在系統初始化過程的devices_init函數(見文件drivers/base/core.c)中調用kset_create_and_add創建,它在sysfs文件系統中對應的目錄為sys/devices。此外,devices_kset的uevent操作表并賦值為device_uevent_ops。如前所述,這將控制內核中設備狀態發生變化時向用戶空間發送uevent的方式。
第560行將設備的內嵌對象的對象類型設置為device_ktype,其中定義了設備的公共屬性操作表,以及釋放方法。后者確保在對設備的引用計數減到0時,銷毀整個device結構。
程序2-15 函數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是設備注冊的第二步,負責將設備添加到系統。
設備的私有數據結構可能還沒有被分配,在第900~904行分配。在第911~919行設置并驗證設備的名字。然后,調用setup_parent設置設備的父親,同時確定了設備在sysfs中的位置。這是一個比較復雜的過程,我們后面會專門討論它。
第932~934行,調用kobject_add將設備的內嵌kobject添加到系統,這將在sys/devices/目錄下添加一個以設備名為名字的子目錄;
然后在sysfs文件系統中創建屬性文件:第940~942行創建uevent屬性對應的文件,這是一個只寫的屬性文件,可以從用戶空間寫事件到這個文件,從而手動產生事件。
第944~954行,如果設備有設備號:
? 在設備目錄下創建dev屬性對應的文件,這是一個只讀的屬性文件,用戶空間讀取這個文件時,將返回設備的設備號,以##:##的形式;
? 調用device_create_sys_dev_entry函數(文件drivers/base/core.c)在sys/block/或sys/char/下創建一個到設備所在目錄的符號鏈接,鏈接名為##:##的形式的設備號。具體選擇目錄取決于設備所屬的類。如果屬于block_class,即為塊設備,將在sys/block下創建;如果屬于其他類,或者不屬于任何類,都會在sys/char/下創建。
第956~958行,調用device_add_class_symlinks,如果設備屬于某個類,則為設備創建和類相關的符號鏈接。這又包括下面的步驟:
? 在設備目錄下,創建到所屬類的符號鏈接,鏈接名為subsystem;
? 在所屬類的目錄下,創建到設備的符號鏈接,鏈接名為設備名;
? 如果設備有父設備,并且設備不為分區,在設備目錄下,創建到其父設備的符號鏈接,鏈接名為device。
第959~961行,調用device_add_attrs為設備的默認屬性添加對應的文件。
第962~964行,調用bus_add_device,如果設備有指定總線類型,則會將設備添加到該總線類型。這又包括下面的步驟:
? 為設備添加該總線類型下設備的公有屬性文件,這些屬性由設備所屬bus_type類型變量的dev_attrs域指針所指向;
? 在總線類型子目錄的devices目錄下創建到該設備的符號鏈接,鏈接名為設備名;
? 在設備的目錄下創建到總線類型的符號鏈接,鏈接名為subsystem;
? 將設備連接到總線類型的設備鏈表。
在bus_add_device函數過程中,還會調用make_deprecated_bus_links函數,后者被早期的版本用于在設備目錄下創建另外一個到總線類型的符號鏈接,鏈接名為bus。如果內核編譯未指定CONFIG_SYSFS_DEPRECATED選項,那么這個函數為空,什么也不做。
至此,設備自身的添加工作已經完成,第977行調用kobject_uevent向用戶空間報告KOBJ_ADD事件。
然后調用bus_probe_device試圖將設備綁定到某個驅動,這個過程我們也在后面專門討論。
第979~981行,如果指定了parent,則將設備添加到它的孩子鏈表中。
此外,第983~995行,如果設備設置了class域,則將設備添加到類的設備鏈表。而且,對于注冊到該類的每個接口,如果定義了add_dev回調函數,則調用它。這個動作是系統對設備熱插處理在類上的體現。
到現在,設備注冊過程已經比較完整了,除了第924行的setup_parent函數和第978行的bus_probe_device函數。它們的作用都很重要,并且過程也比較復雜,下面用專門的篇幅來討論。
1.設備在sysfs的位置
setup_parent函數的根本目的就是確定設備在sysfs文件系統中的位置。之所以說它重要,是因為理解內核對象之間的層次關系就是理解Linux驅動模型的一個關鍵。
分析device的結構,可以看到有兩個parent域:
? device描述符的parent域,指向設備的父設備;
? device描述符內嵌的kobject的parent域,決定了設備在sysfs文件系統中的位置。
setup_parent函數(其代碼如程序2-16所示)的實質就是為后者賦值。它傳入兩個參數:第一個為指向設備本身的device描述符的指針;第二個為指向其父設備的device描述符的指針。
程序2-16 函數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函數將主要工作交給get_device_parent函數(其代碼如程序2-17所示)。若后者的返回值非NULL,則保存在設備的內嵌對象的parent域。
程序2-17 函數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描述符的內嵌kobject的父kobject的因素有如下三個:
? 設備本身device描述符的parent域;
? 設備本身device描述符的class域;
? 父設備device描述符的class域(如果父設備存在的話)。
Linux當前版本的處理方法和以前版本稍有不同。我們只考慮當前版本的處理規則,即代碼中未定義CONFIG_SYS_DEPRECATED的部分。設置設備內嵌內核對象的父對象的情況有6種,如圖2-12所示,具體說明如下。
? device有class,有parent,且parent有class,不管parent->class和device->class是否相同,parent_kobj都為parent內嵌的kobject;這分別對應圖中的情況①和②。
? device有class,有parent,且parent沒有class,則parent_obj為新創建的一個kobject,保存在class的class_dirs(這是個kset)里。這個新創建的kobject的parent為parent內嵌的kobject,名稱為device的class_name,也就是說,它對應的sysfs目錄為sys/devices/.../parent_name/ class_name/。這對應圖中的情況③。

圖2-12 設置設備內嵌內核對象的父對象
? device有class,沒有parent,則parent_kobj為新創建的一個kobject,保存在class的class_dirs(這是個kset)里。這個新創建的kobject的parent為sys/devices/virtual/目錄對應的kobject,名稱為device的class_name,也就是說,它對應的sys目錄為sys/devices/virtual/class_name。這對應圖中的情況④。
? device無class,有parent,則device->kobj.parent = parent.kobj。這對應圖中的情況⑤。
? device無class,無parent,則device->kobj.parent為空。這對應圖中的情況⑥。應該說這是一種理論上的可能,在Linux代碼中沒有找到這樣的例子(萬一出現,設備目錄將直接建在sys/之下)。我們將它列舉在這里,純粹出于完整性的考慮。
對于情況③和④的補充說明:class_dirs是用于保存那些為了將其父設備為空,或其父設備的class為空的設備掛入sys/devices/子樹,而新創建的內核對象,它將這些內核對象鏈接在一起。這些內核對象將作為前述設備內嵌內核對象的父對象。
綜合上述情況,deivce在sysfs中的目錄有以下幾種形式:
? sys/devices/.../parent_name/device_name;
? sys/devices/.../parent_name/class_name/device_name;
? sys/devices/virtual/class_name/device_name。
如果采用新規則,所有的設備都放在sys/devices/目錄下(這不同于舊規則將設備分散在sys/devices/和sys/class/兩個目錄中),這個系統的設備樹統一在sys/devices/一個目錄中。
2.將設備綁定到驅動
將設備添加到系統,還需要嘗試將設備綁定到驅動。我們現在來看對bus_probe_device函數(其代碼如程序2-18所示)的調用。它只有一個參數,即指向設備本身的device描述符的指針;
程序2-18 函數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}
從字面上理解,這個函數的功能是自動探測設備,探測的主體是總線類型上的驅動。探測需要設備所在總線類型的支持。如果設備不屬于任何總線類型,或者總線類型沒有自動探測能力(即driver_autoprobe域為0),則什么也不需要做;否則調用device_attach函數,其代碼如程序2-19所示。
程序2-19 函數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函數的目的是試圖將設備綁定到某個驅動,返回1表示設備被綁定到驅動,返回0表示沒有找到匹配的驅動。在調用device_attach時,存在兩種情況:
? 已經為設備指定了驅動,即設備的driver域已經設置,就直接調用device_bind_driver函數。如果它返回錯誤,說明設備沒辦法被綁定到這個驅動,那么就清零設備的driver域,將錯誤報告給調用者;
? 否則遍歷總線類型的驅動鏈表,調用__device_attach函數(其代碼如程序2-20所示)嘗試將設備綁定到這些驅動。如果設備不能被綁定到任何驅動,那么bus_for_each_drv宏最終會返回0,我們直接回傳給調用者,報告錯誤。
我們只跟蹤后面一種情況的執行流程。事實上,前面一種情況,所做的工作只是其中的一部分。
程序2-20 函數__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函數試圖綁定具體驅動和具體設備。同樣,它返回1表示設備被綁定到驅動,返回0表示沒有找到匹配的驅動。這個返回值被bus_for_each_drv宏進行了非正常思維的解釋:返回1被當成一種“錯誤”,遇到即退出循環,放棄處理驅動鏈表中余下的驅動。
它調用driver_match_device函數(其代碼如程序2-21所示)檢查是否匹配,匹配失敗,返回0。若匹配成功,則調用driver_probe_device函數(其代碼如程序2-22所示)進一步探測,并向調用者傳遞它的返回值:成功時為1;否則為0。
程序2-21 函數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}
驅動和設備的匹配是子系統特定的,Linux驅動模型必須通過回調函數來實現。子系統在聲明總線類型時,可以給出其match方法。它以device和device_driver作為其參數,在具體實現中,需要先轉換為對應的設備和驅動描述符(例如pci_dev和pci_driver)。match回調函數判斷驅動是否能“匹配”設備。若是,返回1;否則返回0。如果沒定義match回調函數,我們直接返回1。認為是總線類型自動匹配了驅動和設備。
程序2-22 函數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}
進一步探測設備要求設備已經注冊。如果設備被注冊,對本驅動以及后續驅動的匹配都沒有意義,返回負的錯誤碼,這是一種真正意義上的錯誤。
這個函數調用了多個與電源管理有關的函數,因為電源管理不是本書的重點,我們跳過它們,直接跟蹤第208行的really_probe函數,其代碼如程序2-23所示。
程序2-23 函數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}
最終,如果設備可以綁定到驅動,則需要執行下列操作:
? 第113行將設備的driver域指向驅動;
? 第114~118行,調用driver_sysfs_add在sysfs文件系統中“綁定”,即在驅動對應的目錄下創建目標為設備的符號鏈接,鏈接名使用設備名,同時在設備對應的目錄下創建目標為驅動的符號鏈接,鏈接名為“driver”;
? 第120~128行,如果設備的總線類型給出了probe方法,則調用之;否則如果設備的驅動給出了probe方法,調用之。probe函數返回0表示成功;否則返回錯誤碼;
? 第130行調用driver_bound將設備鏈入驅動的設備鏈表。
如果一切正常,really_probe函數返回1,表示匹配成功。如果中間出現問題,回滾已經做過的操作,向調用者返回0。
我們再看一下總線類型和驅動的probe回調函數(如圖2-13所示)。這兩個函數都是在設備可以被綁定到驅動的情況下,用來對設備進行初始化。若成功,返回0;否則返回錯誤碼。
對于PCI子系統,總線類型的probe回調函數是pci_device_probe,驅動的probe回調函數為NULL。PCI設備的初始化是具體HBA特定的,比如PCI-SCSI適配器要在這時開始SCSI設備掃描,因此pci_device_probe進而會調用PCI驅動的probe回調函數,即具體HBA驅動的探測方法實現。注意,這里驅動的probe回調函數是指device_driver結構的probe域;而PCI驅動的probe回調函數是指pci_driver結構的probe域。
對于SCSI子系統,總線類型的probe回調函數為NULL,驅動的probe回調函數隨具體SCSI驅動的定義提供。例如對于SCSI磁盤驅動,該回調函數被定義為sd_probe,它將為這個SCSI磁盤設備分配通用磁盤描述符。
總結一下,Linux驅動模型會為設備在sysfs文件系統中創建的符號鏈接列舉如下。相應代碼分散在前面的函數里,集中整理于此。
類相關的符號鏈接:
? device對應的目錄下,名稱為subsystem,指向device->class對應的目錄;

圖2-13 PCI驅動和SCSI驅動的probe回調函數
? device->class對應的目錄下,名稱為device_name,指向device對應的目錄,僅當device對應的目錄不在device->class對應的目錄下,并且device不是塊設備分區的情況下才創建;
? device對應的目錄下,名稱為device,指向device->parent對應的目錄,僅當device有parent并且device不是塊設備分區的情況下才創建。
總線類型相關的符號鏈接:
? device->bus的devices_kset對應的目錄下,名稱為device_name,指向device對應的目錄;
? device對應的目錄下,名稱為subsystem,指向device->bus對應的目錄。
驅動相關的符號鏈接:
? device->driver對應的目錄下,名稱為device_name,指向device對應的目錄;
? device對應的目錄下,名稱為driver,指向device->driver對應的目錄。
注意:在device對應的目錄下,名稱為subsystem的符號鏈接,可能指向device->class或是device->bus對應的目錄。這里并不會出現沖突的情況,即便對應SCSI設備(可能屬于總線類型scsi_bus_type或類sg_sysfs_class)。它在scsi_device結構中定義了兩個內嵌對象域(內嵌通用對象域和內嵌類對象域)。
和device_register對應的函數是device_unregister,用于從系統注銷設備。
2.6.3 驅動
同總線類型和設備一樣,在Linux驅動模型中表示一個驅動也需要兩個結構體:device_driver和driver_private。這兩個結構體同時存在,并且一一對應。程序員在遵循驅動模型編程時只需要實現device_driver,其結構中的域如表2-17所示,而結構體driver_private中的域如表2-18所示。
表2-17 device_driver結構中的域(來自文件include/linux/device.h)
表2-18 driver_private結構中的域(來自文件drivers/base/base.h)
驅動和總線類型與設備的關系是:驅動的bus域指向所屬的總線類型,以knode_bus域為連接件鏈入到它以klist_drivers域為表頭的驅動鏈表。驅動還有一個綁定到它的設備鏈表,以klist_devices為表頭,設備鏈入此鏈表的連接件為knode_driver域。
在驅動被加載時,需要向Linux驅動模型注冊,為此調用它提供的公共函數driver_register(其代碼如程序2-24所示)。這個函數具有唯一的參數,指向device_driver描述符的指針。
程序2-24 函數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}
在注冊驅動前,必須為驅動指定了總線類型,并且該總線類型已經注冊到Linux驅動模型,第227行確保這一點。
第229~233行給出一個警告消息,如果總線類型和驅動都定義了相同名字的函數。實際上,Linux驅動模型只會使用其中的一個。前面,在really_probe函數中,我們已經看到過了。
第235~241行調用driver_find在總線類型的驅動鏈表中查找該驅動名,如果找到,表明這個驅動名已經在使用中,我們只好返回錯誤。
否則繼續,在第243行調用bus_add_driver添加驅動到系統,我們馬上會詳細分析它。
如果一切正常,第246行添加驅動的屬性組文件并結束驅動注冊過程。
就驅動注冊來講,函數bus_add_driver(該函數的代碼如程序2-25所示)無疑是最主要的函數,因為大部分工作還是要在總線類型上做的。這個函數返回0表示成功,否則表示錯誤。
程序2-25 函數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}
因為要把驅動添加到總線類型,自然驅動描述符的bus域應該已經設置。第654~656行的代碼確保這一點。
一般來說,驅動的私有數據結構應該還沒有被分配,在第660~664行進行分配,并在第666和667行將它和驅動結構相互關聯在一起。
然后,第668行,將驅動通過私有數據結構的內嵌kobj關聯到總線類型的drivers_kset,接下來,第669行執行kobject_init_and_add會添加驅動到sysfs文件系統,將它放在總線類型所對應目錄的drivers子目錄下。
第673~678行,如果總線類型支持自動探測,我們調用driver_attach。這個函數(其代碼如程序2-26所示)在后面分析。
第679行將驅動添加到總線類型的驅動鏈表。
接下來,在驅動目錄下創建各種屬性文件:
? 第682~685行,調用driver_create_file在驅動子目錄下添加uevent屬性文件,即sys/bus/###/drivers/###/ uevent。這是一個只寫的屬性文件,可以從用戶空間寫事件到這個文件,從而手動產生事件;
? 第687~692行,調用driver_add_attrs為驅動的默認屬性添加對應的文件;
? 第694~701行,如果允許通過sysfs文件系統從用戶空間綁定/松綁,則調用add_bind_files,這會在驅動目錄下創建bind和unbind兩個屬性文件。這兩個文件都是只寫的,被用來手動綁定某個設備,或者松綁某個設備。
至此,驅動自身的添加工作已經完成,第703行調用kobject_uevent函數向用戶空間報告KOBJ_ADD事件。
程序2-26 函數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函數的目的是試圖將驅動去綁定所有可能的設備,成功返回0,否則返回非零值。
它調用bus_for_each_dev遍歷總線類型的設備鏈表,調用是傳入__driver_attach作為回調函數(其代碼如程序2-27所示)。
程序2-27 函數__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函數,我們又碰到了具體驅動和具體設備的綁定問題。除了為某些USB設備所加入的特定鎖代碼,大體流程和__device_attach是相同的。這里也調用了driver_match_device和driver_probe_device兩個函數。需要注意,這個函數只返回0,因為當前沒有一種錯誤可以阻止我們讓驅動繼續嘗試匹配設備鏈表的下一個設備。
關于device_attach和driver_attach函數,做一個小結:
? device_attach函數的目的是試圖將設備綁定到某個驅動。它以回調函數__device_attach調用bus_for_each_drv。device_attach和__device_attach函數都用返回1表示設備被綁定到驅動,返回0表示沒有找到匹配的驅動;
? driver_attach函數的目的是試圖將驅動去綁定所有可能的設備。它以回調函數__driver_attach調用bus_for_each_dev。driver_attach和__driver_attach函數都用返回0表示成功,返回1表示錯誤。但實際上__driver_attach函數只會返回0。
和driver_register對應的函數是driver_unregister,用于從系統注銷驅動。
2.6.4 類
Linux驅動模型中的類用相互關聯的class和class_private域表示。class結構中的域如表2-19所示,class_private結構中的域如表2-20所示。
表2-19 class結構中的域(來自文件include/linux/class.h)
表2-20 class_private結構中的域(來自文件drivers/base/base.h)
每個類有一個內嵌的kset,即class_subsys域,因為它,在類被注冊后,會在/sys/class下創建一個和類名對應的子目錄。類有兩個鏈表,一個鏈接屬于該類的所有設備,另一個鏈接注冊到這個類的接口。另外,需要說明的是class_dirs域,它的作用在前面已經介紹過。
子系統要注冊類,需調用Linux驅動模型提供的class_register方法。它被定義為一個宏,在文件include/linux/device.h,是__class_register的簡單封裝。我們直接看__class_register,其函數的代碼如程序2-28所示。
程序2-28 函數__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行分配類的私有數據結構,接著進行一些初始化。第168~172行設置類的class_subsys域的內嵌kobject的名字為類的名字,這就是顯示在/sys/class/下的類的目錄名。
早期的類被分開放置。block類位于/sys/目錄下,而其他類則位于/sys/class/目錄下,最新版本的內核作了統一。所有類都被放在/sys/class/目錄下,這就是第178~184行代碼產生的效果。
第186和187行,將類結構和類私有數據結構相互關聯起來。
第189~193行調用kset_register注冊類的class_subsys,這最終創建類在sysfs文件系統中的對應目錄。第194行,調用add_class_attrs在類的目錄下創建默認的類屬性文件。
和class_register對應的函數是class_unregister,用于從系統注銷類。
2.6.5 接口
相對于前面的結構,接口比較簡單,它用class_interface(其結構中的域如表2-21所示)來表示。
表2-21 class_interface結構中的域(來自文件include/linux/device.h)
每個接口都是相對于某個類的,通過class域指向它。接口被鏈接到類的接口鏈表,類的class_interfaces域為表頭,接口的node域為連接件。
Linux驅動模型提供用于類接口注冊的公共函數為class_interface_register,該函數的代碼如程序2-29所示。
程序2-29 函數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}
函數要求傳入的類接口不為NULL,并且設置了class域,即被關聯到某個類(第453~454行)。
注冊類接口的具體操作只有兩個:
? 將類接口添加到類的接口鏈表,這是第461行;
? 如果類接口提供了add_dev方法,對類的設備鏈表上的每個設備調用之,這是第462~467行。
需要強調的是后面一點,也是“熱插拔”的一種表現。類接口雖然不綁定到設備,但它可以通過自定義的回調方法作用于設備,并且無論是先發現設備,還是先注冊接口。
和class_interface_register對應的函數是class_interface_unregister,用于從系統注銷接口。
- 輕松學PHP
- Dreamweaver CS3網頁設計50例
- 工業機器人工程應用虛擬仿真教程:MotoSim EG-VRC
- 物聯網與云計算
- 人工智能與人工生命
- RPA(機器人流程自動化)快速入門:基于Blue Prism
- 信息物理系統(CPS)測試與評價技術
- Enterprise PowerShell Scripting Bootcamp
- Mastering Game Development with Unreal Engine 4(Second Edition)
- Red Hat Linux 9實務自學手冊
- Introduction to R for Business Intelligence
- 運動控制系統(第2版)
- 計算機組裝與維修實訓
- 機器人剛柔耦合動力學
- Kubernetes on AWS