sysfs是一個基於ramfs的文件系統,在2.6內核開始引入,用來導出內核對象(kernel object)的數據、屬性到用戶空間。與同樣用於查看內核數據的proc不同,sysfs只關心具有層次結構的設備信息,比如系統中的總線,驅動以及已經加載的模塊等,而諸如PID等信息還是使用proc來管理。本質上,sysfs文件的層次結構就是基於內核中kset與kobject邏輯結構來組織的。從驅動開發的角度,sysfs為我們提供了除了虛擬字符設備的read/write/ioctl和proc系統之外的另外一種通過用戶空間訪問內核數據的方式。想要使用sysfs,編譯內核的時候需要定義CONFIG_SYSFS,可以通過mount -t sysfs sysfs /sys命令來掛載sysfs到"/sys"目錄。本文以Ubuntu15.04(3.19)為例分析。
sysfs的布局體現內核的數據結構,頂層的目錄有
$ls /sys/
block/ bus/ class/ dev/ devices/ firmware/ fs/ hypervisor/ kernel/ module/ power/
每一個目錄都對應內核中的一個kset,每一個kset還會包含一些kobject或其他kset。下面針對常用目錄做一個簡單的介紹
塊設備的存放目錄,這是一個過時的接口,按照sysfs的設計理念,所有的設備都存放在"sys/devices/"同時在"sys/bus/"或(和)"sys/class/"存放相應的符號鏈接,所以現在這個目錄只是為了提高兼容性的設計,裡面的文件已經被全部替換成了符號鏈接,只有在編譯內核的時候勾選CONFIG_SYSFS_DEPRECATED才會有這個目錄,
sys $ll block/
total 0
lrwxrwxrwx 1 root root 0 12月 20 11:29 dm-0 -> ../devices/virtual/block/dm-0/
lrwxrwxrwx 1 root root 0 12月 20 11:29 dm-1 -> ../devices/virtual/block/dm-1/
...
bus包含了系統中所有的總線,比如我的系統當前提供的總線有:
sys $ls bus/
acpi/ container/ i2c/ media/ mipi-dsi/ pci/ pnp/ sdio/ usb/ platform/ scsi/ spi/ ...
每一種總線通常還有兩個子目錄:device和driver,這兩個字目錄分別對應內核中的兩個kset,同時bus本身也對應一個kset,也有自己的kobject和以及(可能)有相應的ktype。我們可以查看相應的kset屬性。
sys $ls bus/platform/
devices/ drivers/ drivers_autoprobe drivers_probe uevent
sys $cat bus/platform/drivers_autoprobe
1
我們可以扒一下3.19的源碼,找到這個屬性
//include/linux/platform_device.h
22 struct platform_device {
...
26 struct device dev;
...
38 };
//include/linux/device.h
731 struct device {
...
744 struct bus_type *bus; /* type of bus device is on */
...
800 };
104 struct bus_type {
...
129 struct subsys_private *p;
...
131 };
//drivers/base/base.h
28 struct subsys_private {
29 struct kset subsys;
30 struct kset *devices_kset;
...
38 unsigned int drivers_autoprobe:1; #Bingo!!!
...
43 };
同時,根據kset的組織形式,平台總線的設備kset鏈接了掛接在平台總線上的所有設備,所以"platform/devices"下應該可以查看到,要注意的事,為了使一個設備在sysfs中只有一個實例,很多目錄都是使用符號鏈接的形式,下面顯示的結果也驗證了這種設計。
sys $ll bus/platform/devices/
lrwxrwxrwx 1 root root 0 12月 19 08:17 ACPI0003:00 -> ../../../devices/pci0000:00/0000:00:14.3/PNP0C09:00/ACPI0003:00/ ...
sys $ll bus/platform/drivers/thinkpad_acpi/
lrwxrwxrwx 1 root root 0 12月 20 20:19 thinkpad_acpi -> ../../../../devices/platform/thinkpad_acpi/
--w------- 1 root root 4096 12月 20 20:18 uevent
--w------- 1 root root 4096 12月 20 20:19 unbind
-r--r--r-- 1 root root 4096 12月 20 20:19 version
...
sys $cat bus/platform/drivers/thinkpad_acpi/version
ThinkPad ACPI Extras v0.25
按照設備功能對系統設備進行分類的結果放在這個目錄,如系統所有輸入設備都會出現在 "/sys/class/input"之下。和sys/bus一樣,sys/class最終的文件都是符號鏈接,這種設備可以保證整個系統中每一個設備都只有一個實例。
sys $l class/
ata_device/ i2c-adapter/ net/ rtc/ spi_master/ gpio/ input/ ...
sys $l class/input/
event0@ event10@ event12@ mouse0@ ...
按照設備號對字符設備和塊設備進行分類的結果放在這個目錄,同樣,文件依然是使用符號鏈接的形式鏈接到"sys/devices/"中的相應文件
sys $ls dev/
block/ char/
sys $ls dev/char/
10:1@ 10:236@ 108:0@ 1:3@ ...
如前所述,所有的設備文件實例都在"sys/devices/"目錄下,
sys $ls devices/
amd_nb/ breakpoint/ cpu/ ibs_fetch/ ibs_op/ LNXSYSTM:00/ pci0000:00/ platform/ ...
sys $ls devices/platform/serial8250/
driver@ driver_override modalias power/ subsystem@ tty/ uevent
sys $cat devices/platform/serial8250/driver_override
(null)
"sys/class/","sys/bus/","sys/devices"是設備開發中最重要的幾個目錄。他們之間的關系可以用下圖表示。
這裡按照設計是用於描述系統中所有文件系統,包括文件系統本身和按文件系統分類存放的已掛載點,但目前只有 fuse,gfs2 等少數文件系統支持 sysfs 接口,一些傳統的虛擬文件系統(VFS)層次控制參數仍然在 sysctl (/proc/sys/fs) 接口中中;
這裡是內核所有可調整參數的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的 slab 分配器等幾項較新的設計在使用它,其它內核可調整參數仍然位於 sysctl (/proc/sys/kernel) 接口中 ;
這裡有系統中所有模塊的信息,不論這些模塊是以內聯(inlined)方式編譯到內核映像文件(vmlinuz)中還是編譯為外部模塊(ko文件),都可能會出現在 /sys/module 中:編譯為外部模塊(ko文件)在加載後會出現對應的/sys/module/
這裡是系統中電源選項,這個目錄下有幾個屬性文件可以用於控制整個機器的電源狀態,如可以向其中寫入控制命令讓機器關機、重啟等。
(對應 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/
對於每一個注冊到內核的kobject,都會在sysfs中創建一個目錄!!!一個目錄!!!一個目錄!!!,目錄名就是kobject.name,這個目錄會從屬於kobject.parent對應的目錄,我們就可以實現在sysfs中用樹狀結構來呈現內核中的kobject。最初的sysfs下頂層目錄下的目錄使用subsystem的結構,在某些書中還會見到這個概念,不過現在已經被kset替代了。在 kobject 下還有一些符號鏈接文件,指向其它的 kobject,這些符號鏈接文件用於組織上面所說的 device, driver, bus_type, class, module 之間的關系。我們再來看看kobject結構:
//include/linux/kobject.h
63 struct kobject {
64 const char *name;
65 struct list_head entry;
66 struct kobject *parent;
67 struct kset *kset;
68 struct kobj_type *ktype;
69 struct kernfs_node *sd;
70 struct kref kref;
...
79 };
//include/linux/kernfs.h
106 struct kernfs_node {
...
125 union {
126 struct kernfs_elem_dir dir;
127 struct kernfs_elem_symlink symlink;
128 struct kernfs_elem_attr attr;
129 };
...
137 };
這其中的symlink就組成了下面的符號鏈接,許許多多這樣的符號鏈接就構成了整個sysfs的符號鏈接體系
sys $ll devices/platform/serial8250/
lrwxrwxrwx 1 root root 0 12月 20 16:17 driver -> ../../../bus/platform/drivers/serial8250/
-rw-r--r-- 1 root root 4096 12月 20 16:17 driver_override
-rw-r--r-- 1 root root 4096 12月 20 16:17 uevent
...
在sysfs中,kobject的屬性(kobject.ktype.attribute)可以以普通文件的形式導出,sysfs還提供了使用文件I/O直接修改內核屬性的機制,這些屬性一般都是ASCII格式的文本文件(ktype.attribute.name)或二進制文件(通常只用在sys/firmware中),為了提高效率,可以將具有同一類型的屬性放置在一個文件中,這樣就可以使用數組進行批量修改,不要在一個文件中使用混合類型,也不要使用多行數據,這些做法會大大降低代碼的可讀性,下面就是一個屬性的定義,可以看到,屬性中並沒有包含讀寫屬性的函數,但是從面向對象的思想看,內核提供了兩個用於讀寫attribute結構的函數。
//include/linux/sysfs.h
29 struct attribute {
30 const char *name;
31 umode_t mode;
32 #ifdef CONFIG_DEBUG_LOCK_ALLOC
33 bool ignore_lockdep:1;
34 struct lock_class_key *key;
35 struct lock_class_key skey;
36 #endif
37 };
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);
void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);
由於一個ktype往往包含很多屬性(default_attr是一個二級指針),當用戶通過sysfs讀寫一個kobject的屬性的時候,會自動回調ktype中的sysfs_ops->show()和sysfops->remove(),所以一個典型的做法是,當我們創建了一個繼承自kobject的子類child後,同時還會創建兩個調用了sysfs_create_file()和sys_remove_file()的讀寫函數,並將它們注冊到struct sysfs_ops中。比如內核使用的struct device就將相應的方法和屬性都封裝在了一起。
//include/linux/device.h
512 /* interface for exporting device attributes */
513 struct device_attribute {
514 struct attribute attr;
515 ssize_t (*show)(struct device *dev, struct device_attribute *attr,
516 char *buf);
517 ssize_t (*store)(struct device *dev, struct device_attribute *attr,
518 const char *buf, size_t count);
519 };
560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);
此外,內核甚至還提供了輔助定義這個屬性的宏
//include/linux/device.h
539 #define DEVICE_ATTR(_name, _mode, _show, _store) \
540 struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
//include/linux/sysfs.h
75 #define __ATTR(_name, _mode, _show, _store) { \
76 .attr = {.name = __stringify(_name), \
77 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
78 .show = _show, \
79 .store = _store, \
80 }
有了這個宏,我們就可以直接通過這個接口創建我們自己的對象
static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);
我們可以追一下源碼,可以發現,我們使用的自動創建設備文件的device_create()就會調用device_create_file()並最終調用sysfs_create_file()
"drivers/base/core.c"
device_create()
└── device_create_vargs()
└── device_create_groups_vargs()
└── device_add()
└── device_create_file()
├── "include/linux/sysfs.h"
└── sysfs_create_file()
#define to_dev(obj) container_of(obj, struct device, kobj)
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct device_attribute *dev_attr = to_dev_attr(attr);
struct device *dev = to_dev(kobj);
ssize_t ret = -EIO;
if (dev_attr->show)
ret = dev_attr->show(dev, dev_attr, buf);
if (ret >= (ssize_t)PAGE_SIZE) {
print_symbol("dev_attr_show: %s returned bad count\n",
(unsigned long)dev_attr->show);
}
return ret;
}
當一個子系統定義了一個新的屬性,它必須執行一組針對的sysfs操作以便對實現對屬性的讀寫,這些讀寫操作通過回調ktype.sysfs_ops.show()和store()。
//include/linux/sysfs.h
184 struct sysfs_ops {
185 ssize_t (*show)(struct kobject *, struct attribute *, char *);
186 ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
187 };
當進行讀寫的時候,sysfs會分配一個PAGE_SIZE大小的buf並把它作為參數傳入這兩個函數,同時,對於每一次對屬性的讀寫操作,sysfs都會調用這兩個函數,所以,調用read系統調用的時候,show()方法應該填滿整個buf,注���一個屬性應該是一個或一組相似的值,所以這種機制並不會浪費很多系統資源。這種機制允許用戶讀取一部分內容並且可以任意的移動文件位置指針,如果用戶空間將文件指針置為0或以0為偏移量調用了pread(),show()會被重新調用並且再填滿一個buf。類似地,調用write()系統調用的時候,sysfs希望第一次傳入的buf是被填滿的,sysfs會在傳入的數據最後自動加NUL,這可以讓諸如sysfs_strqe()一類的函數用起來更安全。當對sysfs執行寫操作時,用戶空間應該首先讀取整個文件的內容,按自己的需求改變其中的一部分並回寫,屬性讀寫操作應該使用同一個buf。
static ssize_t show_name(struct device *dev, struct device_attribute *attr,
char *buf)
{
return scnprintf(buf, PAGE_SIZE, "%s\n", dev->name);
}
static ssize_t store_name(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
snprintf(dev->name, sizeof(dev->name), "%.*s",
(int)min(count, sizeof(dev->name) - 1), buf);
return count;
}
static DEVICE_ATTR(name, S_IRUGO, show_name, store_name);
內核中已經使用sysfs實現了很多的讀寫函數,下面是幾個典型的
/* devices */
/* structure */
//include/linux/device.h)
512 /* interface for exporting device attributes */
513 struct device_attribute {
514 struct attribute attr;
515 ssize_t (*show)(struct device *dev, struct device_attribute *attr,
516 char *buf);
517 ssize_t (*store)(struct device *dev, struct device_attribute *attr,
518 const char *buf, size_t count);
519 };
/* Declaring */
539 #define DEVICE_ATTR(_name, _mode, _show, _store) \
540 struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
/* Creation/Removal */
560 extern int device_create_file(struct device *device,const struct device_attribute *entry);
562 extern void device_remove_file(struct device *dev,const struct device_attribute *attr);
/* bus drivers */
/* Structure */
//include/linux/device.h
44 struct bus_attribute {
45 struct attribute attr;
46 ssize_t (*show)(struct bus_type *bus, char *buf);
47 ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
48 };
/* Declaring */
50 #define BUS_ATTR(_name, _mode, _show, _store) \
51 struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
/* Creation/Removal */
57 extern int __must_check bus_create_file(struct bus_type *,struct bus_attribute *);
59 extern void bus_remove_file(struct bus_type *, struct bus_attribute *);
/* device drivers */
/* Structure */
//include/linux/device.h
265 struct driver_attribute {
266 struct attribute attr;
267 ssize_t (*show)(struct device_driver *driver, char *buf);
268 ssize_t (*store)(struct device_driver *driver, const char *buf,
269 size_t count);
270 };
/* Declaring */
272 #define DRIVER_ATTR(_name, _mode, _show, _store) \
273 struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
/* Creation/Removal */
281 extern int __must_check driver_create_file(struct device_driver *driver,
282 const struct driver_attribute *attr);
283 extern void driver_remove_file(struct device_driver *driver,
284 const struct driver_attribute *attr);