1. 程式人生 > >使用/sys檔案系統訪問Linux核心

使用/sys檔案系統訪問Linux核心

linux2.6核心引入sysfs檔案系統,sysfs可以看成與proc,devfs和devpty同類別的檔案系統,該檔案系統是虛擬的檔案系統,可以更方便對系統裝置進行管理。它可以產生一個包含所有系統硬體層次檢視,與提供程序和狀態資訊的proc檔案系統十分類似。
sysfs把連線在系統上的裝置和匯流排組織成為一個分級的檔案,它們可以由使用者空間存取,向用戶空間匯出核心的資料結構以及它們的屬性。sysfs的一個目的就是展示裝置驅動模型中各元件的層次關係,其頂級目錄包括block,bus,drivers,class,power和firmware等.

 

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 保留給純淨的“程序檔案系統”。

表 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/<module_name>/, 並且在這個目錄下會出現一些屬性檔案和屬性目錄來表示此外部模組的一些資訊,如版本號、載入狀態、所提供的驅動程式等;

編譯為內聯方式的模組則只在當它有非0屬性的模組引數時會出現對應的 /sys/module/<module_name>, 這些模組的可用引數會出現在/sys/modules/<modname>/parameters/<param_name> 中,

如 /sys/module/printk/parameters/time 這個可讀寫引數控制著內聯模組 printk 在列印核心訊息時是否加上時間字首;

所有內聯模組的引數也可以由 "<module_name>.<param_name>=<value>" 的形式寫在核心啟動引數上,如啟動核心時加上引數 "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/<slab_name> 介面中的各項資訊和可調整項顯得更為清晰。

 

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 還需要各裝置驅動程式的程式設計支援;在 2.6 核心誕生 5年以來的發展中,很多子系統、裝置驅動程式逐漸轉向了 sysfs 作為與使用者空間友好的介面,但仍然也存在大量的程式碼還在使用舊的 proc 或虛擬字元裝置的 ioctl 方式;如果僅從終端使用者的角度來說, sysfs 與 proc 都是在提供相同或類似的功能,對於舊的 proc 程式碼,沒有絕對的必要去做 proc 至 sysfs 的升級;因此在可預見的將來, sysfs 會與 proc, debugfs, configfs 等共存很長一段時間。

 


linux2.6核心引入sysfs檔案系統,sysfs可以看成與proc,devfs和devpty同類別的檔案系統,該檔案系統是虛擬的檔案系統,可以更方便對系統裝置進行管理。它可以產生一個包含所有系統硬體層次檢視,與提供程序和狀態資訊的proc檔案系統十分類似。
sysfs把連線在系統上的裝置和匯流排組織成為一個分級的檔案,它們可以由使用者空間存取,向用戶空間匯出核心的資料結構以及它們的屬性。sysfs的一個目的就是展示裝置驅動模型中各元件的層次關係,其頂級目錄包括block,bus,drivers,class,power和firmware等.

 

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 保留給純淨的“程序檔案系統”。

表 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/<module_name>/, 並且在這個目錄下會出現一些屬性檔案和屬性目錄來表示此外部模組的一些資訊,如版本號、載入狀態、所提供的驅動程式等;

編譯為內聯方式的模組則只在當它有非0屬性的模組引數時會出現對應的 /sys/module/<module_name>, 這些模組的可用引數會出現在/sys/modules/<modname>/parameters/<param_name> 中,

如 /sys/module/printk/parameters/time 這個可讀寫引數控制著內聯模組 printk 在列印核心訊息時是否加上時間字首;

所有內聯模組的引數也可以由 "<module_name>.<param_name>=<value>" 的形式寫在核心啟動引數上,如啟動核心時加上引數 "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/<slab_name> 介面中的各項資訊和可調整項顯得更為清晰。

 

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;