sysfs詳解

http://bbs.chinaunix.net/thread-1974706-1-1.html

sysfs

是 Linux 內核中設計較新的一種虛擬的基於內存的文件系統,它的作用與 proc 有些類似,但除了與 proc
相同的具有查看和設定內核參數功能之外,還有爲 Linux 統一設備模型作爲管理之用。相比於 proc 文件系統,使用 sysfs
導出內核數據的方式更爲統一,並且組織的方式更好,它的設計從 proc 中吸取了很多教訓。本文就 sysfs 的掛載點 /sys 目錄結構、其與
Linux 統一設備模型的關係、常見屬性文件的用法等方面對 sysfs 作入門介紹,並且就內核編程方面,以具體的例子來展示如何添加 sysfs
支持。
      

        sysfs 的歷史,其與 proc 的關係?
sysfs 本身並不是一項很新的技術,但筆者發現,雖然 sysfs 從2003年誕生至今已有5年,但人們對 sysfs
依然缺乏瞭解;一個很重要的原因可能是缺乏文檔, Linux 內核方面最重要的理論書籍“Linux 設備驅動第3版”和“理解 Linux
內核第2版”都誕生於2003年前後,並且從那以後尚未有再版過,其它一些重要文章則多對 sysfs 與 proc 相提並論且舉例常常只有
proc,這導致了 sysfs 的很多重要概念至今仍鮮爲人知,因此有必要對 sysfs 作更多介紹,這是寫作本文的初衷。
      
sysfs 與 /sys
      
sysfs
文件系統總是被掛載在 /sys 掛載點上。雖然在較早期的2.6內核系統上並沒有規定 sysfs 的標準掛載位置,可以把 sysfs
掛載在任何位置,但較近的2.6內核修正了這一規則,要求 sysfs 總是掛載在 /sys 目錄上;針對以前的 sysfs
掛載位置不固定或沒有標準被掛載,有些程序從 /proc/mounts 中解析出 sysfs
是否被掛載以及具體的掛載點,這個步驟現在已經不需要了。請參考附錄給出的
sysfs-rules.txt
文件鏈接。
      
sysfs 與 proc
      
sysfs 與 proc 相比有很多優點,最重要的莫過於設計上的清晰。一個 proc 虛擬文件可能有內部格式,如 /proc/scsi/scsi
,它是可讀可寫的,(其文件權限被錯誤地標記爲了 0444
!,這是內核的一個BUG),並且讀寫格式不一樣,代表不同的操作,應用程序中讀到了這個文件的內容一般還需要進行字符串解析,而在寫入時需要先用字符串
格式化按指定的格式寫入字符串進行操作;相比而言, sysfs 的設計原則是一個屬性文件只做一件事情, sysfs
屬性文件一般只有一個值,直接讀取或寫入。整個 /proc/scsi 目錄在2.6內核中已被標記爲過時(LEGACY),它的功能已經被相應的 /sys 屬性文件所完全取代。新設計的內核機制應該儘量使用 sysfs 機制,而將 proc 保留給純淨的“進程文件系統”。
      




回頁首
初識 /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
這些子目錄,本文將分別介紹這些目錄存在的含義。
      
第二個 ls 命令展示了在一個 pci 設備目錄下的文件, "ls"
命令的 "-F" 命令爲所列出的每個文件使用後綴來顯示文件的類型,後綴 "/" 表示列出的是目錄,後綴 "@"
表示列出的是符號鏈接文件。可以看到第二個目錄下包含有普通文件 (regular file) 和符號鏈接文件 (symbolic link
file) ,本文也將以這個具體的設備爲例說明其中每一個普通文件的用途。
      




回頁首
/sys 文件系統下的目錄結構
      
/sys 下的目錄結構是經過精心設計的:在 /sys/devices
下是所有設備的真實對象,包括如視頻卡和以太網卡等真實的設備,也包括 ACPI 等不那麼顯而易見的真實設備、還有 tty, bonding
等純粹虛擬的設備;在其它目錄如 class, bus 等中則在分類的目錄中含有大量對 devices 中真實對象引用的符號鏈接文件;
清單1
中在 /sys 根目錄下頂層目錄的意義如下:
      
