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 提供了這些物件操作:
- reference count
- 管理物件的鏈結串列(集合)
- 集合的 spinlock
- 將物件屬性導出到使用者空間(透過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結構,用來描述檔案類型:
sysfs_elem_dir
為目錄,指向一個 kobjectsysfs_elem_symlink
為符號連結,指向另一個 sysfs_direntsysfs_elem_attr
為普通屬性,指向 attributesysfs_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_store
、dev1_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