| 2009 年 1 月 08 日 sysfs 是 Linux 內核中設計較新的一種虛擬的基于內存的文件系統,它的作用與 proc 有些類(lèi)似,但除了與 proc 相同的具有查看和設定內核參數功能之外,還有為 Linux 統一設備模型作為管理之用。相比于 proc 文件系統,使用 sysfs 導出內核數據的方式更為統一,并且組織的方式更好,它的設計從 proc 中吸取了很多教訓。本文就 sysfs 的掛載點(diǎn) /sys 目錄結構、其與 Linux 統一設備模型的關(guān)系、常見(jiàn)屬性文件的用法等方面對 sysfs 作入門(mén)介紹,并且就內核編程方面,以具體的例子來(lái)展示如何添加 sysfs 支持。 | sysfs 的歷史,其與 proc 的關(guān)系? sysfs 本身并不是一項很新的技術(shù),但筆者發(fā)現,雖然 sysfs 從2003年誕生至今已有5年,但人們對 sysfs 依然缺乏了解;一個(gè)很重要的原因可能是缺乏文檔, Linux 內核方面最重要的理論書(shū)籍“Linux 設備驅動(dòng)第3版”和“理解 Linux 內核第2版”都誕生于2003年前后,并且從那以后尚未有再版過(guò),其它一些重要文章則多對 sysfs 與 proc 相提并論且舉例常常只有 proc,這導致了 sysfs 的很多重要概念至今仍鮮為人知,因此有必要對 sysfs 作更多介紹,這是寫(xiě)作本文的初衷。 | | sysfs 與 /sys sysfs 文件系統總是被掛載在 /sys 掛載點(diǎn)上。雖然在較早期的2.6內核系統上并沒(méi)有規定 sysfs 的標準掛載位置,可以把 sysfs 掛載在任何位置,但較近的2.6內核修正了這一規則,要求 sysfs 總是掛載在 /sys 目錄上;針對以前的 sysfs 掛載位置不固定或沒(méi)有標準被掛載,有些程序從 /proc/mounts 中解析出 sysfs 是否被掛載以及具體的掛載點(diǎn),這個(gè)步驟現在已經(jīng)不需要了。請參考附錄給出的 sysfs-rules.txt 文件鏈接。 sysfs 與 proc sysfs 與 proc 相比有很多優(yōu)點(diǎn),最重要的莫過(guò)于設計上的清晰。一個(gè) proc 虛擬文件可能有內部格式,如 /proc/scsi/scsi ,它是可讀可寫(xiě)的,(其文件權限被錯誤地標記為了 0444 !,這是內核的一個(gè)BUG),并且讀寫(xiě)格式不一樣,代表不同的操作,應用程序中讀到了這個(gè)文件的內容一般還需要進(jìn)行字符串解析,而在寫(xiě)入時(shí)需要先用字符串 格式化按指定的格式寫(xiě)入字符串進(jìn)行操作;相比而言, sysfs 的設計原則是一個(gè)屬性文件只做一件事情, sysfs 屬性文件一般只有一個(gè)值,直接讀取或寫(xiě)入。整個(gè) /proc/scsi 目錄在2.6內核中已被標記為過(guò)時(shí)(LEGACY),它的功能已經(jīng)被相應的 /sys 屬性文件所完全取代。新設計的內核機制應該盡量使用 sysfs 機制,而將 proc 保留給純凈的“進(jìn)程文件系統”。
初識 /sys 清單 1. 與 /sys 文件系統的一次交互(視內核版本號和外接設備的不同,在您的系統上執行這些命令的結果可能與此有所不同) $ ls -F /sys block/ bus/ class/ dev/ devices/ firmware/ fs/ kernel/ module/ power/ $ ls -F /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/ broken_parity_status enable modalias resource0 rom uevent class irq msi_bus resource0_wc subsystem@ vendor config local_cpulist power/ resource1 subsystem_device device local_cpus resource resource2 subsystem_vendor | 這 是在 Fedora 10 的 2.6.27.5-117.fc10.i686 的內核上,可以看到在 /sys 目錄下有 block, bus, class, dev, devices, firmware, fs, kernel, module, power 這些子目錄,本文將分別介紹這些目錄存在的含義。 第二個(gè) ls 命令展示了在一個(gè) pci 設備目錄下的文件, "ls" 命令的 "-F" 命令為所列出的每個(gè)文件使用后綴來(lái)顯示文件的類(lèi)型,后綴 "/" 表示列出的是目錄,后綴 "@" 表示列出的是符號鏈接文件??梢钥吹降诙€(gè)目錄下包含有普通文件 (regular file) 和符號鏈接文件 (symbolic link file) ,本文也將以這個(gè)具體的設備為例說(shuō)明其中每一個(gè)普通文件的用途。
/sys 文件系統下的目錄結構 /sys 下的目錄結構是經(jīng)過(guò)精心設計的:在 /sys/devices 下是所有設備的真實(shí)對象,包括如視頻卡和以太網(wǎng)卡等真實(shí)的設備,也包括 ACPI 等不那么顯而易見(jiàn)的真實(shí)設備、還有 tty, bonding 等純粹虛擬的設備;在其它目錄如 class, bus 等中則在分類(lèi)的目錄中含有大量對 devices 中真實(shí)對象引用的符號鏈接文件; 清單1 中在 /sys 根目錄下頂層目錄的意義如下: 表 1. /sys 下的目錄結構 | /sys 下的子目錄 | 所包含的內容 | | /sys/devices | 這是內核對系統中所有設備的分層次表達模型,也是 /sys 文件系統管理設備的最重要的目錄結構,下文會(huì )對它的內部結構作進(jìn)一步分析; | | /sys/dev | 這個(gè)目錄下維護一個(gè)按字符設備和塊設備的主次號碼(major:minor)鏈接到真實(shí)的設備(/sys/devices下)的符號鏈接文件,它是在內核 2.6.26 首次引入; | | /sys/bus | 這是內核設備按總線(xiàn)類(lèi)型分層放置的目錄結構, devices 中的所有設備都是連接于某種總線(xiàn)之下,在這里的每一種具體總線(xiàn)之下可以找到每一個(gè)具體設備的符號鏈接,它也是構成 Linux 統一設備模型的一部分; | | /sys/class | 這是按照設備功能分類(lèi)的設備模型,如系統所有輸入設備都會(huì )出現在 /sys/class/input 之下,而不論它們是以何種總線(xiàn)連接到系統。它也是構成 Linux 統一設備模型的一部分; | | /sys/block | 這 里是系統中當前所有的塊設備所在,按照功能來(lái)說(shuō)放置在 /sys/class 之下會(huì )更合適,但只是由于歷史遺留因素而一直存在于 /sys/block, 但從 2.6.22 開(kāi)始就已標記為過(guò)時(shí),只有在打開(kāi)了 CONFIG_SYSFS_DEPRECATED 配置下編譯才會(huì )有這個(gè)目錄的存在,并且在 2.6.26 內核中已正式移到 /sys/class/block, 舊的接口 /sys/block 為了向后兼容保留存在,但其中的內容已經(jīng)變?yōu)橹赶蛩鼈冊?/sys/devices/ 中真實(shí)設備的符號鏈接文件; | | /sys/firmware | 這里是系統加載固件機制的對用戶(hù)空間的接口,關(guān)于固件有專(zhuān)用于固件加載的一套API,在附錄 LDD3 一書(shū)中有關(guān)于內核支持固件加載機制的更詳細的介紹; | | /sys/fs | 這 里按照設計是用于描述系統中所有文件系統,包括文件系統本身和按文件系統分類(lèi)存放的已掛載點(diǎn),但目前只有 fuse,gfs2 等少數文件系統支持 sysfs 接口,一些傳統的虛擬文件系統(VFS)層次控制參數仍然在 sysctl (/proc/sys/fs) 接口中中; | | /sys/kernel | 這里是內核所有可調整參數的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等幾項較新的設計在使用它,其它內核可調整參數仍然位于 sysctl (/proc/sys/kernel) 接口中 ; | | /sys/module | 這里有系統中所有模塊的信息,不論這些模塊是以?xún)嚷?lián)(inlined)方式編譯到內核映像文件(vmlinuz)中還是編譯為外部模塊(ko文件),都可能會(huì )出現在 /sys/module 中: - 編譯為外部模塊(ko文件)在加載后會(huì )出現對應的 /sys/module/<module_name>/, 并且在這個(gè)目錄下會(huì )出現一些屬性文件和屬性目錄來(lái)表示此外部模塊的一些信息,如版本號、加載狀態(tài)、所提供的驅動(dòng)程序等;
- 編譯為內聯(lián)方式的模塊則只在當它有非0屬性的模塊參數時(shí)會(huì )出現對應的 /sys/module/<module_name>, 這些模塊的可用參數會(huì )出現在
/sys/modules/<modname>/parameters/<param_name> 中, - 如 /sys/module/printk/parameters/time 這個(gè)可讀寫(xiě)參數控制著(zhù)內聯(lián)模塊 printk 在打印內核消息時(shí)是否加上時(shí)間前綴;
- 所有內聯(lián)模塊的參數也可以由 "<module_name>.<param_name>=<value>" 的形式寫(xiě)在內核啟動(dòng)參數上,如啟動(dòng)內核時(shí)加上參數 "printk.time=1" 與 向 "/sys/module/printk/parameters/time" 寫(xiě)入1的效果相同;
- 沒(méi)有非0屬性參數的內聯(lián)模塊不會(huì )出現于此。
| | /sys/power | 這里是系統中電源選項,這個(gè)目錄下有幾個(gè)屬性文件可以用于控制整個(gè)機器的電源狀態(tài),如可以向其中寫(xiě)入控制命令讓機器關(guān)機、重啟等。 | | /sys/slab (對應 2.6.23 內核,在 2.6.24 以后移至 /sys/kernel/slab) | 從2.6.23 開(kāi)始可以選擇 SLAB 內存分配器的實(shí)現,并且新的 SLUB(Unqueued Slab Allocator)被設置為缺省值;如果編譯了此選項,在 /sys 下就會(huì )出現 /sys/slab ,里面有每一個(gè) kmem_cache 結構體的可調整參數。對應于舊的 SLAB 內存分配器下的 /proc/slabinfo 動(dòng)態(tài)調整接口,新式的 /sys/kernel/slab/<slab_name> 接口中的各項信息和可調整項顯得更為清晰。 | 接下來(lái)對 /sys/devices/ 下的目錄結構作進(jìn)一步探討: 清單 2. 查看 /sys/devices/ 的目錄結構 $ ls -F /sys/devices/ isa/ LNXSYSTM:00/ pci0000:00/ platform/ pnp0/ pnp1/ system/ virtual/ | 可以看到,在 /sys/devices/ 目錄下是按照設備的基本總線(xiàn)類(lèi)型分類(lèi)的目錄,再進(jìn)入進(jìn)去查看其中的 PCI 類(lèi)型的設備: 清單 3. 查看 /sys/devices/pci0000:00/ 的目錄結構 $ ls -F /sys/devices/pci0000:00/ 0000:00:00.0/ 0000:00:02.5/ 0000:00:03.1/ 0000:00:0e.0/ power/ 0000:00:01.0/ 0000:00:02.7/ 0000:00:03.2/ firmware_node@ uevent 0000:00:02.0/ 0000:00:03.0/ 0000:00:03.3/ pci_bus/ | 在 /sys/devices/pci0000:00/ 目錄下是按照 PCI 總線(xiàn)接入的設備號分類(lèi)存放的目錄,再查看其中一個(gè), 清單 4. 查看 /sys/devices/pci0000:00/ 的目錄結構 $ ls -F /sys/devices/pci0000:00/0000:00:01.0/ 0000:01:00.0/ device local_cpus power/ subsystem_vendor broken_parity_status enable modalias resource uevent class irq msi_bus subsystem@ vendor config local_cpulist pci_bus/ subsystem_device | 可以看到,其中有一個(gè)目錄 0000:01:00.0/, 其它都是屬性文件和屬性組,而如果對 0000:01:00.0/ 子目錄中進(jìn)行再列表查看則會(huì )得到 清單1 的目錄結構。 繼續以上過(guò)程可以了解整個(gè)目錄樹(shù)的結構,這里把它整理成 圖 1. sysfs 目錄層次圖 圖 1. sysfs 目錄層次圖 其中涉及到 ksets, kobjects, attrs 等很多術(shù)語(yǔ),這就不得不提到 Linux 統一設備模型。
Linux 統一設備模型 在 Linux 2.5 內核的開(kāi)發(fā)過(guò)程中,人們設計了一套新的設備模型,目的是為了對計算機上的所有設備進(jìn)行統一地表示和操作,包括設備本身和設備之間的連接關(guān)系。這個(gè)模型是在 分析了 PCI 和 USB 的總線(xiàn)驅動(dòng)過(guò)程中得到的,這兩個(gè)總線(xiàn)類(lèi)型能代表當前系統中的大多數設備類(lèi)型,它們都有完善的熱挺拔機制和電源管理的支持,也都有級連機制的支持,以橋接的 PCI/USB 總線(xiàn)控制器的方式可以支持更多的 PCI/USB 設備。為了給所有設備添加統一的電源管理的支持,而不是讓每個(gè)設備中去獨立實(shí)現電源管理的支持,人們考慮的是如何盡可能地重用代碼;而且在有層次模型的 PCI/USB 總線(xiàn)中,必須以合理形式展示出這個(gè)層次關(guān)系,這也是電源管理等所要求的必須有層次結構。 如在一個(gè)典型的 PC 系統中,中央處理器(CPU)能直接控制的是 PCI 總線(xiàn)設備,而 USB 總線(xiàn)設備是以一個(gè) PCI 設備(PCI-USB橋)的形式接入在 PCI 總線(xiàn)設備上,外部 USB 設備再接入在 USB 總線(xiàn)設備上;當計算機執行掛起(suspend)操作時(shí), Linux 內核應該以 “外部USB設備->USB總線(xiàn)設備->PCI總線(xiàn)設備” 的順序通知每一個(gè)設備將電源掛起;執行恢復(resume)時(shí)則以相反的順序通知;反之如果不按此順序則將有設備得不到正確的電源狀態(tài)變遷的通知,將無(wú)法 正常工作。 sysfs 是在這個(gè) Linux 統一設備模型的開(kāi)發(fā)過(guò)程中的一項副產(chǎn)品(見(jiàn) 參考資料 中 Greg K. Hartman 寫(xiě)作的 LinuxJournal 文章)。為了將這些有層次結構的設備以用戶(hù)程序可見(jiàn)的方式表達出來(lái),人們很自然想到了利用文件系統的目錄樹(shù)結構(這是以 UNIX 方式思考問(wèn)題的基礎,一切都是文件?。┰谶@個(gè)模型中,有幾種基本類(lèi)型,它們的對應關(guān)系見(jiàn) 表 2. Linux 統一設備模型的基本結構 : 表 2. Linux 統一設備模型的基本結構 | 類(lèi)型 | 所包含的內容 | 對應內核數據結構 | 對應/sys項 | | 設備(Devices) | 設備是此模型中最基本的類(lèi)型,以設備本身的連接按層次組織 | struct device | /sys/devices/*/*/.../ | | 設備驅動(dòng)(Device Drivers) | 在一個(gè)系統中安裝多個(gè)相同設備,只需要一份驅動(dòng)程序的支持 | struct device_driver | /sys/bus/pci/drivers/*/ | | 總線(xiàn)類(lèi)型(Bus Types) | 在整個(gè)總線(xiàn)級別對此總線(xiàn)上連接的所有設備進(jìn)行管理 | struct bus_type | /sys/bus/*/ | | 設備類(lèi)別(Device Classes) | 這是按照功能進(jìn)行分類(lèi)組織的設備層次樹(shù);如 USB 接口和 PS/2 接口的鼠標都是輸入設備,都會(huì )出現在 /sys/class/input/ 下 | struct class | /sys/class/*/ | 從內核在實(shí)現它們時(shí)所使用的數據結構來(lái)說(shuō), Linux 統一設備模型又是以?xún)煞N基本數據結構進(jìn)行樹(shù)型和鏈表型結構組織的: - kobject: 在 Linux 設備模型中最基本的對象,它的功能是提供引用計數和維持父子(parent)結構、平級(sibling)目錄關(guān)系,上面的 device, device_driver 等各對象都是以 kobject 基礎功能之上實(shí)現的;
struct kobject { const char *name; struct list_head entry; struct kobject *parent; struct kset *kset; struct kobj_type *ktype; struct sysfs_dirent *sd; struct kref kref; 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; }; | 其中 struct kref 內含一個(gè) atomic_t 類(lèi)型用于引用計數, parent 是單個(gè)指向父節點(diǎn)的指針, entry 用于父 kset 以鏈表頭結構將 kobject 結構維護成雙向鏈表; - kset: 它用來(lái)對同類(lèi)型對象提供一個(gè)包裝集合,在內核數據結構上它也是由內嵌一個(gè) kboject 實(shí)現,因而它同時(shí)也是一個(gè) kobject (面向對象 OOP 概念中的繼承關(guān)系) ,具有 kobject 的全部功能;
struct kset { struct list_head list; spinlock_t list_lock; struct kobject kobj; struct kset_uevent_ops *uevent_ops; }; | 其中的 struct list_head list 用于將集合中的 kobject 按 struct list_head entry 維護成雙向鏈表; 涉及到文件系統實(shí)現來(lái)說(shuō), sysfs 是一種基于 ramfs 實(shí)現的內存文件系統,與其它同樣以 ramfs 實(shí)現的內存文件系統(configfs,debugfs,tmpfs,...)類(lèi)似, sysfs 也是直接以 VFS 中的 struct inode 和 struct dentry 等 VFS 層次的結構體直接實(shí)現文件系統中的各種對象;同時(shí)在每個(gè)文件系統的私有數據 (如 dentry->d_fsdata 等位置) 上,使用了稱(chēng)為 struct sysfs_dirent 的結構用于表示 /sys 中的每一個(gè)目錄項。 struct sysfs_dirent { atomic_t s_count; atomic_t s_active; struct sysfs_dirent *s_parent; struct sysfs_dirent *s_sibling; const char *s_name; union { struct sysfs_elem_dir s_dir; struct sysfs_elem_symlink s_symlink; struct sysfs_elem_attr s_attr; struct sysfs_elem_bin_attr s_bin_attr; }; unsigned int s_flags; ino_t s_ino; umode_t s_mode; struct iattr *s_iattr; }; | 在上面的 kobject 對象中可以看到有向 sysfs_dirent 的指針,因此在sysfs中是用同一種 struct sysfs_dirent 來(lái)統一設備模型中的 kset/kobject/attr/attr_group. 具 體在數據結構成員上, sysfs_dirent 上有一個(gè) union 共用體包含四種不同的結構,分別是目錄、符號鏈接文件、屬性文件、二進(jìn)制屬性文件;其中目錄類(lèi)型可以對應 kobject,在相應的 s_dir 中也有對 kobject 的指針,因此在內核數據結構, kobject 與 sysfs_dirent 是互相引用的; 有了這些概念,再來(lái)回頭看 圖 1. sysfs 目錄層次圖 所表達的 /sys 目錄結構就是非常清晰明了: - 在 /sys 根目錄之下的都是 kset,它們組織了 /sys 的頂層目錄視圖;
- 在部分 kset 下有二級或更深層次的 kset;
- 每個(gè) kset 目錄下再包含著(zhù)一個(gè)或多個(gè) kobject,這表示一個(gè)集合所包含的 kobject 結構體;
- 在 kobject 下有屬性(attrs)文件和屬性組(attr_group),屬性組就是組織屬性的一個(gè)目錄,它們一起向用戶(hù)層提供了表示和操作這個(gè) kobject 的屬性特征的接口;
- 在 kobject 下還有一些符號鏈接文件,指向其它的 kobject,這些符號鏈接文件用于組織上面所說(shuō)的 device, driver, bus_type, class, module 之間的關(guān)系;
- 不同類(lèi)型如設備類(lèi)型的、設備驅動(dòng)類(lèi)型的 kobject 都有不同的屬性,不同驅動(dòng)程序支持的 sysfs 接口也有不同的屬性文件;而相同類(lèi)型的設備上有很多相同的屬性文件;
注 意,此表內容是按照最新開(kāi)發(fā)中的 2.6.28 內核的更新組織的,在附錄資源如 LDD3 等位置中有提到 sysfs 中曾有一種管理對象稱(chēng)為 subsys (子系統對象),在最新的內核中經(jīng)過(guò)重構認為它是不需要的,它的功能完全可以由 kset 代替,也就是說(shuō) sysfs 中只需要一種管理結構是 kset,一種代表具體對象的結構是 kobject,在 kobject 下再用屬性文件表示這個(gè)對象所具有的屬性;
常見(jiàn) sysfs 屬性的功能 使用 sysfs 的關(guān)鍵就是掌握這些 sysfs 屬性的用法,下面以一些常見(jiàn)的 sysfs 屬性來(lái)展示它的用法; 使用設備(PCI)的 sysfs 屬性文件 以一份桌面系統上的視頻卡為例,列舉它對應的 kobject 上的屬性文件的對應用途; 一般來(lái)說(shuō),在 Linux 桌面上都有視頻卡以支持 Xorg 軟件包作為 XWindow 服務(wù)器來(lái)運行,因此先找到 Xorg 的進(jìn)程號,查看這個(gè)進(jìn)程所使用的所有文件(注意查看這個(gè)進(jìn)程屬性需要 root 用戶(hù)權限); # ps xfa |grep Xorg 2001 tty1 Ss+ 2:24 \_ /usr/bin/Xorg :0 -nr -verbose -auth /var/run/gdm/auth-for-gdm-NPrkZK/database -nolisten tcp vt1 # lsof -nP -p 2001 Xorg 2001 root mem REG 8,3 617732 231033 /usr/lib/xorg/modules/drivers/sis_drv.so [...] Xorg 2001 root mem REG 0,0 134217728 5529 /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource0 Xorg 2001 root mem REG 0,0 131072 5531 /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource1 [...] Xorg 2001 root 7u REG 0,0 256 5504 /sys/devices/pci0000:00/0000:00:00.0/config Xorg 2001 root 8u unix 0xdbe66000 0t0 8756 socket Xorg 2001 root 9u REG 0,0 256 5528 /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/config | 注意到此 Xorg 服務(wù)器是以?xún)却嬗成?(mem) 的形式打開(kāi)了 "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource0" 和 "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/resource1" ,同時(shí)以文件讀寫(xiě)形式 (7u,9u) 打開(kāi)了 "/sys/devices/pci0000:00/0000:00:00.0/config" 和 "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/config" 事 實(shí)上, PCI 設備對應的 kobject 目錄下的 config 正是代表PCI設備的“配置空間”,對于普通 PCI (非PCI-E)設備而言,其配置空間大小一般是 256字節,這個(gè)空間可以使用十六進(jìn)制工具 dump 出來(lái),如下。(有關(guān) PCI 設備本身的三種地址空間,請參考附錄 LDD3) # hexdump -C /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/config 00000000 39 10 30 63 03 00 30 02 00 00 00 03 00 00 00 80 |9.0c..0.........| 00000010 08 00 00 d8 00 00 00 e1 01 d0 00 00 00 00 00 00 |................| 00000020 00 00 00 00 00 00 00 00 00 00 00 00 19 10 30 1b |..............0.| 00000030 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 |....@...........| 00000040 01 50 02 06 00 00 00 00 00 00 00 00 00 00 00 00 |.P..............| 00000050 02 00 30 00 0b 02 00 ff 00 00 00 00 00 00 00 00 |..0.............| 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000100 | 這 個(gè)空間正好是 256字節大小,熟悉 PCI 的人們還可以知道,從 PCI 配置空間可以讀到有關(guān)此 PCI 設備的很多有用信息,如廠(chǎng)商代碼,設備代碼,IRQ 號碼等;前四個(gè)字節 0x39 0x10 0x30 0x63 就是按小端(little endian)存放的2個(gè)短整數,因此其 PCI 廠(chǎng)商號碼和 PCI 設備號碼分別是 0x1039 和 0x6330 # lspci -v -d 1039:6330 01:00.0 VGA compatible controller: Silicon Integrated Systems [SiS] 661/741/760 PCI/AGP or 662/761Gx PCIE VGA Display Adapter (prog-if 00 [VGA controller]) Subsystem: Elitegroup Computer Systems Device 1b30 Flags: 66MHz, medium devsel BIST result: 00 Memory at d8000000 (32-bit, prefetchable) [size=128M] Memory at e1000000 (32-bit, non-prefetchable) [size=128K] I/O ports at d000 [size=128] Capabilities: [40] Power Management version 2 Capabilities: [50] AGP version 3.0 | 在 PCI 設備上除了有 config 是配置空間對用戶(hù)的接口以外,還有 resource{0,1,2,...} 是資源空間,對應著(zhù) PCI 設備的可映射內存空間;此外 PCI 設備還提供了很多接口,全部列表如下: # ls -lU /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/ 總計 0 -rw-r--r-- 1 root root 4096 12-09 00:28 uevent -r--r--r-- 1 root root 4096 12-09 00:27 resource -r--r--r-- 1 root root 4096 12-09 00:27 vendor -r--r--r-- 1 root root 4096 12-09 00:27 device -r--r--r-- 1 root root 4096 12-09 00:28 subsystem_vendor -r--r--r-- 1 root root 4096 12-09 00:28 subsystem_device -r--r--r-- 1 root root 4096 12-09 00:27 class -r--r--r-- 1 root root 4096 12-09 00:27 irq -r--r--r-- 1 root root 4096 12-09 00:28 local_cpus -r--r--r-- 1 root root 4096 12-09 00:28 local_cpulist -r--r--r-- 1 root root 4096 12-09 00:28 modalias -rw------- 1 root root 4096 12-09 00:28 enable -rw-r--r-- 1 root root 4096 12-09 00:28 broken_parity_status -rw-r--r-- 1 root root 4096 12-09 00:28 msi_bus lrwxrwxrwx 1 root root 0 12-09 00:28 subsystem -> ../../../../bus/pci drwxr-xr-x 2 root root 0 12-09 00:28 power -rw-r--r-- 1 root root 256 12-08 23:03 config -rw------- 1 root root 134217728 12-08 23:03 resource0 -rw------- 1 root root 134217728 12-09 00:28 resource0_wc -rw------- 1 root root 131072 12-08 23:03 resource1 -rw------- 1 root root 128 12-09 00:28 resource2 -r-------- 1 root root 0 12-09 00:28 rom | 可 以看到很多其它屬性文件,這些屬性文件的權限位也都是正確的,有 w 權限位的才是可以寫(xiě)入。其中大小為 4096字節的屬性一般是純文本描述的屬性,可以直接 cat 讀出和用 echo 字符串的方法寫(xiě)入;其它非 4096字節大小的一般是二進(jìn)制屬性,類(lèi)似于上面的 config 屬性文件;關(guān)于純文本屬性和二進(jìn)制屬性,在下文 編程實(shí)踐:添加sysfs支持 一節會(huì )進(jìn)一步說(shuō)明。 - 從 vendor, device, subsystem_vendor, subsystem_device, class, resource 這些只讀屬性上分別可以讀到此 PCI 設備的廠(chǎng)商號、設備號、子系統廠(chǎng)商號、子系統設備號、PCI類(lèi)別、資源表等,這些都是相應 PCI 設備的屬性,其實(shí)就是直接從 config 二進(jìn)制文件讀出來(lái),按照配置空間的格式讀出這些號碼;
- 使用 enable 這個(gè)可寫(xiě)屬性可以禁用或啟用這個(gè) PCI 設備,設備的過(guò)程很直觀(guān),寫(xiě)入1代表啟用,寫(xiě)入0則代表禁用;
- subsystem 和 driver 符號鏈接文件分別指向對應的 sysfs 位置;(這里缺少 driver 符號鏈接說(shuō)明這個(gè)設備當前未使用內核級的驅動(dòng)程序)
- resource0, resource0_wc, resource1, resource2 等是從"PCI 配置空間"解析出來(lái)的資源定義段落分別生成的,它們是 PCI 總線(xiàn)驅動(dòng)在 PCI 設備初始化階段加上去的,都是二進(jìn)制屬性,但沒(méi)有實(shí)現讀寫(xiě)接口,只支持 mmap 內存映射接口,嘗試進(jìn)行讀寫(xiě)會(huì )提示 IO 錯誤,其中 _wc 后綴表示 "合并式寫(xiě)入(write combined)" ,它們用于作應用程序的內存映射,就可以訪(fǎng)問(wèn)對應的 PCI 設備上相應的內存資源段落;
有了 PCI 核心對 sysfs 的完善支持,每個(gè)設備甚至不用單獨的驅動(dòng)程序,如這里的 "0000:01:00.0" 不需要一個(gè)內核級的驅動(dòng)程序,有了 PCI 核心對該設備的配置空間發(fā)現機制,可以自動(dòng)發(fā)現它的各個(gè)不同段落的資源屬性,在 Xorg 應用程序中可以直接以 "/usr/lib/xorg/modules/drivers/sis_drv.so" 這個(gè)用戶(hù)空間的驅動(dòng)程序對其進(jìn)行映射,就可以直接操作此視頻卡了; 有了這一個(gè) PCI 設備的示例可以知道,有了一個(gè) PCI 設備的 /sys/devices/ 設備對象,去訪(fǎng)問(wèn)它的各項屬性和設置屬性都非常簡(jiǎn)單。 使用 uevent 在 sysfs 下的很多 kobject 下都有 uevent 屬性,它主要用于內核與 udev (自動(dòng)設備發(fā)現程序)之間的一個(gè)通信接口;從 udev 本身與內核的通信接口 netlink 協(xié)議套接字來(lái)說(shuō),它并不需要知道設備的 uevent 屬性文件,但多了 uevent 這樣一個(gè)接口,可用于 udevmonitor 通過(guò)內核向 udevd (udev 后臺程序)發(fā)送消息,也可用于檢查設備本身所支持的 netlink 消息上的環(huán)境變量,這個(gè)特性一般用于開(kāi)發(fā)人員調試 udev 規則文件, udevtrigger 這個(gè)調試工具本身就是以寫(xiě)各設備的 uevent 屬性文件實(shí)現的。 這些 uevent 屬性文件一般都是可寫(xiě)的,其中 /sys/devices/ 樹(shù)下的很多 uevent 屬性在較新內核下還支持可讀: # find /sys/ -type f -name uevent -ls 11 0 -rw-r--r-- 1 root root 4096 12月 12 21:10 /sys/devices/platform/uevent 1471 0 -rw-r--r-- 1 root root 4096 12月 12 21:10 /sys/devices/platform/pcspkr/uevent 3075 0 -rw-r--r-- 1 root root 4096 12月 12 21:10 /sys/devices/platform/vesafb.0/uevent 3915 0 -rw-r--r-- 1 root root 4096 12月 12 21:10 /sys/devices/platform/serial8250/uevent 3941 0 -rw-r--r-- 1 root root 4096 12月 12 21:10 /sys/devices/platform/serial8250/tty/ttyS2/uevent 3950 0 -rw-r--r-- 1 root root 4096 12月 12 21:10 /sys/devices/platform/serial8250/tty/ttyS3/uevent 5204 0 -rw-r--r-- 1 root root 4096 12月 12 21:10 /sys/devices/platform/i8042/uevent [...] 912 0 -rw-r--r-- 1 root root 4096 12月 12 21:17 /sys/devices/pci0000:00/0000:00:02.5/uevent [...] | 上 面截取的最后一個(gè)是 SCSI 硬盤(pán)控制器設備的 uevent 屬性文件,這些 /devices/ 屬性文件都支持寫(xiě)入,當前支持寫(xiě)入的參數有 "add","remove","change","move","online","offline"。如,寫(xiě)入 "add",這樣可以向 udevd 發(fā)送一條 netlink 消息,讓它再重新一遍相關(guān)的 udev 規則文件;這個(gè)功能對開(kāi)發(fā)人員調試 udev 規則文件很有用。 # echo add > /sys/devices/pci0000:00/0000:00:02.5/uevent | 使用驅動(dòng)(PCI)的 sysfs 屬性文件, bind, unbind 和 new_id 在設備驅動(dòng) /sys/bus/*/driver/... 下可以看到很多驅動(dòng)都有 bind, unbind, new_id 這三個(gè)屬性, # find /sys/bus/*/drivers/ -name bind -ls ... | 每 一個(gè)設備驅動(dòng)程序在程序內以某種方式注明了可用于哪些硬件,如所有的 PCI 驅動(dòng)都使用 MODULE_DEVICE_TABLE 聲明了所能驅動(dòng)的 PCI 硬件的 PCI 設備號。但驅動(dòng)程序不能預知未來(lái),未來(lái)生產(chǎn)的新的硬件有可能兼容現有硬件的工作方式,就還可以使用現有硬件驅動(dòng)程序來(lái)工作。在 bind 和 unbind 發(fā)明以前,這種情況除了修改 PCI 設備驅動(dòng)程序的 DEVICE_TABLE 段落,重新編譯驅動(dòng)程序,以外別無(wú)他法,在 2.6 內核上添加了 bind 和 unbind 之后可以在不重新編譯的情況下對設備和驅動(dòng)之間進(jìn)行手工方式地綁定。 而且對于有些硬件設備可以有多份驅動(dòng)可用,但任何具體 時(shí)刻只能有一個(gè)驅動(dòng)程序來(lái)驅動(dòng)這個(gè)硬件,這時(shí)可以使用 bind/unbind 來(lái)強制使用和不使用哪一個(gè)驅動(dòng)程序;(注意關(guān)于多種驅動(dòng)程序的選擇,更好的管理方法是使用 modprobe.conf 配置文件,需要重啟才生效,而 bind/unbind 提供的是一種臨時(shí)的無(wú)需重啟立即生效的途徑;) 使用它們可以強制綁定某個(gè)設備使用或強制不使用某個(gè)驅動(dòng)程序,操作方法就是通過(guò) bind 和 unbind 接口。 # find /sys/ -type f \( -name bind -or -name unbind -or -name new_id \) -ls 69 0 -rw-r--r-- 1 root root 4096 12月 12 22:12 /sys/devices/virtual/vtconsole/vtcon0/bind 3072 0 --w------- 1 root root 4096 12月 12 22:15 /sys/bus/platform/drivers/vesafb/unbind [...] 6489 0 --w------- 1 root root 4096 12月 12 22:09 /sys/bus/pci/drivers/8139too/unbind 6490 0 --w------- 1 root root 4096 12月 12 22:09 /sys/bus/pci/drivers/8139too/bind 6491 0 --w------- 1 root root 4096 12月 12 22:15 /sys/bus/pci/drivers/8139too/new_id | 這個(gè)結果中特別提到了 8139too 這份驅動(dòng)程序的這三個(gè)屬性文件, # find /sys/bus/pci/drivers/8139too/ -ls 6435 0 drwxr-xr-x 2 root root 0 12月 12 22:08 /sys/bus/pci/drivers/8139too/ 6436 0 lrwxrwxrwx 1 root root 0 12月 12 22:08 /sys/bus/pci/drivers/8139too/0000:00:0e.0 -> ../../../../devices/pci0000:00/0000:00:0e.0 6485 0 lrwxrwxrwx 1 root root 0 12月 12 22:08 /sys/bus/pci/drivers/8139too/module -> ../../../../module/8139too 6488 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/uevent 6489 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/unbind 6490 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/bind 6491 0 --w------- 1 root root 4096 12月 12 22:08 /sys/bus/pci/drivers/8139too/new_id # echo 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind -bash: echo: write error: 沒(méi)有那個(gè)設備 # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 link/ether 00:14:2a:d1:16:72 brd ff:ff:ff:ff:ff:ff inet 192.168.1.102/24 brd 192.168.1.255 scope global eth0 3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff # echo -n 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff # echo -n 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/bind # ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo 3: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff 4: eth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 00:14:2a:d1:16:72 brd ff:ff:ff:ff:ff:ff | 這一段操作過(guò)程演示了如何對 PCI 設備 "0000:00:0e.0" 強制取消綁定 "8139too" 驅動(dòng)和強制綁定 "8139too" 驅動(dòng): - 對 unbind 屬性寫(xiě)入總線(xiàn)號碼(bus_id)即是強制取消綁定;
- 對 bind 屬性寫(xiě)入總線(xiàn)號碼(bus_id)即是強制綁定;
注意,它要求的寫(xiě)入的是總線(xiàn)號碼,對應于PCI設備的總線(xiàn)號碼是按照 "domain(4位):bus(2位):slot(2位):function號(不限)" 的方式組織,是可以從其設備 kobject 節點(diǎn)上找到,而其它類(lèi)型的總線(xiàn)有各自不同的規則; 請 特別注意: 在這一個(gè)例子中, "echo 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind" 這第一個(gè)寫(xiě)入命令以 "No such device" 為錯誤退出,而后續的 "echo -n" 命令則可以成功。這是因為內核在對總線(xiàn)號碼進(jìn)行匹配時(shí)過(guò)于嚴格了,通常的 "echo" 命令寫(xiě)入一個(gè)字符串會(huì )以一個(gè)換行符結束輸出,內核所接收到的是帶有這個(gè)換行符的 bus_id 字符串,將它與內核數據結構中的真正的 bus_id 字符串相比較,當然不能找到;所幸的是,這個(gè)問(wèn)題在最新的 2.6.28 開(kāi)發(fā)中的內核上已已經(jīng)解決,它將這個(gè)比較函數改為一個(gè)特殊實(shí)現的字符串比較,自動(dòng)忽略結尾處的換行符,在 2.6.28-rc6 內核上測試,不帶"-n"參數的 echo 命令已經(jīng)可以寫(xiě)入成功。 而 new_id 屬性文件也可以以另一種途徑解決新的設備號問(wèn)題:它是一個(gè)只寫(xiě)的驅動(dòng)屬性,可用于向其中寫(xiě)新的設備號。它支持寫(xiě)入 2至7個(gè)十六進(jìn)制整形參數,分別代表 vendor, device, subvendor, subdevice, class, class_mask, driver_data 最少為 2個(gè)是因為一個(gè) PCI設備主要以廠(chǎng)商號(vendor)和設備號(device)所唯一標定,其它 5個(gè)參數如果不輸入則缺省值為 PCI_ANY_ID(0xffff)。 5441 0 --w------- 1 root root 4096 12月 14 18:15 /sys/bus/pci/drivers/8139too/new_id | 從 8139too 驅動(dòng)上可以看到它當前所靜態(tài)支持的設備號碼列表,其中包括當前系統中的設備 10ec:8139, 假設未來(lái)有一款 8140 設備也滿(mǎn)足 8139 設備的硬件通訊協(xié)議,于是可以使用 8139too 驅動(dòng)程序來(lái)驅動(dòng)它,操作如下 # echo '10ec 8140' > /sys/bus/pci/drivers/8139too/new_id | 這在不更新驅動(dòng)程序的情況下調試設備很有用處。 使用 scsi_host 的 scan 屬性 在具有使用 SCSI 總線(xiàn)連接的主機上,與 PCI類(lèi)似的是也采用四個(gè)號碼作為一組來(lái)描述一個(gè)設備,其中位于最頂層的是 scsi_host。 我們從設備類(lèi)別 /class/為起點(diǎn)來(lái)探索: # ls -lU /sys/class/scsi_host 總計 0 lrwxrwxrwx 1 root root 0 12-13 01:59 host0 -> ../../devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0 lrwxrwxrwx 1 root root 0 12-13 01:59 host1 -> ../../devices/pci0000:00/0000:00:02.5/host1/scsi_host/host1 | 注意這是 2.6.27 內核的最新變化,在 /sys/class/ 下的都改為符號鏈接,真實(shí)的 kobject 都存在于 /sys/devices/ 中;我們這里探索其中的 host0 這個(gè) SCSI 控制器: # readlink -f /sys/class/scsi_host/host0 /sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0 # ls -lU /sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0 總計 0 -rw-r--r-- 1 root root 4096 12-13 02:02 uevent lrwxrwxrwx 1 root root 0 12-13 02:02 subsystem -> ../../../../../../class/scsi_host lrwxrwxrwx 1 root root 0 12-13 02:02 device -> ../../../host0 -r--r--r-- 1 root root 4096 12-13 02:02 unique_id -r--r--r-- 1 root root 4096 12-13 02:02 host_busy -r--r--r-- 1 root root 4096 12-13 02:02 cmd_per_lun -r--r--r-- 1 root root 4096 12-13 02:02 can_queue -r--r--r-- 1 root root 4096 12-13 02:02 sg_tablesize -r--r--r-- 1 root root 4096 12-13 02:02 unchecked_isa_dma -r--r--r-- 1 root root 4096 12-13 02:02 proc_name --w------- 1 root root 4096 12-13 02:02 scan -rw-r--r-- 1 root root 4096 12-13 02:02 state -rw-r--r-- 1 root root 4096 12-13 02:02 supported_mode -rw-r--r-- 1 root root 4096 12-13 02:02 active_mode -r--r--r-- 1 root root 4096 12-13 02:02 prot_capabilities -r--r--r-- 1 root root 4096 12-13 02:02 prot_guard_type drwxr-xr-x 2 root root 0 12-13 02:02 power | 對這些屬性文件解釋如下: - 有四個(gè) SCSI 特有的可寫(xiě)參數: scan,state,supported_mode,active_mode;可以向其中寫(xiě)入不同的參數來(lái)控制此 SCSI 控制器的各種狀態(tài);
- 其它一些可讀屬性用于讀取這個(gè) SCSI 控制器的一些當前值;
其 中的 scan 屬性文件在調試一些 SCSI 硬件驅動(dòng)時(shí)很有用,它是只寫(xiě)的,可以寫(xiě)入三個(gè)至四個(gè)以空格分開(kāi)的整數,用于分別指定對應的 host, channel, id, lun 進(jìn)行重新搜索。且這個(gè) scan 屬性支持以"-"作為通配符,如以下命令可以執行讓整個(gè) scsi_host 進(jìn)行重新搜索,這個(gè)功能用于調試某些對熱挺拔實(shí)現不完善的 SCSI 驅動(dòng)程序很有用: # echo '- - -' >/sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0/scan | 內核模塊中的 sysfs 屬性文件 以一個(gè) 8139too 模塊為例解釋在這個(gè) kboject 下每一個(gè)屬性的用途; # find /sys/module/8139too/ -ls 6408 0 -r--r--r-- 1 root root 4096 12月 13 02:17 /sys/module/8139too/version 6412 0 drwxr-xr-x 2 root root 0 12月 13 02:17 /sys/module/8139too/sections 6433 0 drwxr-xr-x 2 root root 0 12月 13 02:17 /sys/module/8139too/notes 6434 0 -r--r--r-- 1 root root 36 12月 13 02:17 /sys/module/8139too/notes/.note.gnu.build-id 6486 0 drwxr-xr-x 2 root root 0 12月 13 02:17 /sys/module/8139too/drivers 6487 0 lrwxrwxrwx 1 root root 0 12月 13 02:17 /sys/module/8139too/drivers/pci:8139too -> ../../../bus/pci/drivers/8139too | 其 中的屬性文件都是只讀的,用于提供信息。從 version, srcversion 上可以了解到這個(gè)模塊所聲明的版本號,源碼版本號, refcnt 是模塊引用計數, sections 屬性組中有一些模塊加載至內存的相應節信息, drivers/ 目錄中是對所提供的驅動(dòng)的鏈接; 因 為模塊是內核驅動(dòng)編程的最佳選擇,而一個(gè)模塊有可能提供多個(gè)驅動(dòng)程序,因而在未知一個(gè)設備在用哪一個(gè)驅動(dòng)的情況下可以先從 /sys/module/ 查找相應模塊的情況,再從 drivers/ 發(fā)現出真正的驅動(dòng)程序?;蛘咭部梢酝耆催^(guò)來(lái)利用這些信息,先用 lspci/lshw 等工具找到 /sys/devices/ 下的設備節點(diǎn),再從其設備的 driver 鏈接找到 /sys/bus/*/drivers/ 下的 device_driver, 再從 device_driver 下的 module 鏈接找到 /sys/module/*/,這樣就可以得到已加載模塊中空間是哪一個(gè)模塊在給一個(gè)設備提供驅動(dòng)程序。 更多 sysfs 屬性文件 以 上所舉的例子僅僅是一些常見(jiàn)的 sysfs 屬性用法,實(shí)際的系統中還常常有很多其它的從未見(jiàn)過(guò)的 sysfs 屬性,因此只有舉例是不夠的,即使維護了一份 sysfs 屬性用法參考大全也不夠,未來(lái)的內核版本還會(huì )出現新的 sysfs 屬性,因此還必須了解 Linux 內核代碼以找到實(shí)現這些屬性的代碼位置,以學(xué)會(huì )在沒(méi)有相應屬性文檔的情況從內核源代碼來(lái)分析其 sysfs 屬性功能。
Sysfs 源碼分析和編程實(shí)踐 從源代碼中理解 sysfs 屬性的用途 更多的 sysfs 屬性的功能只能靠閱讀源代碼來(lái)理解。還是以上文提到的 scsi_host 的 scan 屬性來(lái)理解,這個(gè)功能沒(méi)有任何文檔上有描述,因此只能去讀源代碼。 在 內核中, sysfs 屬性一般是由 __ATTR 系列的宏來(lái)聲明的,如對設備的使用 DEVICE_ATTR ,對總線(xiàn)使用 BUS_ATTR ,對驅動(dòng)使用 DRIVER_ATTR ,對類(lèi)別(class)使用 CLASS_ATTR, 這四個(gè)高級的宏來(lái)自于 <include/linux/device.h>, 都是以更低層的來(lái)自 <include/linux/sysfs.h> 中的 __ATTR/__ATRR_RO 宏實(shí)現; 因此我們在內核源碼樹(shù)中相應位置 drivers/scsi/ 找到這幾個(gè)宏的使用情況,可以得到在 drivers/scsi/scsi_sysfs.c 中: static ssize_t store_scan(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct Scsi_Host *shost = class_to_shost(dev); int res; res = scsi_scan(shost, buf); if (res == 0) res = count; return res; }; static DEVICE_ATTR(scan, S_IWUSR, NULL, store_scan); | DEVICE_ATTR 宏聲明有四個(gè)參數,分別是名稱(chēng)、權限位、讀函數、寫(xiě)函數。這里對應的,名稱(chēng)是 scan, 權限是只有屬主可寫(xiě)(S_IWUSR)、沒(méi)有讀函數、只有寫(xiě)函數。因此讀寫(xiě)功能與權限位是對應的,因為 DEVICE_ATTR 把權限位聲明與真正的讀寫(xiě)是否實(shí)現放在了一起,減少了出現不一致的可能。(上文提到 /proc/scsi/scsi 接口的權限位聲明與其功能不對應,這與注冊 proc 接口的函數設計中的不一致是有關(guān)系的,權限位聲明與功能實(shí)現不在代碼中同一個(gè)位置,因此易出錯。雖然修復 /proc/scsi/scsi 的權限位錯誤很容易,但內核團隊中多年來(lái)一直沒(méi)有人發(fā)現或未有人去修正這個(gè) BUG,應該是與 /proc/scsi/ 接口的過(guò)時(shí)有關(guān),過(guò)時(shí)的功能會(huì )在未來(lái)某個(gè)內核版本中去除。) 上面的 scan 屬性寫(xiě)入功能是在 store_scan 函數中實(shí)現的,這個(gè)接口的四個(gè)參數中, buf/count 代表用戶(hù)寫(xiě)入過(guò)來(lái)的字符串,它把 buf 進(jìn)一步傳給了 scsi_scan 函數;如果進(jìn)一步分析 scsi_scan 函數實(shí)現可以知道,它期望從 buf 中接受三個(gè)或四個(gè)整型值(也接受"-"作為通配符),分別代表 host, channel, id 三個(gè)值,(第四個(gè)整數在早期內核中曾代表 lun 號碼,但在較新內核中第四個(gè)數字被忽略,僅作為向后兼容保留接受四個(gè)整數),然后對具體的 (host, channel, id) 進(jìn)行重新掃描以發(fā)現這個(gè) SCSI 控制器上的設備變動(dòng)。 添加 sysfs 支持 如果你正在開(kāi)發(fā)的設備驅動(dòng)程序中需要與用戶(hù)層的接口,一般可選的方法有: - 注 冊虛擬的字符設備文件,以這個(gè)虛擬設備上的 read/write/ioctl 等接口與用戶(hù)交互;但 read/write 一般只能做一件事情, ioctl 可以根據 cmd 參數做多個(gè)功能,但其缺點(diǎn)是很明顯的: ioctl 接口無(wú)法直接在 Shell 腳本中使用,為了使用 ioctl 的功能,還必須編寫(xiě)配套的 C語(yǔ)言的虛擬設備操作程序, ioctl 的二進(jìn)制數據接口也是造成大小端問(wèn)題 (big endian與little endian)、32位/64位不可移植問(wèn)題的根源;
- 注冊 proc 接口,接受用戶(hù)的 read/write/ioctl 操作;同樣的,一個(gè) proc 項通常使用其 read/write/ioctl 接口,它所存在的問(wèn)題與上面的虛擬字符設備的的問(wèn)題相似;
- 注冊 sysfs 屬性;
最 重要的是,添加虛擬字符設備支持和注冊 proc 接口支持這兩者所需要增加的代碼量都并不少,最好的方法還是使用 sysfs 屬性支持,一切在用戶(hù)層是可見(jiàn)的透明,且增加的代碼量是最少的,可維護性也最好;方法就是使用 <include/linux/device.h> 頭文件提供的這四個(gè)宏,分別應用于總線(xiàn)/類(lèi)別/驅動(dòng)/設備四種內核數據結構對象上: #define BUS_ATTR(_name, _mode, _show, _store) struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store) #define CLASS_ATTR(_name, _mode, _show, _store) struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store) #define DRIVER_ATTR(_name, _mode, _show, _store) struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store) #define DEVICE_ATTR(_name, _mode, _show, _store) struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) | 總線(xiàn)(BUS)和類(lèi)別(CLASS)屬性一般用于 新設計的總線(xiàn)和新設計的類(lèi)別,這兩者一般是不用的;因為你的設備一般是以PCI等成熟的常規方式連接到主機,而不會(huì )去新發(fā)明一種類(lèi)型;使用驅動(dòng)屬性和設備 屬性的區別就在于:看你的 sysfs 屬性設計是針對整個(gè)驅動(dòng)有效的還是針對這份驅動(dòng)所可能支持的每個(gè)設備分別有效。 從 頭文件中還可以找到 show/store 函數的原型,注意到它和虛擬字符設備或 proc 項的 read/write 的作用很類(lèi)似,但有一點(diǎn)不同是 show/store 函數上的 buf/count 參數是在 sysfs 層已作了用戶(hù)區/內核區的內存復制,虛擬字符設備上常見(jiàn)的 __user 屬性在這里并不需要,因而也不需要多一次 copy_from_user/copy_to_user, 在 show/store 函數參數上的 buf/count 參數已經(jīng)是內核區的地址,可以直接操作。 上面四種都是 Linux 統一設備模型所添加的高級接口,如果使用 sysfs 所提供的底層接口的話(huà),則還有下面兩個(gè),定義來(lái)自 <include/linux/sysfs.h> :(上面的總線(xiàn)/類(lèi)別/驅動(dòng)/設備四個(gè)接口都是以這里的__ATTR實(shí)現的) #define __ATTR(_name,_mode,_show,_store) { .attr = {.name = __stringify(_name), .mode = _mode }, .show = _show, .store = _store, } #define __ATTR_RO(_name) { .attr = { .name = __stringify(_name), .mode = 0444 }, .show = _name##_show, } | 上 面這些宏都是在注冊總線(xiàn)/類(lèi)別/驅動(dòng)/設備時(shí)作為缺省屬性而使用的,在實(shí)際應用中還有一種情況是根據條件動(dòng)態(tài)添加屬性,如 PCI 設備上的 resource{0,1,2,...} 屬性文件,因為一個(gè) PCI 設備上的可映射資源究竟有多少無(wú)法預知,也只能以條件判斷的方式動(dòng)態(tài)添加上。 int __must_check sysfs_create_file(struct kobject *kobj, const struct attribute *attr); int __must_check sysfs_create_bin_file(struct kobject *kobj, struct bin_attribute *attr); | 這兩個(gè)函數可以對一個(gè) kobject 動(dòng)態(tài)添加上文本屬性或二進(jìn)制屬性,這也是唯一可以添加二進(jìn)制屬性的方法。 二進(jìn)制屬性與普通文本屬性的區別在于: - 二進(jìn)制屬性
struct bin_attribute 中內嵌一個(gè) struct attribute 結構體對象,因此具有普通屬性的所有功能特征; - 二進(jìn)制屬性上多一個(gè) size 用來(lái)描述此二進(jìn)制文件的大小,而普通屬性文件的大小總是 4096, 準確地說(shuō),應該是一個(gè)內存頁(yè)的大小,因為從當前 sysfs 內核實(shí)現來(lái)說(shuō),它分配一個(gè)內存頁(yè)面來(lái)作為 (buf/count) 的緩沖區;
- 二進(jìn)制屬性比普通屬性多內存映射(mmap)接口的支持;
編程示例,對 LDD3 一書(shū)中的 lddbus 驅動(dòng)程序的 sysfs 改進(jìn) 首先,這個(gè)程序本身是針對當時(shí)作者寫(xiě)書(shū)的年代的內核(2.6.11)而編寫(xiě)的,在當前的 Fedora10 系統 (2.6.27.5-117.fc10.i686) 上甚至無(wú)法編譯編譯通過(guò);因此首先需要將它移植過(guò)來(lái)至少達到可運行狀態(tài); 附件的壓縮包中含有修改過(guò)的 lddbus, sculld 的源代碼和修改過(guò)程的四個(gè)patch:
小結 sysfs 給應用程序提供了統一訪(fǎng)問(wèn)設備的接口,但可以看到, sysfs 僅僅是提供了一個(gè)可以統一訪(fǎng)問(wèn)設備的框架,但究竟是否支持 sysfs 還需要各設備驅動(dòng)程序的編程支持;在 2.6 內核誕生 5年以來(lái)的發(fā)展中,很多子系統、設備驅動(dòng)程序逐漸轉向了 sysfs 作為與用戶(hù)空間友好的接口,但仍然也存在大量的代碼還在使用舊的 proc 或虛擬字符設備的 ioctl 方式;如果僅從最終用戶(hù)的角度來(lái)說(shuō), sysfs 與 proc 都是在提供相同或類(lèi)似的功能,對于舊的 proc 代碼,沒(méi)有絕對的必要去做 proc 至 sysfs 的升級;因此在可預見(jiàn)的將來(lái), sysfs 會(huì )與 proc, debugfs, configfs 等共存很長(cháng)一段時(shí)間。
下載 |