表 1. /sys 下的目錄結構
      /sys 下的子目錄所包含的內容/sys/devices這是內核對系統中所有設備的分層次表達模型,也是 /sys 文件系統管理設備的最重要的目錄結構,下文會對它的內部結構作進一步分析;/sys/dev這個目錄下維護一個按字符設備和塊設備的主次號碼(major:minor)鏈接到真實的設備(/sys/devices下)的符號鏈接文件,它是在內核 2.6.26 首次引入;/sys/bus這是內核設備按總線類型分層放置的目錄結構, devices 中的所有設備都是連接於某種總線之下,在這裏的每一種具體總線之下可以找到每一個具體設備的符號鏈接,它也是構成 Linux 統一設備模型的一部分;/sys/class這是按照設備功能分類的設備模型,如系統所有輸入設備都會出現在 /sys/class/input 之下,而不論它們是以何種總線連接到系統。它也是構成 Linux 統一設備模型的一部分;/sys/block這
裏是系統中當前所有的塊設備所在,按照功能來說放置在 /sys/class 之下會更合適,但只是由於歷史遺留因素而一直存在於
/sys/block, 但從 2.6.22 開始就已標記爲過時,只有在打開了 CONFIG_SYSFS_DEPRECATED
配置下編譯纔會有這個目錄的存在,並且在 2.6.26 內核中已正式移到 /sys/class/block, 舊的接口 /sys/block
爲了向後兼容保留存在,但其中的內容已經變爲指向它們在 /sys/devices/ 中真實設備的符號鏈接文件;/sys/firmware這裏是系統加載固件機制的對用戶空間的接口,關於固件有專用於固件加載的一套API,在附錄 LDD3 一書中有關於內核支持固件加載機制的更詳細的介紹;/sys/fs這
裏按照設計是用於描述系統中所有文件系統,包括文件系統本身和按文件系統分類存放的已掛載點,但目前只有 fuse,gfs2 等少數文件系統支持
sysfs 接口,一些傳統的虛擬文件系統(VFS)層次控制參數仍然在 sysctl (/proc/sys/fs) 接口中中;/sys/kernel這裏是內核所有可調整參數的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等幾項較新的設計在使用它,其它內核可調整參數仍然位於 sysctl (/proc/sys/kernel) 接口中 ;/sys/module這裏有系統中所有模塊的信息,不論這些模塊是以內聯(inlined)方式編譯到內核映像文件(vmlinuz)中還是編譯爲外部模塊(ko文件),都可能會出現在 /sys/module 中:
  • 編譯爲外部模塊(ko文件)在加載後會出現對應的 /sys/module//, 並且在這個目錄下會出現一些屬性文件和屬性目錄來表示此外部模塊的一些信息,如版本號、加載狀態、所提供的驅動程序等;
  • 編譯爲內聯方式的模塊則只在當它有非0屬性的模塊參數時會出現對應的 /sys/module/, 這些模塊的可用參數會出現在 /sys/modules//parameters/ 中,
    • 如 /sys/module/printk/parameters/time 這個可讀寫參數控制着內聯模塊 printk 在打印內核消息時是否加上時間前綴;
    • 所有內聯模塊的參數也可以由
      ".="
      的形式寫在內核啓動參數上,如啓動內核時加上參數 "printk.time=1" 與 向
      "/sys/module/printk/parameters/time" 寫入1的效果相同;
  • 沒有非0屬性參數的內聯模塊不會出現於此。
/sys/power這裏是系統中電源選項,這個目錄下有幾個屬性文件可以用於控制整個機器的電源狀態,如可以向其中寫入控制命令讓機器關機、重啓等。/sys/slab (對應 2.6.23 內核,在 2.6.24 以後移至 /sys/kernel/slab)從2.6.23
開始可以選擇 SLAB 內存分配器的實現,並且新的 SLUB(Unqueued Slab
Allocator)被設置爲缺省值;如果編譯了此選項,在 /sys 下就會出現 /sys/slab ,裏面有每一個 kmem_cache
結構體的可調整參數。對應於舊的 SLAB 內存分配器下的 /proc/slabinfo 動態調整接口,新式的
/sys/kernel/slab/ 接口中的各項信息和可調整項顯得更爲清晰。
      
      
接下來對 /sys/devices/ 下的目錄結構作進一步探討:
      
