sysfs 的實作與應用詳解

透過 Linux v2.6.34 的原始碼解釋 sysfs 的實作與應用。

歷史

sysfs 的開發,目的是為了解決 procfs 不夠階層化的問題,並將有關設備的資訊從 procfs 中抽離。最早 sysfs 在實作時,以 ramfs 為基礎,被稱作 ddfs (Device Driver Filesystem),後來在 Linux 2.5 更名為 driverfs。

在 Linux 2.6 時設計了一個 kobject 子系統,拋棄了 ramfs,利用 kobject 來建立這些裝置的資訊。kobject 所提供的階層化組織,將會直接反映在 sysfs 上,因此可以很輕鬆的建出一系列有組織的目錄。後來發現 kobject 不只可以用在裝置模型上,也被證明在其他的核心子系統也同樣很有用,因此 driverfs 最後被更名為 sysfs,並通常掛載在 /sys 下。

因為本身就源自於 procfs 的設計思路,因此它所提供的也是 user space 與 kernel space 交換資訊的介面,同樣可以輕鬆透過 cat 或 echo 來與核心溝通。也由於系統的所有設備與匯流排都是透過 kobject 組織的,所以 sysfs 提供了系統的硬體拓樸面向 user space 的一種介面。

裝置模型

實作裝置模型最初的動機是:電源管理,因此實作一個樹狀的裝置拓樸是勢在必行的,這棵樹可以明確的看出哪個驅動程式連接到哪個控制器,哪個裝置連接到哪個匯流排,如此核心就可以透過裝置樹來以正確的順序關閉電源。

資料結構

kobject

裝置模型實作的中心為 kobject,kobject 提供了這些物件操作:

  1. reference count
  2. 管理物件的鏈結串列(集合)
  3. 集合的 spinlock
  4. 將物件屬性導出到使用者空間(透過sysfs)

/include/linux/kobject.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct kobject {
    const char *name;                           // 物件的文字名稱,可利用 sysfs 導出至 user space
    struct list_head entry;                     // 雙向鏈結串列,用於將若干 kobject 放置到一個鏈結串列中
    struct kobject *parent;                     // 用於 kobject 的階層結構
    struct kset *kset;                          // 用於將物件與其他物件放置到一個集合時
    struct kobj_type *ktype;                    // 提供包含 kobject 的資料結構的更多詳細資訊
    struct sysfs_dirent *sd;                    // 用於 kobject 與 sysfs 間的關聯
    struct kref kref;                           // 管理 reference count
    unsigned int state_initialized : 1;         // 是否已經初始化
    unsigned int state_in_sysfs : 1;            // 是否已在 sysfs 呈現
    unsigned int state_add_uevent_sent : 1;     // 紀錄 add uevent 是否發送
    unsigned int state_remove_uevent_sent : 1;  // 若有 add uevent 沒有 remove uevent 會補發 remove uevent
    unsigned int uevent_suppress : 1;           // 若為 1,在設備發生變化時不發送 uevent
};

kobject 的使用是直接將其嵌入到其他資料結構中,用作核心物件的基礎。

kobj_type

kobj_type 用於描述一系列 kobject 的預設行為。因此,不需要每個 kobject 都定義自己的行為,可以把行為儲存在 kobj_type 中,相同類型的 kobject 會指向相同的 kobj_type。

/include/linux/kobject.h

1
2
3
4
5
struct kobj_type {
    void (*release)(struct kobject *kobj);  // 當 kobject 的 reference counting == 0 時呼叫
    const struct sysfs_ops *sysfs_ops;      // 描述 sysfs 的讀寫行為
    struct attribute **default_attrs;       // 指向一個 struct attribute 陣列,用於定義與此 kobject 的預設屬性 (sysfs 下的檔案)
};

kset

很多時候必須將不同物件的集合歸類到集合中,例如,所有 character device 的集合,或是所有基於 PCI 的設備集合。kset 透過鏈結串列將相同物件集合起來。

