• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            小默

            【轉(zhuǎn)】使用 /sys 文件系統(tǒng)訪問 Linux 內(nèi)核

            sysfs 虛擬文件系統(tǒng)提供了一種比 proc 更為理想的訪問內(nèi)核數(shù)據(jù)的途徑

            級別: 中級

            程 任全 (crquan@gmail.com), Linux 內(nèi)核開發(fā)者、存儲開發(fā)工程師, UIT(創(chuàng)新科存儲技術(shù)有限公司)

            2009 年 1 月 08 日

            sysfs 是 Linux 內(nèi)核中設(shè)計(jì)較新的一種虛擬的基于內(nèi)存的文件系統(tǒng),它的作用與 proc 有些類似,但除了與 proc 相同的具有查看和設(shè)定內(nèi)核參數(shù)功能之外,還有為 Linux 統(tǒng)一設(shè)備模型作為管理之用。相比于 proc 文件系統(tǒng),使用 sysfs 導(dǎo)出內(nèi)核數(shù)據(jù)的方式更為統(tǒng)一,并且組織的方式更好,它的設(shè)計(jì)從 proc 中吸取了很多教訓(xùn)。本文就 sysfs 的掛載點(diǎn) /sys 目錄結(jié)構(gòu)、其與 Linux 統(tǒng)一設(shè)備模型的關(guān)系、常見屬性文件的用法等方面對 sysfs 作入門介紹,并且就內(nèi)核編程方面,以具體的例子來展示如何添加 sysfs 支持。
            sysfs 的歷史,其與 proc 的關(guān)系?
            sysfs 本身并不是一項(xiàng)很新的技術(shù),但筆者發(fā)現(xiàn),雖然 sysfs 從2003年誕生至今已有5年,但人們對 sysfs 依然缺乏了解;一個很重要的原因可能是缺乏文檔, Linux 內(nèi)核方面最重要的理論書籍“Linux 設(shè)備驅(qū)動第3版”和“理解 Linux 內(nèi)核第2版”都誕生于2003年前后,并且從那以后尚未有再版過,其它一些重要文章則多對 sysfs 與 proc 相提并論且舉例常常只有 proc,這導(dǎo)致了 sysfs 的很多重要概念至今仍鮮為人知,因此有必要對 sysfs 作更多介紹,這是寫作本文的初衷。

            sysfs 與 /sys

            sysfs 文件系統(tǒng)總是被掛載在 /sys 掛載點(diǎn)上。雖然在較早期的2.6內(nèi)核系統(tǒng)上并沒有規(guī)定 sysfs 的標(biāo)準(zhǔn)掛載位置,可以把 sysfs 掛載在任何位置,但較近的2.6內(nèi)核修正了這一規(guī)則,要求 sysfs 總是掛載在 /sys 目錄上;針對以前的 sysfs 掛載位置不固定或沒有標(biāo)準(zhǔn)被掛載,有些程序從 /proc/mounts 中解析出 sysfs 是否被掛載以及具體的掛載點(diǎn),這個步驟現(xiàn)在已經(jīng)不需要了。請參考附錄給出的 sysfs-rules.txt 文件鏈接。

            sysfs 與 proc

            sysfs 與 proc 相比有很多優(yōu)點(diǎn),最重要的莫過于設(shè)計(jì)上的清晰。一個 proc 虛擬文件可能有內(nèi)部格式,如 /proc/scsi/scsi,它是可讀可寫的,(其文件權(quán)限被錯誤地標(biāo)記為了 0444 !,這是內(nèi)核的一個BUG),并且讀寫格式不一樣,代表不同的操作,應(yīng)用程序中讀到了這個文件的內(nèi)容一般還需要進(jìn)行字符串解析,而在寫入時需要先用字符串格式化按指定的格式寫入字符串進(jìn)行操作;相比而言, sysfs 的設(shè)計(jì)原則是一個屬性文件只做一件事情, sysfs 屬性文件一般只有一個值,直接讀取或?qū)懭搿U麄€ /proc/scsi 目錄在2.6內(nèi)核中已被標(biāo)記為過時(LEGACY),它的功能已經(jīng)被相應(yīng)的 /sys 屬性文件所完全取代。新設(shè)計(jì)的內(nèi)核機(jī)制應(yīng)該盡量使用 sysfs 機(jī)制,而將 proc 保留給純凈的“進(jìn)程文件系統(tǒng)”。





            回頁首


            初識 /sys


            清單 1. 與 /sys 文件系統(tǒng)的一次交互(視內(nèi)核版本號和外接設(shè)備的不同,在您的系統(tǒng)上執(zhí)行這些命令的結(jié)果可能與此有所不同)
            $ 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 的內(nèi)核上,可以看到在 /sys 目錄下有 block, bus, class, dev, devices, firmware, fs, kernel, module, power 這些子目錄,本文將分別介紹這些目錄存在的含義。

            第二個 ls 命令展示了在一個 pci 設(shè)備目錄下的文件, "ls" 命令的 "-F" 命令為所列出的每個文件使用后綴來顯示文件的類型,后綴 "/" 表示列出的是目錄,后綴 "@" 表示列出的是符號鏈接文件。可以看到第二個目錄下包含有普通文件 (regular file) 和符號鏈接文件 (symbolic link file) ,本文也將以這個具體的設(shè)備為例說明其中每一個普通文件的用途。





            回頁首


            /sys 文件系統(tǒng)下的目錄結(jié)構(gòu)

            /sys 下的目錄結(jié)構(gòu)是經(jīng)過精心設(shè)計(jì)的:在 /sys/devices 下是所有設(shè)備的真實(shí)對象,包括如視頻卡和以太網(wǎng)卡等真實(shí)的設(shè)備,也包括 ACPI 等不那么顯而易見的真實(shí)設(shè)備、還有 tty, bonding 等純粹虛擬的設(shè)備;在其它目錄如 class, bus 等中則在分類的目錄中含有大量對 devices 中真實(shí)對象引用的符號鏈接文件; 清單1 中在 /sys 根目錄下頂層目錄的意義如下:


            表 1. /sys 下的目錄結(jié)構(gòu)
            /sys 下的子目錄所包含的內(nèi)容
            /sys/devices這是內(nèi)核對系統(tǒng)中所有設(shè)備的分層次表達(dá)模型,也是 /sys 文件系統(tǒng)管理設(shè)備的最重要的目錄結(jié)構(gòu),下文會對它的內(nèi)部結(jié)構(gòu)作進(jìn)一步分析;
            /sys/dev這個目錄下維護(hù)一個按字符設(shè)備和塊設(shè)備的主次號碼(major:minor)鏈接到真實(shí)的設(shè)備(/sys/devices下)的符號鏈接文件,它是在內(nèi)核 2.6.26 首次引入;
            /sys/bus這是內(nèi)核設(shè)備按總線類型分層放置的目錄結(jié)構(gòu), devices 中的所有設(shè)備都是連接于某種總線之下,在這里的每一種具體總線之下可以找到每一個具體設(shè)備的符號鏈接,它也是構(gòu)成 Linux 統(tǒng)一設(shè)備模型的一部分;
            /sys/class這是按照設(shè)備功能分類的設(shè)備模型,如系統(tǒng)所有輸入設(shè)備都會出現(xiàn)在 /sys/class/input 之下,而不論它們是以何種總線連接到系統(tǒng)。它也是構(gòu)成 Linux 統(tǒng)一設(shè)備模型的一部分;
            /sys/block這里是系統(tǒng)中當(dāng)前所有的塊設(shè)備所在,按照功能來說放置在 /sys/class 之下會更合適,但只是由于歷史遺留因素而一直存在于 /sys/block, 但從 2.6.22 開始就已標(biāo)記為過時,只有在打開了 CONFIG_SYSFS_DEPRECATED 配置下編譯才會有這個目錄的存在,并且在 2.6.26 內(nèi)核中已正式移到 /sys/class/block, 舊的接口 /sys/block 為了向后兼容保留存在,但其中的內(nèi)容已經(jīng)變?yōu)橹赶蛩鼈冊?/sys/devices/ 中真實(shí)設(shè)備的符號鏈接文件;
            /sys/firmware這里是系統(tǒng)加載固件機(jī)制的對用戶空間的接口,關(guān)于固件有專用于固件加載的一套API,在附錄 LDD3 一書中有關(guān)于內(nèi)核支持固件加載機(jī)制的更詳細(xì)的介紹;
            /sys/fs這里按照設(shè)計(jì)是用于描述系統(tǒng)中所有文件系統(tǒng),包括文件系統(tǒng)本身和按文件系統(tǒng)分類存放的已掛載點(diǎn),但目前只有 fuse,gfs2 等少數(shù)文件系統(tǒng)支持 sysfs 接口,一些傳統(tǒng)的虛擬文件系統(tǒng)(VFS)層次控制參數(shù)仍然在 sysctl (/proc/sys/fs) 接口中中;
            /sys/kernel這里是內(nèi)核所有可調(diào)整參數(shù)的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等幾項(xiàng)較新的設(shè)計(jì)在使用它,其它內(nèi)核可調(diào)整參數(shù)仍然位于 sysctl (/proc/sys/kernel) 接口中 ;
            /sys/module這里有系統(tǒng)中所有模塊的信息,不論這些模塊是以內(nèi)聯(lián)(inlined)方式編譯到內(nèi)核映像文件(vmlinuz)中還是編譯為外部模塊(ko文件),都可能會出現(xiàn)在 /sys/module 中:
            • 編譯為外部模塊(ko文件)在加載后會出現(xiàn)對應(yīng)的 /sys/module/<module_name>/, 并且在這個目錄下會出現(xiàn)一些屬性文件和屬性目錄來表示此外部模塊的一些信息,如版本號、加載狀態(tài)、所提供的驅(qū)動程序等;
            • 編譯為內(nèi)聯(lián)方式的模塊則只在當(dāng)它有非0屬性的模塊參數(shù)時會出現(xiàn)對應(yīng)的 /sys/module/<module_name>, 這些模塊的可用參數(shù)會出現(xiàn)在/sys/modules/<modname>/parameters/<param_name> 中,
              • 如 /sys/module/printk/parameters/time 這個可讀寫參數(shù)控制著內(nèi)聯(lián)模塊 printk 在打印內(nèi)核消息時是否加上時間前綴;
              • 所有內(nèi)聯(lián)模塊的參數(shù)也可以由 "<module_name>.<param_name>=<value>" 的形式寫在內(nèi)核啟動參數(shù)上,如啟動內(nèi)核時加上參數(shù) "printk.time=1" 與 向 "/sys/module/printk/parameters/time" 寫入1的效果相同;
            • 沒有非0屬性參數(shù)的內(nèi)聯(lián)模塊不會出現(xiàn)于此。
            /sys/power這里是系統(tǒng)中電源選項(xiàng),這個目錄下有幾個屬性文件可以用于控制整個機(jī)器的電源狀態(tài),如可以向其中寫入控制命令讓機(jī)器關(guān)機(jī)、重啟等。
            /sys/slab (對應(yīng) 2.6.23 內(nèi)核,在 2.6.24 以后移至 /sys/kernel/slab)從2.6.23 開始可以選擇 SLAB 內(nèi)存分配器的實(shí)現(xiàn),并且新的 SLUB(Unqueued Slab Allocator)被設(shè)置為缺省值;如果編譯了此選項(xiàng),在 /sys 下就會出現(xiàn) /sys/slab ,里面有每一個 kmem_cache 結(jié)構(gòu)體的可調(diào)整參數(shù)。對應(yīng)于舊的 SLAB 內(nèi)存分配器下的 /proc/slabinfo 動態(tài)調(diào)整接口,新式的 /sys/kernel/slab/<slab_name> 接口中的各項(xiàng)信息和可調(diào)整項(xiàng)顯得更為清晰。

            接下來對 /sys/devices/ 下的目錄結(jié)構(gòu)作進(jìn)一步探討:


            清單 2. 查看 /sys/devices/ 的目錄結(jié)構(gòu)
            $ ls -F /sys/devices/
            isa/  LNXSYSTM:00/  pci0000:00/  platform/  pnp0/  pnp1/  system/  virtual/

            可以看到,在 /sys/devices/ 目錄下是按照設(shè)備的基本總線類型分類的目錄,再進(jìn)入進(jìn)去查看其中的 PCI 類型的設(shè)備:


            清單 3. 查看 /sys/devices/pci0000:00/ 的目錄結(jié)構(gòu)
            $ 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 總線接入的設(shè)備號分類存放的目錄,再查看其中一個,


            清單 4. 查看 /sys/devices/pci0000:00/ 的目錄結(jié)構(gòu)
            $ 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

            可以看到,其中有一個目錄 0000:01:00.0/, 其它都是屬性文件和屬性組,而如果對 0000:01:00.0/ 子目錄中進(jìn)行再列表查看則會得到 清單1 的目錄結(jié)構(gòu)。

            繼續(xù)以上過程可以了解整個目錄樹的結(jié)構(gòu),這里把它整理成 圖 1. sysfs 目錄層次圖


            圖 1. sysfs 目錄層次圖
            sysfs目錄層次圖 

            其中涉及到 ksets, kobjects, attrs 等很多術(shù)語,這就不得不提到 Linux 統(tǒng)一設(shè)備模型。





            回頁首


            Linux 統(tǒng)一設(shè)備模型

            在 Linux 2.5 內(nèi)核的開發(fā)過程中,人們設(shè)計(jì)了一套新的設(shè)備模型,目的是為了對計(jì)算機(jī)上的所有設(shè)備進(jìn)行統(tǒng)一地表示和操作,包括設(shè)備本身和設(shè)備之間的連接關(guān)系。這個模型是在分析了 PCI 和 USB 的總線驅(qū)動過程中得到的,這兩個總線類型能代表當(dāng)前系統(tǒng)中的大多數(shù)設(shè)備類型,它們都有完善的熱挺拔機(jī)制和電源管理的支持,也都有級連機(jī)制的支持,以橋接的 PCI/USB 總線控制器的方式可以支持更多的 PCI/USB 設(shè)備。為了給所有設(shè)備添加統(tǒng)一的電源管理的支持,而不是讓每個設(shè)備中去獨(dú)立實(shí)現(xiàn)電源管理的支持,人們考慮的是如何盡可能地重用代碼;而且在有層次模型的 PCI/USB 總線中,必須以合理形式展示出這個層次關(guān)系,這也是電源管理等所要求的必須有層次結(jié)構(gòu)。

            如在一個典型的 PC 系統(tǒng)中,中央處理器(CPU)能直接控制的是 PCI 總線設(shè)備,而 USB 總線設(shè)備是以一個 PCI 設(shè)備(PCI-USB橋)的形式接入在 PCI 總線設(shè)備上,外部 USB 設(shè)備再接入在 USB 總線設(shè)備上;當(dāng)計(jì)算機(jī)執(zhí)行掛起(suspend)操作時, Linux 內(nèi)核應(yīng)該以 “外部USB設(shè)備->USB總線設(shè)備->PCI總線設(shè)備” 的順序通知每一個設(shè)備將電源掛起;執(zhí)行恢復(fù)(resume)時則以相反的順序通知;反之如果不按此順序則將有設(shè)備得不到正確的電源狀態(tài)變遷的通知,將無法正常工作。

            sysfs 是在這個 Linux 統(tǒng)一設(shè)備模型的開發(fā)過程中的一項(xiàng)副產(chǎn)品(見 參考資料 中 Greg K. Hartman 寫作的 LinuxJournal 文章)。為了將這些有層次結(jié)構(gòu)的設(shè)備以用戶程序可見的方式表達(dá)出來,人們很自然想到了利用文件系統(tǒng)的目錄樹結(jié)構(gòu)(這是以 UNIX 方式思考問題的基礎(chǔ),一切都是文件!)在這個模型中,有幾種基本類型,它們的對應(yīng)關(guān)系見 表 2. Linux 統(tǒng)一設(shè)備模型的基本結(jié)構(gòu) :


            表 2. Linux 統(tǒng)一設(shè)備模型的基本結(jié)構(gòu)
            類型所包含的內(nèi)容對應(yīng)內(nèi)核數(shù)據(jù)結(jié)構(gòu)對應(yīng)/sys項(xiàng)
            設(shè)備(Devices)設(shè)備是此模型中最基本的類型,以設(shè)備本身的連接按層次組織struct device/sys/devices/*/*/.../
            設(shè)備驅(qū)動(Device Drivers)在一個系統(tǒng)中安裝多個相同設(shè)備,只需要一份驅(qū)動程序的支持struct device_driver/sys/bus/pci/drivers/*/
            總線類型(Bus Types)在整個總線級別對此總線上連接的所有設(shè)備進(jìn)行管理struct bus_type/sys/bus/*/
            設(shè)備類別(Device Classes)這是按照功能進(jìn)行分類組織的設(shè)備層次樹;如 USB 接口和 PS/2 接口的鼠標(biāo)都是輸入設(shè)備,都會出現(xiàn)在 /sys/class/input/ 下struct class/sys/class/*/

            從內(nèi)核在實(shí)現(xiàn)它們時所使用的數(shù)據(jù)結(jié)構(gòu)來說, Linux 統(tǒng)一設(shè)備模型又是以兩種基本數(shù)據(jù)結(jié)構(gòu)進(jìn)行樹型和鏈表型結(jié)構(gòu)組織的:

            • kobject: 在 Linux 設(shè)備模型中最基本的對象,它的功能是提供引用計(jì)數(shù)和維持父子(parent)結(jié)構(gòu)、平級(sibling)目錄關(guān)系,上面的 device, device_driver 等各對象都是以 kobject 基礎(chǔ)功能之上實(shí)現(xiàn)的;
              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 內(nèi)含一個 atomic_t 類型用于引用計(jì)數(shù), parent 是單個指向父節(jié)點(diǎn)的指針, entry 用于父 kset 以鏈表頭結(jié)構(gòu)將 kobject 結(jié)構(gòu)維護(hù)成雙向鏈表;
            • kset: 它用來對同類型對象提供一個包裝集合,在內(nèi)核數(shù)據(jù)結(jié)構(gòu)上它也是由內(nèi)嵌一個 kboject 實(shí)現(xiàn),因而它同時也是一個 kobject (面向?qū)ο?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 維護(hù)成雙向鏈表;

            涉及到文件系統(tǒng)實(shí)現(xiàn)來說, sysfs 是一種基于 ramfs 實(shí)現(xiàn)的內(nèi)存文件系統(tǒng),與其它同樣以 ramfs 實(shí)現(xiàn)的內(nèi)存文件系統(tǒng)(configfs,debugfs,tmpfs,...)類似, sysfs 也是直接以 VFS 中的 struct inode 和 struct dentry 等 VFS 層次的結(jié)構(gòu)體直接實(shí)現(xiàn)文件系統(tǒng)中的各種對象;同時在每個文件系統(tǒng)的私有數(shù)據(jù) (如 dentry->d_fsdata 等位置) 上,使用了稱為 struct sysfs_dirent 的結(jié)構(gòu)用于表示 /sys 中的每一個目錄項(xiàng)。

            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 來統(tǒng)一設(shè)備模型中的 kset/kobject/attr/attr_group.

            具體在數(shù)據(jù)結(jié)構(gòu)成員上, sysfs_dirent 上有一個 union 共用體包含四種不同的結(jié)構(gòu),分別是目錄、符號鏈接文件、屬性文件、二進(jìn)制屬性文件;其中目錄類型可以對應(yīng) kobject,在相應(yīng)的 s_dir 中也有對 kobject 的指針,因此在內(nèi)核數(shù)據(jù)結(jié)構(gòu), kobject 與 sysfs_dirent 是互相引用的;

            有了這些概念,再來回頭看 圖 1. sysfs 目錄層次圖 所表達(dá)的 /sys 目錄結(jié)構(gòu)就是非常清晰明了:

            • 在 /sys 根目錄之下的都是 kset,它們組織了 /sys 的頂層目錄視圖;
            • 在部分 kset 下有二級或更深層次的 kset;
            • 每個 kset 目錄下再包含著一個或多個 kobject,這表示一個集合所包含的 kobject 結(jié)構(gòu)體;
            • 在 kobject 下有屬性(attrs)文件和屬性組(attr_group),屬性組就是組織屬性的一個目錄,它們一起向用戶層提供了表示和操作這個 kobject 的屬性特征的接口;
            • 在 kobject 下還有一些符號鏈接文件,指向其它的 kobject,這些符號鏈接文件用于組織上面所說的 device, driver, bus_type, class, module 之間的關(guān)系;
            • 不同類型如設(shè)備類型的、設(shè)備驅(qū)動類型的 kobject 都有不同的屬性,不同驅(qū)動程序支持的 sysfs 接口也有不同的屬性文件;而相同類型的設(shè)備上有很多相同的屬性文件;

            注意,此表內(nèi)容是按照最新開發(fā)中的 2.6.28 內(nèi)核的更新組織的,在附錄資源如 LDD3 等位置中有提到 sysfs 中曾有一種管理對象稱為 subsys (子系統(tǒng)對象),在最新的內(nèi)核中經(jīng)過重構(gòu)認(rèn)為它是不需要的,它的功能完全可以由 kset 代替,也就是說 sysfs 中只需要一種管理結(jié)構(gòu)是 kset,一種代表具體對象的結(jié)構(gòu)是 kobject,在 kobject 下再用屬性文件表示這個對象所具有的屬性;





            回頁首


            常見 sysfs 屬性的功能

            使用 sysfs 的關(guān)鍵就是掌握這些 sysfs 屬性的用法,下面以一些常見的 sysfs 屬性來展示它的用法;

            使用設(shè)備(PCI)的 sysfs 屬性文件

            以一份桌面系統(tǒng)上的視頻卡為例,列舉它對應(yīng)的 kobject 上的屬性文件的對應(yīng)用途;

            一般來說,在 Linux 桌面上都有視頻卡以支持 Xorg 軟件包作為 XWindow 服務(wù)器來運(yùn)行,因此先找到 Xorg 的進(jìn)程號,查看這個進(jìn)程所使用的所有文件(注意查看這個進(jìn)程屬性需要 root 用戶權(quán)限);

            # 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ù)器是以內(nèi)存映射 (mem) 的形式打開了 "/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" ,同時以文件讀寫形式 (7u,9u) 打開了 "/sys/devices/pci0000:00/0000:00:00.0/config" 和 "/sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/config"

            事實(shí)上, PCI 設(shè)備對應(yīng)的 kobject 目錄下的 config 正是代表PCI設(shè)備的“配置空間”,對于普通 PCI (非PCI-E)設(shè)備而言,其配置空間大小一般是 256字節(jié),這個空間可以使用十六進(jìn)制工具 dump 出來,如下。(有關(guān) PCI 設(shè)備本身的三種地址空間,請參考附錄 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 

            這個空間正好是 256字節(jié)大小,熟悉 PCI 的人們還可以知道,從 PCI 配置空間可以讀到有關(guān)此 PCI 設(shè)備的很多有用信息,如廠商代碼,設(shè)備代碼,IRQ 號碼等;前四個字節(jié) 0x39 0x10 0x30 0x63 就是按小端(little endian)存放的2個短整數(shù),因此其 PCI 廠商號碼和 PCI 設(shè)備號碼分別是 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 設(shè)備上除了有 config 是配置空間對用戶的接口以外,還有 resource{0,1,2,...} 是資源空間,對應(yīng)著 PCI 設(shè)備的可映射內(nèi)存空間;此外 PCI 設(shè)備還提供了很多接口,全部列表如下:

            # ls -lU /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0/
            總計(jì) 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 

            可以看到很多其它屬性文件,這些屬性文件的權(quán)限位也都是正確的,有 w 權(quán)限位的才是可以寫入。其中大小為 4096字節(jié)的屬性一般是純文本描述的屬性,可以直接 cat 讀出和用 echo 字符串的方法寫入;其它非 4096字節(jié)大小的一般是二進(jìn)制屬性,類似于上面的 config 屬性文件;關(guān)于純文本屬性和二進(jìn)制屬性,在下文 編程實(shí)踐:添加sysfs支持 一節(jié)會進(jìn)一步說明。

            • 從 vendor, device, subsystem_vendor, subsystem_device, class, resource 這些只讀屬性上分別可以讀到此 PCI 設(shè)備的廠商號、設(shè)備號、子系統(tǒng)廠商號、子系統(tǒng)設(shè)備號、PCI類別、資源表等,這些都是相應(yīng) PCI 設(shè)備的屬性,其實(shí)就是直接從 config 二進(jìn)制文件讀出來,按照配置空間的格式讀出這些號碼;
            • 使用 enable 這個可寫屬性可以禁用或啟用這個 PCI 設(shè)備,設(shè)備的過程很直觀,寫入1代表啟用,寫入0則代表禁用;
            • subsystem 和 driver 符號鏈接文件分別指向?qū)?yīng)的 sysfs 位置;(這里缺少 driver 符號鏈接說明這個設(shè)備當(dāng)前未使用內(nèi)核級的驅(qū)動程序)
            • resource0, resource0_wc, resource1, resource2 等是從"PCI 配置空間"解析出來的資源定義段落分別生成的,它們是 PCI 總線驅(qū)動在 PCI 設(shè)備初始化階段加上去的,都是二進(jìn)制屬性,但沒有實(shí)現(xiàn)讀寫接口,只支持 mmap 內(nèi)存映射接口,嘗試進(jìn)行讀寫會提示 IO 錯誤,其中 _wc 后綴表示 "合并式寫入(write combined)" ,它們用于作應(yīng)用程序的內(nèi)存映射,就可以訪問對應(yīng)的 PCI 設(shè)備上相應(yīng)的內(nèi)存資源段落;

            有了 PCI 核心對 sysfs 的完善支持,每個設(shè)備甚至不用單獨(dú)的驅(qū)動程序,如這里的 "0000:01:00.0" 不需要一個內(nèi)核級的驅(qū)動程序,有了 PCI 核心對該設(shè)備的配置空間發(fā)現(xiàn)機(jī)制,可以自動發(fā)現(xiàn)它的各個不同段落的資源屬性,在 Xorg 應(yīng)用程序中可以直接以 "/usr/lib/xorg/modules/drivers/sis_drv.so" 這個用戶空間的驅(qū)動程序?qū)ζ溥M(jìn)行映射,就可以直接操作此視頻卡了;

            有了這一個 PCI 設(shè)備的示例可以知道,有了一個 PCI 設(shè)備的 /sys/devices/ 設(shè)備對象,去訪問它的各項(xiàng)屬性和設(shè)置屬性都非常簡單。

            使用 uevent

            在 sysfs 下的很多 kobject 下都有 uevent 屬性,它主要用于內(nèi)核與 udev (自動設(shè)備發(fā)現(xiàn)程序)之間的一個通信接口;從 udev 本身與內(nèi)核的通信接口 netlink 協(xié)議套接字來說,它并不需要知道設(shè)備的 uevent 屬性文件,但多了 uevent 這樣一個接口,可用于 udevmonitor 通過內(nèi)核向 udevd (udev 后臺程序)發(fā)送消息,也可用于檢查設(shè)備本身所支持的 netlink 消息上的環(huán)境變量,這個特性一般用于開發(fā)人員調(diào)試 udev 規(guī)則文件, udevtrigger 這個調(diào)試工具本身就是以寫各設(shè)備的 uevent 屬性文件實(shí)現(xiàn)的。

            這些 uevent 屬性文件一般都是可寫的,其中 /sys/devices/ 樹下的很多 uevent 屬性在較新內(nèi)核下還支持可讀:

            # 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
            [...]
             

            上面截取的最后一個是 SCSI 硬盤控制器設(shè)備的 uevent 屬性文件,這些 /devices/ 屬性文件都支持寫入,當(dāng)前支持寫入的參數(shù)有 "add","remove","change","move","online","offline"。如,寫入 "add",這樣可以向 udevd 發(fā)送一條 netlink 消息,讓它再重新一遍相關(guān)的 udev 規(guī)則文件;這個功能對開發(fā)人員調(diào)試 udev 規(guī)則文件很有用。

            # echo add > /sys/devices/pci0000:00/0000:00:02.5/uevent
             

            使用驅(qū)動(PCI)的 sysfs 屬性文件, bind, unbind 和 new_id

            在設(shè)備驅(qū)動 /sys/bus/*/driver/... 下可以看到很多驅(qū)動都有 bind, unbind, new_id 這三個屬性,

            # find /sys/bus/*/drivers/ -name bind -ls
            ... 

            每一個設(shè)備驅(qū)動程序在程序內(nèi)以某種方式注明了可用于哪些硬件,如所有的 PCI 驅(qū)動都使用 MODULE_DEVICE_TABLE 聲明了所能驅(qū)動的 PCI 硬件的 PCI 設(shè)備號。但驅(qū)動程序不能預(yù)知未來,未來生產(chǎn)的新的硬件有可能兼容現(xiàn)有硬件的工作方式,就還可以使用現(xiàn)有硬件驅(qū)動程序來工作。在 bind 和 unbind 發(fā)明以前,這種情況除了修改 PCI 設(shè)備驅(qū)動程序的 DEVICE_TABLE 段落,重新編譯驅(qū)動程序,以外別無他法,在 2.6 內(nèi)核上添加了 bind 和 unbind 之后可以在不重新編譯的情況下對設(shè)備和驅(qū)動之間進(jìn)行手工方式地綁定。

            而且對于有些硬件設(shè)備可以有多份驅(qū)動可用,但任何具體時刻只能有一個驅(qū)動程序來驅(qū)動這個硬件,這時可以使用 bind/unbind 來強(qiáng)制使用和不使用哪一個驅(qū)動程序;(注意關(guān)于多種驅(qū)動程序的選擇,更好的管理方法是使用 modprobe.conf 配置文件,需要重啟才生效,而 bind/unbind 提供的是一種臨時的無需重啟立即生效的途徑;)

            使用它們可以強(qiáng)制綁定某個設(shè)備使用或強(qiáng)制不使用某個驅(qū)動程序,操作方法就是通過 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 

            這個結(jié)果中特別提到了 8139too 這份驅(qū)動程序的這三個屬性文件,

            # 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: 沒有那個設(shè)備
            # 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 

            這一段操作過程演示了如何對 PCI 設(shè)備 "0000:00:0e.0" 強(qiáng)制取消綁定 "8139too" 驅(qū)動和強(qiáng)制綁定 "8139too" 驅(qū)動:

            • 對 unbind 屬性寫入總線號碼(bus_id)即是強(qiáng)制取消綁定;
            • 對 bind 屬性寫入總線號碼(bus_id)即是強(qiáng)制綁定;

            注意,它要求的寫入的是總線號碼,對應(yīng)于PCI設(shè)備的總線號碼是按照 "domain(4位):bus(2位):slot(2位):function號(不限)" 的方式組織,是可以從其設(shè)備 kobject 節(jié)點(diǎn)上找到,而其它類型的總線有各自不同的規(guī)則;

            請?zhí)貏e注意: 在這一個例子中, "echo 0000:00:0e.0 > /sys/bus/pci/drivers/8139too/unbind" 這第一個寫入命令以 "No such device" 為錯誤退出,而后續(xù)的 "echo -n" 命令則可以成功。這是因?yàn)閮?nèi)核在對總線號碼進(jìn)行匹配時過于嚴(yán)格了,通常的 "echo" 命令寫入一個字符串會以一個換行符結(jié)束輸出,內(nèi)核所接收到的是帶有這個換行符的 bus_id 字符串,將它與內(nèi)核數(shù)據(jù)結(jié)構(gòu)中的真正的 bus_id 字符串相比較,當(dāng)然不能找到;所幸的是,這個問題在最新的 2.6.28 開發(fā)中的內(nèi)核上已已經(jīng)解決,它將這個比較函數(shù)改為一個特殊實(shí)現(xiàn)的字符串比較,自動忽略結(jié)尾處的換行符,在 2.6.28-rc6 內(nèi)核上測試,不帶"-n"參數(shù)的 echo 命令已經(jīng)可以寫入成功。

            而 new_id 屬性文件也可以以另一種途徑解決新的設(shè)備號問題:它是一個只寫的驅(qū)動屬性,可用于向其中寫新的設(shè)備號。它支持寫入 2至7個十六進(jìn)制整形參數(shù),分別代表 vendor, device, subvendor, subdevice, class, class_mask, driver_data 最少為 2個是因?yàn)橐粋€ PCI設(shè)備主要以廠商號(vendor)和設(shè)備號(device)所唯一標(biāo)定,其它 5個參數(shù)如果不輸入則缺省值為 PCI_ANY_ID(0xffff)。

              5441    0 --w-------   1 root     root         4096 12月 14 18:15 \
            /sys/bus/pci/drivers/8139too/new_id 

            從 8139too 驅(qū)動上可以看到它當(dāng)前所靜態(tài)支持的設(shè)備號碼列表,其中包括當(dāng)前系統(tǒng)中的設(shè)備 10ec:8139, 假設(shè)未來有一款 8140 設(shè)備也滿足 8139 設(shè)備的硬件通訊協(xié)議,于是可以使用 8139too 驅(qū)動程序來驅(qū)動它,操作如下

            # echo '10ec 8140' > /sys/bus/pci/drivers/8139too/new_id 

            這在不更新驅(qū)動程序的情況下調(diào)試設(shè)備很有用處。

            使用 scsi_host 的 scan 屬性

            在具有使用 SCSI 總線連接的主機(jī)上,與 PCI類似的是也采用四個號碼作為一組來描述一個設(shè)備,其中位于最頂層的是 scsi_host。

            我們從設(shè)備類別 /class/為起點(diǎn)來探索:

            # ls -lU /sys/class/scsi_host
            總計(jì) 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 內(nèi)核的最新變化,在 /sys/class/ 下的都改為符號鏈接,真實(shí)的 kobject 都存在于 /sys/devices/ 中;我們這里探索其中的 host0 這個 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
            總計(jì) 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

            對這些屬性文件解釋如下:

            • 有四個 SCSI 特有的可寫參數(shù): scan,state,supported_mode,active_mode;可以向其中寫入不同的參數(shù)來控制此 SCSI 控制器的各種狀態(tài);
            • 其它一些可讀屬性用于讀取這個 SCSI 控制器的一些當(dāng)前值;

            其中的 scan 屬性文件在調(diào)試一些 SCSI 硬件驅(qū)動時很有用,它是只寫的,可以寫入三個至四個以空格分開的整數(shù),用于分別指定對應(yīng)的 host, channel, id, lun 進(jìn)行重新搜索。且這個 scan 屬性支持以"-"作為通配符,如以下命令可以執(zhí)行讓整個 scsi_host 進(jìn)行重新搜索,這個功能用于調(diào)試某些對熱挺拔實(shí)現(xiàn)不完善的 SCSI 驅(qū)動程序很有用:

            # echo '- - -' >/sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0/scan

            內(nèi)核模塊中的 sysfs 屬性文件

            以一個 8139too 模塊為例解釋在這個 kboject 下每一個屬性的用途;

            # 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 上可以了解到這個模塊所聲明的版本號,源碼版本號, refcnt 是模塊引用計(jì)數(shù), sections 屬性組中有一些模塊加載至內(nèi)存的相應(yīng)節(jié)信息, drivers/ 目錄中是對所提供的驅(qū)動的鏈接;

            因?yàn)槟K是內(nèi)核驅(qū)動編程的最佳選擇,而一個模塊有可能提供多個驅(qū)動程序,因而在未知一個設(shè)備在用哪一個驅(qū)動的情況下可以先從 /sys/module/ 查找相應(yīng)模塊的情況,再從 drivers/ 發(fā)現(xiàn)出真正的驅(qū)動程序。或者也可以完全反過來利用這些信息,先用 lspci/lshw 等工具找到 /sys/devices/ 下的設(shè)備節(jié)點(diǎn),再從其設(shè)備的 driver 鏈接找到 /sys/bus/*/drivers/ 下的 device_driver, 再從 device_driver 下的 module 鏈接找到 /sys/module/*/,這樣就可以得到已加載模塊中空間是哪一個模塊在給一個設(shè)備提供驅(qū)動程序。

            更多 sysfs 屬性文件

            以上所舉的例子僅僅是一些常見的 sysfs 屬性用法,實(shí)際的系統(tǒng)中還常常有很多其它的從未見過的 sysfs 屬性,因此只有舉例是不夠的,即使維護(hù)了一份 sysfs 屬性用法參考大全也不夠,未來的內(nèi)核版本還會出現(xiàn)新的 sysfs 屬性,因此還必須了解 Linux 內(nèi)核代碼以找到實(shí)現(xiàn)這些屬性的代碼位置,以學(xué)會在沒有相應(yīng)屬性文檔的情況從內(nèi)核源代碼來分析其 sysfs 屬性功能。





            回頁首


            Sysfs 源碼分析和編程實(shí)踐

            從源代碼中理解 sysfs 屬性的用途

            更多的 sysfs 屬性的功能只能靠閱讀源代碼來理解。還是以上文提到的 scsi_host 的 scan 屬性來理解,這個功能沒有任何文檔上有描述,因此只能去讀源代碼。

            在內(nèi)核中, sysfs 屬性一般是由 __ATTR 系列的宏來聲明的,如對設(shè)備的使用 DEVICE_ATTR ,對總線使用 BUS_ATTR ,對驅(qū)動使用 DRIVER_ATTR ,對類別(class)使用 CLASS_ATTR, 這四個高級的宏來自于 <include/linux/device.h>, 都是以更低層的來自 <include/linux/sysfs.h> 中的 __ATTR/__ATRR_RO 宏實(shí)現(xiàn); 因此我們在內(nèi)核源碼樹中相應(yīng)位置 drivers/scsi/ 找到這幾個宏的使用情況,可以得到在 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 宏聲明有四個參數(shù),分別是名稱、權(quán)限位、讀函數(shù)、寫函數(shù)。這里對應(yīng)的,名稱是 scan, 權(quán)限是只有屬主可寫(S_IWUSR)、沒有讀函數(shù)、只有寫函數(shù)。因此讀寫功能與權(quán)限位是對應(yīng)的,因?yàn)?DEVICE_ATTR 把權(quán)限位聲明與真正的讀寫是否實(shí)現(xiàn)放在了一起,減少了出現(xiàn)不一致的可能。(上文提到 /proc/scsi/scsi 接口的權(quán)限位聲明與其功能不對應(yīng),這與注冊 proc 接口的函數(shù)設(shè)計(jì)中的不一致是有關(guān)系的,權(quán)限位聲明與功能實(shí)現(xiàn)不在代碼中同一個位置,因此易出錯。雖然修復(fù) /proc/scsi/scsi 的權(quán)限位錯誤很容易,但內(nèi)核團(tuán)隊(duì)中多年來一直沒有人發(fā)現(xiàn)或未有人去修正這個 BUG,應(yīng)該是與 /proc/scsi/ 接口的過時有關(guān),過時的功能會在未來某個內(nèi)核版本中去除。)

            上面的 scan 屬性寫入功能是在 store_scan 函數(shù)中實(shí)現(xiàn)的,這個接口的四個參數(shù)中, buf/count 代表用戶寫入過來的字符串,它把 buf 進(jìn)一步傳給了 scsi_scan 函數(shù);如果進(jìn)一步分析 scsi_scan 函數(shù)實(shí)現(xiàn)可以知道,它期望從 buf 中接受三個或四個整型值(也接受"-"作為通配符),分別代表 host, channel, id 三個值,(第四個整數(shù)在早期內(nèi)核中曾代表 lun 號碼,但在較新內(nèi)核中第四個數(shù)字被忽略,僅作為向后兼容保留接受四個整數(shù)),然后對具體的 (host, channel, id) 進(jìn)行重新掃描以發(fā)現(xiàn)這個 SCSI 控制器上的設(shè)備變動。

            添加 sysfs 支持

            如果你正在開發(fā)的設(shè)備驅(qū)動程序中需要與用戶層的接口,一般可選的方法有:

            1. 注冊虛擬的字符設(shè)備文件,以這個虛擬設(shè)備上的 read/write/ioctl 等接口與用戶交互;但 read/write 一般只能做一件事情, ioctl 可以根據(jù) cmd 參數(shù)做多個功能,但其缺點(diǎn)是很明顯的: ioctl 接口無法直接在 Shell 腳本中使用,為了使用 ioctl 的功能,還必須編寫配套的 C語言的虛擬設(shè)備操作程序, ioctl 的二進(jìn)制數(shù)據(jù)接口也是造成大小端問題 (big endian與little endian)、32位/64位不可移植問題的根源;
            2. 注冊 proc 接口,接受用戶的 read/write/ioctl 操作;同樣的,一個 proc 項(xiàng)通常使用其 read/write/ioctl 接口,它所存在的問題與上面的虛擬字符設(shè)備的的問題相似;
            3. 注冊 sysfs 屬性;

            最重要的是,添加虛擬字符設(shè)備支持和注冊 proc 接口支持這兩者所需要增加的代碼量都并不少,最好的方法還是使用 sysfs 屬性支持,一切在用戶層是可見的透明,且增加的代碼量是最少的,可維護(hù)性也最好;方法就是使用 <include/linux/device.h> 頭文件提供的這四個宏,分別應(yīng)用于總線/類別/驅(qū)動/設(shè)備四種內(nèi)核數(shù)據(jù)結(jié)構(gòu)對象上:

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

            總線(BUS)和類別(CLASS)屬性一般用于新設(shè)計(jì)的總線和新設(shè)計(jì)的類別,這兩者一般是不用的;因?yàn)槟愕脑O(shè)備一般是以PCI等成熟的常規(guī)方式連接到主機(jī),而不會去新發(fā)明一種類型;使用驅(qū)動屬性和設(shè)備屬性的區(qū)別就在于:看你的 sysfs 屬性設(shè)計(jì)是針對整個驅(qū)動有效的還是針對這份驅(qū)動所可能支持的每個設(shè)備分別有效。

            從頭文件中還可以找到 show/store 函數(shù)的原型,注意到它和虛擬字符設(shè)備或 proc 項(xiàng)的 read/write 的作用很類似,但有一點(diǎn)不同是 show/store 函數(shù)上的 buf/count 參數(shù)是在 sysfs 層已作了用戶區(qū)/內(nèi)核區(qū)的內(nèi)存復(fù)制,虛擬字符設(shè)備上常見的 __user 屬性在這里并不需要,因而也不需要多一次 copy_from_user/copy_to_user, 在 show/store 函數(shù)參數(shù)上的 buf/count 參數(shù)已經(jīng)是內(nèi)核區(qū)的地址,可以直接操作。

            上面四種都是 Linux 統(tǒng)一設(shè)備模型所添加的高級接口,如果使用 sysfs 所提供的底層接口的話,則還有下面兩個,定義來自 <include/linux/sysfs.h> :(上面的總線/類別/驅(qū)動/設(shè)備四個接口都是以這里的__ATTR實(shí)現(xiàn)的)

            #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,                                 \
            }

            上面這些宏都是在注冊總線/類別/驅(qū)動/設(shè)備時作為缺省屬性而使用的,在實(shí)際應(yīng)用中還有一種情況是根據(jù)條件動態(tài)添加屬性,如 PCI 設(shè)備上的 resource{0,1,2,...} 屬性文件,因?yàn)橐粋€ PCI 設(shè)備上的可映射資源究竟有多少無法預(yù)知,也只能以條件判斷的方式動態(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); 

            這兩個函數(shù)可以對一個 kobject 動態(tài)添加上文本屬性或二進(jìn)制屬性,這也是唯一可以添加二進(jìn)制屬性的方法。

            二進(jìn)制屬性與普通文本屬性的區(qū)別在于:

            • 二進(jìn)制屬性 struct bin_attribute 中內(nèi)嵌一個 struct attribute 結(jié)構(gòu)體對象,因此具有普通屬性的所有功能特征;
            • 二進(jìn)制屬性上多一個 size 用來描述此二進(jìn)制文件的大小,而普通屬性文件的大小總是 4096, 準(zhǔn)確地說,應(yīng)該是一個內(nèi)存頁的大小,因?yàn)閺漠?dāng)前 sysfs 內(nèi)核實(shí)現(xiàn)來說,它分配一個內(nèi)存頁面來作為 (buf/count) 的緩沖區(qū);
            • 二進(jìn)制屬性比普通屬性多內(nèi)存映射(mmap)接口的支持;

            編程示例,對 LDD3 一書中的 lddbus 驅(qū)動程序的 sysfs 改進(jìn)

            首先,這個程序本身是針對當(dāng)時作者寫書的年代的內(nèi)核(2.6.11)而編寫的,在當(dāng)前的 Fedora10 系統(tǒng) (2.6.27.5-117.fc10.i686) 上甚至無法編譯編譯通過;因此首先需要將它移植過來至少達(dá)到可運(yùn)行狀態(tài);

            附件的壓縮包中含有修改過的 lddbus, sculld 的源代碼和修改過程的四個patch:

            • 第一個 0001-ldd3-examples-build-on-fedora-10-2.6.27.5-117.fc10.i.patch 是將 lddbus 和 sculld 移植到 Fedora10 內(nèi)核上可運(yùn)行,這其中主要是一此內(nèi)核 API 的變化;
            • 第二個 0002-port-dmem-proc-entry-to-use-sysfs-entry.patch 演示了怎樣將原有的 proc 接口改進(jìn)成為 sysfs 屬性接口的,從這個 patch 中可以看到刪除的代碼多而新增加的代碼少,這說明對于相同的功能,使用 sysfs 編程接口的代碼量更少,而且 sysfs 代碼看起來也比 proc 更為整潔:打印每個設(shè)備的調(diào)試信息可以做成每個設(shè)備上分別有自己的接口,而不是統(tǒng)一的一個 proc 接口;設(shè)備屬性文件最終出現(xiàn)的位置如 "/sys/devices/ldd0/sculld0/dmem";
              static ssize_t sculld_show_dmem(struct device *ddev,
              		struct device_attribute *attr, char *buf)
              {
              	/* 其中打印每個設(shè)備調(diào)試信息的代碼復(fù)制自原proc接口 */
              }
              static DEVICE_ATTR(dmem, S_IRUGO, sculld_show_dmem, NULL);
              static int __init sculld_register_dev(struct sculld_dev *dev, int index)
              {
              	/* 創(chuàng)建此device屬性文件 */
              	ret |= device_create_file(&dev->ldev.dev, &dev_attr_dmem);
              }

            • 第三個 0003-add-.gitignore.patch 是增加了 .gitignore 文件,屏蔽一些編譯生成的臨時文件;
            • 第四個 0004-port-qset-get-set-ioctl-to-use-sysfs-entry.patch 演示了怎樣把基于 ioctl 的操作接口改進(jìn)成為基于 sysfs 接口,由于原來的 ioctl 接口設(shè)置和獲取 qset 信息是表示整個驅(qū)動模塊級的變量,它用來控制整個驅(qū)動程序而非驅(qū)動所支持的單個的設(shè)備,因此這個 qset 屬性使用 DRIVER_ATTR 來添加更為合適;
              ssize_t sculld_show_qset(struct device_driver *driver, char *buf)
              {
              	return snprintf(buf, PAGE_SIZE, "%d\n", sculld_qset);
              }
              ssize_t sculld_store_qset(struct device_driver *driver, const char *buf,
              		size_t count)
              {
              	sculld_qset = simple_strtol(buf, NULL, 0);
              	return count;
              }
              	/* 聲明一個權(quán)限為0644的可同時讀寫的driver屬性 */
              static DRIVER_ATTR(qset, S_IRUGO | S_IWUSR, sculld_show_qset, sculld_store_qset);
              	/* 創(chuàng)建此driver屬性文件 */
              	result = driver_create_file(&sculld_driver.driver, &driver_attr_qset);
              

              驅(qū)動屬性最終出現(xiàn)如 "/sys/bus/ldd/drivers/sculld/qset" ,這里聲明的是同時可讀寫的,權(quán)限位 0644 與其保持一致。

              6446 0 -rw-r--r-- 1 root root 4096 12月 14 07:44 /sys/bus/ldd/drivers/sculld/qset




            回頁首


            小結(jié)

            sysfs 給應(yīng)用程序提供了統(tǒng)一訪問設(shè)備的接口,但可以看到, sysfs 僅僅是提供了一個可以統(tǒng)一訪問設(shè)備的框架,但究竟是否支持 sysfs 還需要各設(shè)備驅(qū)動程序的編程支持;在 2.6 內(nèi)核誕生 5年以來的發(fā)展中,很多子系統(tǒng)、設(shè)備驅(qū)動程序逐漸轉(zhuǎn)向了 sysfs 作為與用戶空間友好的接口,但仍然也存在大量的代碼還在使用舊的 proc 或虛擬字符設(shè)備的 ioctl 方式;如果僅從最終用戶的角度來說, sysfs 與 proc 都是在提供相同或類似的功能,對于舊的 proc 代碼,沒有絕對的必要去做 proc 至 sysfs 的升級;因此在可預(yù)見的將來, sysfs 會與 proc, debugfs, configfs 等共存很長一段時間。






            回頁首


            下載

            描述名字大小下載方法
            本文用到的 Sysfs 模塊編程示例1sysfs-examples.tar.gz58KBHTTP
            關(guān)于下載方法的信息

            注意:

            1. 這是針對 Fedora10 內(nèi)核(2.6.27.5-117.fc10.i686)改進(jìn)后的代碼,而 LDD3-examples 原始代碼下載位置在 examples.oreilly.com 站點(diǎn)。


            參考資料

            學(xué)習(xí)

            獲得產(chǎn)品和技術(shù)
            • 下載 IBM 軟件試用版,體驗(yàn)強(qiáng)大的 DB2®,Lotus®,Rational®,Tivoli® 和 WebSphere® 軟件。

            討論


            關(guān)于作者

            程任全是一名中國的 Linux 內(nèi)核開發(fā)者,2005 年從國防科學(xué)技術(shù)大學(xué)(N.U.D.T.)獲得空間工程學(xué)士學(xué)位,現(xiàn)任 UIT(創(chuàng)新科存儲技術(shù)有限公司)存儲開發(fā)工程師,目前致力于高性能 iSCSI/NAS 存儲設(shè)備的開發(fā);而在業(yè)余時間里更喜歡的是與國際開源社區(qū)交流,和向 Linux 內(nèi)核貢獻(xiàn)代碼。

            posted on 2010-05-31 15:33 小默 閱讀(1484) 評論(3)  編輯 收藏 引用 所屬分類: Linux

            評論

            # re: 【轉(zhuǎn)】使用 /sys 文件系統(tǒng)訪問 Linux 內(nèi)核 2012-03-13 18:12 Mary22Mcpherson

            If you are willing to buy a house, you would have to receive the <a href="http://goodfinance-blog.com">loan</a>. Furthermore, my mother always takes a small business loan, which is really firm.   回復(fù)  更多評論   

            導(dǎo)航

            統(tǒng)計(jì)

            留言簿(13)

            隨筆分類(287)

            隨筆檔案(289)

            漏洞

            搜索

            積分與排名

            最新評論

            閱讀排行榜

            亚洲国产日韩综合久久精品| 久久精品国产亚洲AV无码麻豆 | 久久激情亚洲精品无码?V| 亚洲αv久久久噜噜噜噜噜| 久久有码中文字幕| 伊人久久大香线蕉综合5g| 模特私拍国产精品久久| 国产精品视频久久| 精品999久久久久久中文字幕| 久久久久国产精品熟女影院 | 亚洲国产成人久久综合一| 久久久久久免费一区二区三区| 一级做a爰片久久毛片16| 一本色道久久88精品综合| 久久久国产打桩机| 久久久久亚洲Av无码专| 国产精品久久久久久久人人看| 亚洲精品乱码久久久久66| 日韩久久无码免费毛片软件| 久久久久亚洲精品日久生情 | 久久久精品2019免费观看| 久久天天躁狠狠躁夜夜不卡| 蜜桃麻豆www久久国产精品| 激情久久久久久久久久| 免费精品国产日韩热久久| 久久精品无码一区二区日韩AV| 青青草国产精品久久久久| 国产午夜精品理论片久久影视 | 色偷偷偷久久伊人大杳蕉| 欧美亚洲另类久久综合| 久久精品一区二区| 美女写真久久影院| 一本久久久久久久| 久久97久久97精品免视看| 国产99久久久久久免费看 | 亚洲七七久久精品中文国产| 久久国产精品一区| 狠狠精品干练久久久无码中文字幕 | 亚洲日本va午夜中文字幕久久| 欧美午夜A∨大片久久| 一级A毛片免费观看久久精品|