清單 2. 查看 /sys/devices/ 的目錄結構
$ ls -F /sys/devices/
isa/  LNXSYSTM:00/  pci0000:00/  platform/  pnp0/  pnp1/  system/  virtual/
      
可以看到,在 /sys/devices/ 目錄下是按照設備的基本總線類型分類的目錄,再進入進去查看其中的 PCI 類型的設備:
      
清單 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 總線接入的設備號分類存放的目錄,再查看其中一個,
      
清單 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
      
可以看到,其中有一個目錄 0000:01:00.0/, 其它都是屬性文件和屬性組,而如果對 0000:01:00.0/ 子目錄中進行再列表查看則會得到
清單1
的目錄結構。
      
繼續以上過程可以瞭解整個目錄樹的結構,這裏把它整理成
圖 1. sysfs 目錄層次圖
      
        
圖 1. sysfs 目錄層次圖
        

      
      
其中涉及到 ksets, kobjects, attrs 等很多術語,這就不得不提到 Linux 統一設備模型。
      




回頁首
Linux 統一設備模型
      

Linux 2.5
內核的開發過程中,人們設計了一套新的設備模型,目的是爲了對計算機上的所有設備進行統一地表示和操作,包括設備本身和設備之間的連接關係。這個模型是在
分析了 PCI 和 USB
的總線驅動過程中得到的,這兩個總線類型能代表當前系統中的大多數設備類型,它們都有完善的熱挺拔機制和電源管理的支持,也都有級連機制的支持,以橋接的
PCI/USB 總線控制器的方式可以支持更多的 PCI/USB
設備。爲了給所有設備添加統一的電源管理的支持,而不是讓每個設備中去獨立實現電源管理的支持,人們考慮的是如何儘可能地重用代碼;而且在有層次模型的
PCI/USB 總線中,必須以合理形式展示出這個層次關係,這也是電源管理等所要求的必須有層次結構。
      
如在一個典型的
PC 系統中,中央處理器(CPU)能直接控制的是 PCI 總線設備,而 USB 總線設備是以一個 PCI 設備(PCI-USB橋)的形式接入在
PCI 總線設備上,外部 USB 設備再接入在 USB 總線設備上;當計算機執行掛起(suspend)操作時, Linux 內核應該以
“外部USB設備->USB總線設備->PCI總線設備”
的順序通知每一個設備將電源掛起;執行恢復(resume)時則以相反的順序通知;反之如果不按此順序則將有設備得不到正確的電源狀態變遷的通知,將無法
正常工作。
      