/include/linux/kobject.h

1
2
3
4
5
6
struct kset {
    struct list_head list;                     // 所有屬於目前集合的 kobject 的鏈結串列
    spinlock_t list_lock;                      // 用於保護 list
    struct kobject kobj;                       // kset 即為使用 kobject 的第一個例子,用於管理 kset 本身
    const struct kset_uevent_ops *uevent_ops;  // function pointer,用於將集合的狀態資訊傳遞給 user space
};

kref

kref 用來儲存 kobject 的 reference count。

/include/linux/kref.h

1
2
3
struct kref {
    atomic_t refcount; // 當 refcount == 0 時,可以釋放該物件的記憶體
};

關係圖

sysfs

sysfs 跟 procfs 相同,是一個位於記憶體的虛擬檔案系統,它為我們提供了 kobject 階層結構的視圖,讓用戶可以將裝置拓樸視為一個簡單的檔案系統。sysfs 將 kobject 的屬性匯出成檔案,將核心變數提供給 user space 做讀取與寫入。

資料結構

sysfs_dirent

/fs/sysfs/sysfs.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct sysfs_dirent {
    atomic_t s_count;   // 當引用到 sysfs 節點時 + 1,不需要時 - 1
    atomic_t s_active;  // 規避掉 user 打開 sysfs 節點,就可以防止核心刪除 kobject 對應的實體的問題
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map dep_map;
#endif
    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;               // 低 8 位用作設定 sysfs 資料項目的型態(目錄、普通屬性、二進位屬性、符號連結),剩餘位元用於標記位元
    unsigned short s_mode;              // 目錄項目的存取權限
    ino_t s_ino;                        // inode index
    struct sysfs_inode_attrs *s_iattr;  // sysfs_dirent 的基本屬性,如 timestamp
};

s_active 是比較特別的屬性,用來解決 user 打開 sysfs 節點,就可以防止核心刪除 kobject 對應的實體的問題。因此當每次操作內部物件時,需要先呼叫 sysfs_get_active 取得活動引用,在結束操作時,必須立即使用 sysfs_put_active 釋放活動引用。

在刪除一個 sysfs 檔時,呼叫 sysfs_deactivate 將 active_count 設為負值,在這個計數器為負值時,就不能對 kobject 進行操作了。在 kobject 的所有使用者都消失時,核心才可以安全的刪除它,但是 sysfs 下的檔案和 sysfs_dirent 實體仍然會繼續存在。

s_active: [PATCH] sysfs: implement sysfs_dirent active reference and immediate disconnect

sysfs_dirent 下有一個匿名的union結構,用來描述檔案類型:

  1. sysfs_elem_dir 為目錄,指向一個 kobject
  2. sysfs_elem_symlink 為符號連結,指向另一個 sysfs_dirent
  3. sysfs_elem_attr 為普通屬性,指向 attribute
  4. sysfs_elem_bin_attr 為二進位屬性,指向 bin_attribute
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct sysfs_elem_dir {  // 目錄
    struct kobject *kobj;
    /* children list starts here and goes through sd->s_sibling */
    struct sysfs_dirent *children;  // 子節點鏈結的表頭,將所有子節點用 s_sibling 連結起來,依 s_ino 遞減排列
};

struct sysfs_elem_symlink {          // 符號連結
    struct sysfs_dirent *target_sd;  // 指向目標的 sysfs_dirent
};

struct sysfs_elem_attr {     // 普通屬性
    struct attribute *attr;  // 指向屬性
    struct sysfs_open_dirent *open;
};

struct sysfs_elem_bin_attr {         // 二進位屬性
    struct bin_attribute *bin_attr;  // 指向屬性
    struct hlist_head buffers;
};

attribute

普通屬性下有一個 attribute 的成員,用來描述被匯出到 sysfs 時的檔案名稱、存取權限與所屬使用者。

/include/linux/sysfs.h