sysfs 是在這個 Linux 統一設備模型的開發過程中的一項副產品(見
參考資料
中 Greg K. Hartman 寫作的 LinuxJournal
文章)。爲了將這些有層次結構的設備以用戶程序可見的方式表達出來,人們很自然想到了利用文件系統的目錄樹結構(這是以 UNIX
方式思考問題的基礎,一切都是文件!)在這個模型中,有幾種基本類型,它們的對應關係見
表 2. Linux 統一設備模型的基本結構

      
表 2. Linux 統一設備模型的基本結構
      類型所包含的內容對應內核數據結構對應/sys項設備(Devices)設備是此模型中最基本的類型,以設備本身的連接按層次組織struct device/sys/devices/*/*/.../設備驅動(Device Drivers)在一個系統中安裝多個相同設備,只需要一份驅動程序的支持struct device_driver/sys/bus/pci/drivers/*/總線類型(Bus Types)在整個總線級別對此總線上連接的所有設備進行管理struct bus_type/sys/bus/*/設備類別(Device Classes)這是按照功能進行分類組織的設備層次樹;如 USB 接口和 PS/2 接口的鼠標都是輸入設備,都會出現在 /sys/class/input/ 下struct class/sys/class/*/
      
從內核在實現它們時所使用的數據結構來說, Linux 統一設備模型又是以兩種基本數據結構進行樹型和鏈表型結構組織的:
      
  • kobject: 在 Linux 設備模型中最基本的對象,它的功能是提供引用計數和維持父子(parent)結構、平級(sibling)目錄關係,上面的 device, device_driver 等各對象都是以 kobject 基礎功能之上實現的;
          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 內含一個 atomic_t 類型用於引用計數, parent 是單個指向父節點的指針, entry 用於父 kset 以鏈表頭結構將 kobject 結構維護成雙向鏈表;
  • kset: 它用來對同類型對象提供一個包裝集合,在內核數據結構上它也是由內嵌一個 kboject 實現,因而它同時也是一個 kobject (面向對象 OOP 概念中的繼承關係) ,具有 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 維護成雙向鏈表;

      
涉及到文件系統實現來說, sysfs 是一種基於 ramfs
實現的內存文件系統,與其它同樣以 ramfs 實現的內存文件系統(configfs,debugfs,tmpfs,...)類似, sysfs
也是直接以 VFS 中的 struct inode 和 struct dentry 等 VFS
層次的結構體直接實現文件系統中的各種對象;同時在每個文件系統的私有數據 (如 dentry->d_fsdata 等位置) 上,使用了稱爲
struct sysfs_dirent 的結構用於表示 /sys 中的每一個目錄項。
      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 來統一設備模型中的 kset/kobject/attr/attr_group.
      

體在數據結構成員上, sysfs_dirent 上有一個 union
共用體包含四種不同的結構,分別是目錄、符號鏈接文件、屬性文件、二進制屬性文件;其中目錄類型可以對應 kobject,在相應的 s_dir
中也有對 kobject 的指針,因此在內核數據結構, kobject 與 sysfs_dirent 是互相引用的;
      
有了這些概念,再來回頭看
圖 1. sysfs 目錄層次圖
所表達的 /sys 目錄結構就是非常清晰明瞭:
      
  • 在 /sys 根目錄之下的都是 kset,它們組織了 /sys 的頂層目錄視圖;
  • 在部分 kset 下有二級或更深層次的 kset;
  • 每個 kset 目錄下再包含着一個或多個 kobject,這表示一個集合所包含的 kobject 結構體;
  • 在 kobject 下有屬性(attrs)文件和屬性組(attr_group),屬性組就是組織屬性的一個目錄,它們一起向用戶層提供了表示和操作這個 kobject 的屬性特徵的接口;
  • 在 kobject 下還有一些符號鏈接文件,指向其它的 kobject,這些符號鏈接文件用於組織上面所說的 device, driver, bus_type, class, module 之間的關係;
  • 不同類型如設備類型的、設備驅動類型的 kobject 都有不同的屬性,不同驅動程序支持的 sysfs 接口也有不同的屬性文件;而相同類型的設備上有很多相同的屬性文件;

      

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




回頁首
常見 sysfs 屬性的功能
      
使用 sysfs 的關鍵就是掌握這些 sysfs 屬性的用法,下面以一些常見的 sysfs 屬性來展示它的用法;
      
使用設備(PCI)的 sysfs 屬性文件
      
以一份桌面系統上的視頻卡爲例,列舉它對應的 kobject 上的屬性文件的對應用途;
      
一般來說,在 Linux 桌面上都有視頻卡以支持 Xorg 軟件包作爲 XWindow 服務器來運行,因此先找到 Xorg 的進程號,查看這個進程所使用的所有文件(注意查看這個進程屬性需要 root 用戶權限);
      # 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 服務器是以內存映射 (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"
      

實上, PCI 設備對應的 kobject 目錄下的 config 正是代表PCI設備的“配置空間”,對於普通 PCI
(非PCI-E)設備而言,其配置空間大小一般是 256字節,這個空間可以使用十六進制工具 dump 出來,如下。(有關 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
      

個空間正好是 256字節大小,熟悉 PCI 的人們還可以知道,從 PCI 配置空間可以讀到有關此 PCI
設備的很多有用信息,如廠商代碼,設備代碼,IRQ 號碼等;前四個字節 0x39 0x10 0x30 0x63 就是按小端(little
endian)存放的2個短整數,因此其 PCI 廠商號碼和 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 是配置空間對用戶的接口以外,還有 resource{0,1,2,...} 是資源空間,對應着 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 權限位的纔是可以寫入。其中大小爲
4096字節的屬性一般是純文本描述的屬性,可以直接 cat 讀出和用 echo 字符串的方法寫入;其它非
4096字節大小的一般是二進制屬性,類似於上面的 config 屬性文件;關於純文本屬性和二進制屬性,在下文
編程實踐:添加sysfs支持
一節會進一步說明。
      

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

      
有了 PCI 核心對 sysfs
的完善支持,每個設備甚至不用單獨的驅動程序,如這裏的 "0000:01:00.0" 不需要一個內核級的驅動程序,有了 PCI
核心對該設備的配置空間發現機制,可以自動發現它的各個不同段落的資源屬性,在 Xorg 應用程序中可以直接以
"/usr/lib/xorg/modules/drivers/sis_drv.so"
這個用戶空間的驅動程序對其進行映射,就可以直接操作此視頻卡了;
      
有了這一個 PCI 設備的示例可以知道,有了一個 PCI 設備的 /sys/devices/ 設備對象,去訪問它的各項屬性和設置屬性都非常簡單。
      
使用 uevent
      

sysfs 下的很多 kobject 下都有 uevent 屬性,它主要用於內核與 udev (自動設備發現程序)之間的一個通信接口;從
udev 本身與內核的通信接口 netlink 協議套接字來說,它並不需要知道設備的 uevent 屬性文件,但多了 uevent
這樣一個接口,可用於 udevmonitor 通過內核向 udevd (udev 後臺程序)發送消息,也可用於檢查設備本身所支持的
netlink 消息上的環境變量,這個特性一般用於開發人員調試 udev 規則文件, udevtrigger 這個調試工具本身就是以寫各設備的
uevent 屬性文件實現的。
      
這些 uevent 屬性文件一般都是可寫的,其中 /sys/devices/ 樹下的很多 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
[...]

      

面截取的最後一個是 SCSI 硬盤控制器設備的 uevent 屬性文件,這些 /devices/ 屬性文件都支持寫入,當前支持寫入的參數有
"add","remove","change","move","online","offline"。如,寫入 "add",這樣可以向
udevd 發送一條 netlink 消息,讓它再重新一遍相關的 udev 規則文件;這個功能對開發人員調試 udev 規則文件很有用。
      # echo add > /sys/devices/pci0000:00/0000:00:02.5/uevent

      
使用驅動(PCI)的 sysfs 屬性文件, bind, unbind 和 new_id
      
在設備驅動 /sys/bus/*/driver/... 下可以看到很多驅動都有 bind, unbind, new_id 這三個屬性,
      # find /sys/bus/*/drivers/ -name bind -ls
...
      

一個設備驅動程序在程序內以某種方式註明了可用於哪些硬件,如所有的 PCI 驅動都使用 MODULE_DEVICE_TABLE 聲明瞭所能驅動的
PCI 硬件的 PCI 設備號。但驅動程序不能預知未來,未來生產的新的硬件有可能兼容現有硬件的工作方式,就還可以使用現有硬件驅動程序來工作。在
bind 和 unbind 發明以前,這種情況除了修改 PCI 設備驅動程序的 DEVICE_TABLE
段落,重新編譯驅動程序,以外別無他法,在 2.6 內核上添加了 bind 和 unbind
之後可以在不重新編譯的情況下對設備和驅動之間進行手工方式地綁定。
      
而且對於有些硬件設備可以有多份驅動可用,但任何具體時
刻只能有一個驅動程序來驅動這個硬件,這時可以使用 bind/unbind
來強制使用和不使用哪一個驅動程序;(注意關於多種驅動程序的選擇,更好的管理方法是使用 modprobe.conf 配置文件,需要重啓才生效,而
bind/unbind 提供的是一種臨時的無需重啓立即生效的途徑;)
      
使用它們可以強制綁定某個設備使用或強制不使用某個驅動程序,操作方法就是通過 bind 和 unbind 接口。
      # find /sys/ -type f namebindornameunbindornamenewid -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
      
這個結果中特別提到了 8139too 這份驅動程序的這三個屬性文件,
      # 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: 沒有那個設備
# ip addr
1: lo:  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:  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:  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:  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:  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:  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:  mtu 1500 qdisc noop state DOWN
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
4: eth0:  mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 00:14:2a:d1:16:72 brd ff:ff:ff:ff:ff:ff
      
這一段操作過程演示瞭如何對 PCI 設備 "0000:00:0e.0" 強制取消綁定 "8139too" 驅動和強制綁定 "8139too" 驅動:
      
  • 對 unbind 屬性寫入總線號碼(bus_id)即是強制取消綁定;
  • 對 bind 屬性寫入總線號碼(bus_id)即是強制綁定;

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