1
2
3
4
5
6
7
8
9
struct attribute {
    const char *name;      // 屬性名稱,在 sysfs 用作檔名
    struct module *owner;  // 屬性的所有者所屬的 module 實體
    mode_t mode;           // 存取權限
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lock_class_key *key;
    struct lock_class_key skey;
#endif
};

bin_attribute

除了 attribute 的屬性,因為對於二進位檔案用於讀取與寫入的方法不盡相同,所以在資料結構中定義了這些 function pointer。

/include/linux/sysfs.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct bin_attribute {
    struct attribute attr;
    size_t size;    // 二進位資料長度
    void *private;  // 通常指向資料實際的位置
    ssize_t (*read)(struct kobject *, struct bin_attribute *,
                    char *, loff_t, size_t);
    ssize_t (*write)(struct kobject *, struct bin_attribute *,
                     char *, loff_t, size_t);
    int (*mmap)(struct kobject *, struct bin_attribute *attr,
                struct vm_area_struct *vma);
};

sysfs_ops

show 函式會在執行 read 時被呼叫,store 函式會在執行 write 時被呼叫。

/include/linux/sysfs.h

1
2
3
4
struct sysfs_ops {
    ssize_t (*show)(struct kobject *, struct attribute *, char *);
    ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};

sysfs_dirent 與 dentry 的關係

dentry 中的 d_fsdata,指向一個 sysfs_dirent,並透過 s_dir.children 串起整個目錄下的文件。

mount

/fs/sysfs/mount.c

sysfs 的 mount 透過 sysfs_fill_super 函式,建立 root inode 與 dentry,將 dentry 的 d_fsdata 指向靜態宣告的 sysfs_root,填上 superblock 的 root。

file_operations

sysfs_buffer

/fs/sysfs/file.c

為了方便 user space 與 sysfs 交換資料,核心設計了 sysfs_buffer 這個資料結構。

open

/fs/sysfs/file.c

在 open 時,首先透過 file 取得 kobject,指定 ops 為 kobject 內的 ktype 上的 sysfs_ops,檢查寫與 store、檢查讀與 show,沒問題的話就分配一個 sysfs_buffer,最後將 file 指標中的 private_data 關連到 sysfs_buffer。

read

/fs/sysfs/file.c

若是在第一次讀取或是寫入後,就呼叫 show 函式來 refresh sysfs_buffer。最後將 sysfs_buffer 內的資料複製到參數傳入的 buf。

write

/fs/sysfs/file.c

分配一個新的 page,再呼叫 store 函式。

範例

devfs & udev

devfs 出現在 Linux 2.4,devfs 使用動態註冊裝置,提供一個新的,更合理的方式管理那些在 /dev 下的所有 block device 與 character device。

缺陷

  • 裝置名稱不夠彈性
  • major/minor number 無法動態綁定
  • 裝置資訊放在 kernel memory,kernel memory 無法 swap out

在 sysfs 獲得成功後,udev 也在 Linux 2.6 出現,成功取代 devfs。udev 解決了 devfs 的缺陷,它執行在 user space,並透過熱插拔 (/sbin/hotplug) 時產生的uevent,與 sysfs 提供的裝置資訊,動態調整 /dev 下的設備。

Toy Example

Gist

這個範例是一個 kernel module,一開始會建立一個名稱為 dev1 的 device_attribute,傳入 dev1_storedev1_show 的 function pointer,透過核心提供的 API 操作 kobject,最後把 dev1 放在 /sys/device/sysfs_demo/subdir1/subdir2/ 下。如果對 dev1 寫入 0,會將 subdir2 下的符號連結與 subdir3 移除。

參考資料

Linux Kernel v2.6.34 Source Code

Linux Kernel Development 3/e, Robert Love

Professional Linux Kernel Architecture, Wolfgang Mauerer

The sysfs Filesystem, Patrick Moch

udev – A Userspace Implementation of devfs, Greg Kroah-Hartman

comments powered by Disqus