特別注意: 在這一個例子中, "echo 0000:00:0e.0 >
/sys/bus/pci/drivers/8139too/unbind" 這第一個寫入命令以 "No such device"
爲錯誤退出,而後續的 "echo -n" 命令則可以成功。這是因爲內核在對總線號碼進行匹配時過於嚴格了,通常的 "echo"
命令寫入一個字符串會以一個換行符結束輸出,內核所接收到的是帶有這個換行符的 bus_id 字符串,將它與內核數據結構中的真正的 bus_id
字符串相比較,當然不能找到;所幸的是,這個問題在最新的 2.6.28
開發中的內核上已已經解決,它將這個比較函數改爲一個特殊實現的字符串比較,自動忽略結尾處的換行符,在 2.6.28-rc6
內核上測試,不帶"-n"參數的 echo 命令已經可以寫入成功。
      
而 new_id
屬性文件也可以以另一種途徑解決新的設備號問題:它是一個只寫的驅動屬性,可用於向其中寫新的設備號。它支持寫入 2至7個十六進制整形參數,分別代表
vendor, device, subvendor, subdevice, class, class_mask, driver_data
最少爲 2個是因爲一個 PCI設備主要以廠商號(vendor)和設備號(device)所唯一標定,其它 5個參數如果不輸入則缺省值爲
PCI_ANY_ID(0xffff)。
        5441    0 --w-------   1 root     root         4096 12月 14 18:15 \
/sys/bus/pci/drivers/8139too/new_id
      
從 8139too 驅動上可以看到它當前所靜態支持的設備號碼列表,其中包括當前系統中的設備 10ec:8139, 假設未來有一款 8140 設備也滿足 8139 設備的硬件通訊協議,於是可以使用 8139too 驅動程序來驅動它,操作如下
      # echo '10ec 8140' > /sys/bus/pci/drivers/8139too/new_id
      
這在不更新驅動程序的情況下調試設備很有用處。
      
使用 scsi_host 的 scan 屬性
      
在具有使用 SCSI 總線連接的主機上,與 PCI類似的是也採用四個號碼作爲一組來描述一個設備,其中位於最頂層的是 scsi_host。
      
我們從設備類別 /class/爲起點來探索:
      # 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/ 下的都改爲符號鏈接,真實的 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
總計 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 特有的可寫參數: scan,state,supported_mode,active_mode;可以向其中寫入不同的參數來控制此 SCSI 控制器的各種狀態;
  • 其它一些可讀屬性用於讀取這個 SCSI 控制器的一些當前值;

      

中的 scan 屬性文件在調試一些 SCSI 硬件驅動時很有用,它是隻寫的,可以寫入三個至四個以空格分開的整數,用於分別指定對應的 host,
channel, id, lun 進行重新搜索。且這個 scan 屬性支持以"-"作爲通配符,如以下命令可以執行讓整個 scsi_host
進行重新搜索,這個功能用於調試某些對熱挺拔實現不完善的 SCSI 驅動程序很有用:
      # echo '- - -' >/sys/devices/pci0000:00/0000:00:02.5/host0/scsi_host/host0/scan
      
內核模塊中的 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 是模塊引用計數, sections 屬性組中有一些模塊加載至內存的相應節信息, drivers/ 目錄中是對所提供的驅動的鏈接;
      

爲模塊是內核驅動編程的最佳選擇,而一個模塊有可能提供多個驅動程序,因而在未知一個設備在用哪一個驅動的情況下可以先從 /sys/module/
查找相應模塊的情況,再從 drivers/ 發現出真正的驅動程序。或者也可以完全反過來利用這些信息,先用 lspci/lshw 等工具找到
/sys/devices/ 下的設備節點,再從其設備的 driver 鏈接找到 /sys/bus/*/drivers/ 下的
device_driver, 再從 device_driver 下的 module 鏈接找到
/sys/module/*/,這樣就可以得到已加載模塊中空間是哪一個模塊在給一個設備提供驅動程序。
      
更多 sysfs 屬性文件
      

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




回頁首
Sysfs 源碼分析和編程實踐
      
從源代碼中理解 sysfs 屬性的用途
      
更多的 sysfs 屬性的功能只能靠閱讀源代碼來理解。還是以上文提到的 scsi_host 的 scan 屬性來理解,這個功能沒有任何文檔上有描述,因此只能去讀源代碼。
      

內核中, sysfs 屬性一般是由 __ATTR 系列的宏來聲明的,如對設備的使用 DEVICE_ATTR ,對總線使用 BUS_ATTR
,對驅動使用 DRIVER_ATTR ,對類別(class)使用 CLASS_ATTR, 這四個高級的宏來自於
, 都是以更低層的來自
中的 __ATTR/__ATRR_RO 宏實現; 因此我們在內核源碼樹中相應位置 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
宏聲明有四個參數,分別是名稱、權限位、讀函數、寫函數。這裏對應的,名稱是 scan,
權限是隻有屬主可寫(S_IWUSR)、沒有讀函數、只有寫函數。因此讀寫功能與權限位是對應的,因爲 DEVICE_ATTR
把權限位聲明與真正的讀寫是否實現放在了一起,減少了出現不一致的可能。(上文提到 /proc/scsi/scsi
接口的權限位聲明與其功能不對應,這與註冊 proc
接口的函數設計中的不一致是有關係的,權限位聲明與功能實現不在代碼中同一個位置,因此易出錯。雖然修復 /proc/scsi/scsi
的權限位錯誤很容易,但內核團隊中多年來一直沒有人發現或未有人去修正這個 BUG,應該是與 /proc/scsi/
接口的過時有關,過時的功能會在未來某個內核版本中去除。)
      
上面的 scan 屬性寫入功能是在 store_scan
函數中實現的,這個接口的四個參數中, buf/count 代表用戶寫入過來的字符串,它把 buf 進一步傳給了 scsi_scan
函數;如果進一步分析 scsi_scan 函數實現可以知道,它期望從 buf 中接受三個或四個整型值(也接受"-"作爲通配符),分別代表
host, channel, id 三個值,(第四個整數在早期內核中曾代表 lun
號碼,但在較新內核中第四個數字被忽略,僅作爲向後兼容保留接受四個整數),然後對具體的 (host, channel, id)
進行重新掃描以發現這個 SCSI 控制器上的設備變動。
      
添加 sysfs 支持
      
如果你正在開發的設備驅動程序中需要與用戶層的接口,一般可選的方法有:
      

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

重要的是,添加虛擬字符設備支持和註冊 proc 接口支持這兩者所需要增加的代碼量都並不少,最好的方法還是使用 sysfs
屬性支持,一切在用戶層是可見的透明,且增加的代碼量是最少的,可維護性也最好;方法就是使用
頭文件提供的這四個宏,分別應用於總線/類別/驅動/設備四種內核數據結構對象上:
      #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)屬性一般用於新設
計的總線和新設計的類別,這兩者一般是不用的;因爲你的設備一般是以PCI等成熟的常規方式連接到主機,而不會去新發明一種類型;使用驅動屬性和設備屬性
的區別就在於:看你的 sysfs 屬性設計是針對整個驅動有效的還是針對這份驅動所可能支持的每個設備分別有效。
      
從頭文
件中還可以找到 show/store 函數的原型,注意到它和虛擬字符設備或 proc 項的 read/write 的作用很類似,但有一點不同是
show/store 函數上的 buf/count 參數是在 sysfs 層已作了用戶區/內核區的內存複製,虛擬字符設備上常見的 __user
屬性在這裏並不需要,因而也不需要多一次 copy_from_user/copy_to_user, 在 show/store 函數參數上的
buf/count 參數已經是內核區的地址,可以直接操作。
      
上面四種都是 Linux
統一設備模型所添加的高級接口,如果使用 sysfs 所提供的底層接口的話,則還有下面兩個,定義來自
:(上面的總線/類別/驅動/設備四個接口都是以這裏的__ATTR實現的)
      #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,                                 \
}
      

面這些宏都是在註冊總線/類別/驅動/設備時作爲缺省屬性而使用的,在實際應用中還有一種情況是根據條件動態添加屬性,如 PCI 設備上的
resource{0,1,2,...} 屬性文件,因爲一個 PCI 設備上的可映射資源究竟有多少無法預知,也只能以條件判斷的方式動態添加上。
      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);
      
這兩個函數可以對一個 kobject 動態添加上文本屬性或二進制屬性,這也是唯一可以添加二進制屬性的方法。
      
二進制屬性與普通文本屬性的區別在於:
      
  • 二進制屬性 struct bin_attribute 中內嵌一個 struct attribute 結構體對象,因此具有普通屬性的所有功能特徵;
  • 二進制屬性上多一個 size 用來描述此二進制文件的大小,而普通屬性文件的大小總是 4096, 準確地說,應該是一個內存頁的大小,因爲從當前 sysfs 內核實現來說,它分配一個內存頁面來作爲 (buf/count) 的緩衝區;
  • 二進制屬性比普通屬性多內存映射(mmap)接口的支持;

      
編程示例,對 LDD3 一書中的 lddbus 驅動程序的 sysfs 改進
      
首先,這個程序本身是針對當時作者寫書的年代的內核(2.6.11)而編寫的,在當前的 Fedora10 系統 (2.6.27.5-117.fc10.i686) 上甚至無法編譯編譯通過;因此首先需要將它移植過來至少達到可運行狀態;
      
附件的壓縮包中含有修改過的 lddbus, sculld 的源代碼和修改過程的四個patch:
      
  • 第一個 0001-ldd3-examples-build-on-fedora-10-2.6.27.5-117.fc10.i.patch 是將 lddbus 和 sculld 移植到 Fedora10 內核上可運行,這其中主要是一此內核 API 的變化;

  • 二個 0002-port-dmem-proc-entry-to-use-sysfs-entry.patch 演示了怎樣將原有的 proc
    接口改進成爲 sysfs 屬性接口的,從這個 patch 中可以看到刪除的代碼多而新增加的代碼少,這說明對於相同的功能,使用 sysfs
    編程接口的代碼量更少,而且 sysfs 代碼看起來也比 proc
    更爲整潔:打印每個設備的調試信息可以做成每個設備上分別有自己的接口,而不是統一的一個 proc 接口;設備屬性文件最終出現的位置如
    "/sys/devices/ldd0/sculld0/dmem"; static ssize_t sculld_show_dmem(struct device *ddev,
                    struct device_attribute *attr, char *buf)
    {
            /* 其中打印每個設備調試信息的代碼複製自原proc接口 */
    }
    static DEVICE_ATTR(dmem, S_IRUGO, sculld_show_dmem, NULL);
    static int __init sculld_register_dev(struct sculld_dev *dev, int index)
    {
            /* 創建此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
    的操作接口改進成爲基於 sysfs 接口,由於原來的 ioctl 接口設置和獲取 qset
    信息是表示整個驅動模塊級的變量,它用來控制整個驅動程序而非驅動所支持的單個的設備,因此這個 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;
    }
            /* 聲明一個權限爲0644的可同時讀寫的driver屬性 */
    static DRIVER_ATTR(qset, S_IRUGO | S_IWUSR, sculld_show_qset, sculld_store_qset);
            /* 創建此driver屬性文件 */
            result = driver_create_file(&sculld_driver.driver, &driver_attr_qset);
          
    驅動屬性最終出現如 "/sys/bus/ldd/drivers/sculld/qset" ,這裏聲明的是同時可讀寫的,權限位 0644 與其保持一致。
      6446    0 -rw-r--r--   1 root     root         4096 12月 14 07:44 /sys/bus/ldd/drivers/sculld/qset
          

      




回頁首
小結
      
sysfs
給應用程序提供了統一訪問設備的接口,但可以看到, sysfs 僅僅是提供了一個可以統一訪問設備的框架,但究竟是否支持 sysfs
還需要各設備驅動程序的編程支持;在 2.6 內核誕生 5年以來的發展中,很多子系統、設備驅動程序逐漸轉向了 sysfs
作爲與用戶空間友好的接口,但仍然也存在大量的代碼還在使用舊的 proc 或虛擬字符設備的 ioctl 方式;如果僅從最終用戶的角度來說,
sysfs 與 proc 都是在提供相同或類似的功能,對於舊的 proc 代碼,沒有絕對的必要去做 proc 至 sysfs
的升級;因此在可預見的將來, sysfs 會與 proc, debugfs, configfs 等共存很長一段時間。
轉自:http://www.ibm.com/developerworks/cn/linux/l-cn-sysfs/
發佈了4 篇原創文章 · 獲贊 4 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章