1. Sysfs

前面章节驱动学习中,我们测试驱动时经常使用/sys目录下文件,我们本章就简单介绍下Sysfs文件系统。

Sysfs是一个内存文件系统(还有configfs,debugfs等),提供了一种用于向用户空间导出内核对象的方法, 展示了kobject对象层次结构的视图,用户可以查看或者设置一些内核参数。kobject层次结构的展现在系统硬件相关的设备、驱动、总线等层级关系,为linux统一设备模型作为管理之用。

1.1. Sysfs的目录结构

相对于我们熟悉的磁盘文件系统,Sysfs文件系统是一种虚拟文件系统,也就是文件系统中文件不对应硬盘上任何文件,存在于内存中。 该文件系统初始化默认挂载到/sys下,可以使用下面命令挂载(也可以挂载到其他地方):

mount -t sysfs sysfs /sys

一般情况下/sys的目录结构:

cat@lubancat:/$ ls -l /sys
total 0
drwxr-xr-x   2 root root 0 Oct 19 09:49 block
drwxr-xr-x  33 root root 0 Oct 19 09:49 bus
drwxr-xr-x  69 root root 0 Oct 19 09:49 class
drwxr-xr-x   4 root root 0 Oct 19 09:49 dev
drwxr-xr-x  11 root root 0 Oct 19 09:49 devices
drwxr-xr-x   3 root root 0 Oct 19 09:49 firmware
drwxr-xr-x   8 root root 0 Oct 19 09:49 fs
drwxr-xr-x  12 root root 0 Oct 19 09:49 kernel
drwxr-xr-x 151 root root 0 Oct 19 09:49 module
drwxr-xr-x   3 root root 0 Oct 19 09:49 power

这些目录是在子系统注册kobject核心的系统启动时刻产生的, 当它们被初始化以后, 它们开始搜寻在各自的目录内注册了的对象。 一个kobject对应一个目录,包含的对象属性对应一个文件,文件只支持 目录普通文件 (文本或二进制文件)和 符号链接文件 三种类型。

1.1.1. block目录

这里是系统中当前所有的块设备所在,按照功能来说放置在/sys/class之下会更合适,但只是由于历史遗留因素而一直存在于/sys/block。 块设备现在是已经移到/sys/class/block, 旧的接口/sys/block为了向后兼容保留存在,现在该目录下的都是链接文件:

cat@lubancat:/sys/block$ ls -l
total 0
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop0 -> ../devices/virtual/block/loop0
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop1 -> ../devices/virtual/block/loop1
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop2 -> ../devices/virtual/block/loop2
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop3 -> ../devices/virtual/block/loop3
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop4 -> ../devices/virtual/block/loop4
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop5 -> ../devices/virtual/block/loop5
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop6 -> ../devices/virtual/block/loop6
lrwxrwxrwx 1 root root 0 Oct 19 10:12 loop7 -> ../devices/virtual/block/loop7
lrwxrwxrwx 1 root root 0 Oct 19 10:12 mmcblk0 -> ../devices/platform/fe310000.sdhci/mmc_host/mmc0/mmc0:0001/block/mmcblk0
lrwxrwxrwx 1 root root 0 Oct 19 10:12 mmcblk0boot0 -> ../devices/platform/fe310000.sdhci/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0boot0
lrwxrwxrwx 1 root root 0 Oct 19 10:12 mmcblk0boot1 -> ../devices/platform/fe310000.sdhci/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0boot1
lrwxrwxrwx 1 root root 0 Oct 19 10:12 mmcblk1 -> ../devices/platform/fe2b0000.dwmmc/mmc_host/mmc1/mmc1:aaaa/block/mmcblk1
lrwxrwxrwx 1 root root 0 Oct 19 10:12 ram0 -> ../devices/virtual/block/ram0
lrwxrwxrwx 1 root root 0 Oct 19 10:12 zram0 -> ../devices/virtual/block/zram0

1.1.2. bus、devices、class目录

这些目录都是与Linux统一设备模型有重要关系,是构成Linux统一设备模型的一部分。

bus目录下的每个子目录都是注册好了的总线类型。这里是设备按照总线类型分层放置的目录结构, 每个子目录(总线类型)下包含两个子目录——devices和drivers文件夹;其中devices下是该总线类型下的所有设备, 而这些设备都是符号链接,它们分别指向真正的设备(/sys/devices/下);如下图bus下的usb总线中的device则是Devices目 录下/pci()/dev 0:10/usb2的符号链接。而drivers下是所有注册在这个总线上的驱动,每个driver子目录下 是一些可以观察和修改的driver参数。

devices目录下是全局设备结构体系,包含所有被发现的注册在各种总线上的各种物理设备。一般来说, 所有的物理设备都按其在总线上的拓扑结构来显示。/sys/devices是内核对系统中所有设备的分层次表达模型, 也是/sys文件系统管理设备的最重要的目录结构。

class目录下则是包含所有注册在kernel里面的设备类型,这是按照设备功能分类的设备模型, 我们知道每种设备都具有自己特定的功能,比如:鼠标的功能是作为人机交互的输入,按照设备功能分类无论它 挂载在哪条总线上都是归类到/sys/class/input下。

下面图片可以直观的看出之间的联系:

设备驱动模型

1.1.3. firmware目录

该目录包含具有固件对象和属性的子目录,关于内核的固件加载和firmware驱动,有兴趣可以自己去了解下。 该目录下:

cat@lubancat:/sys/firmware$ ls -l
total 0
drwxr-xr-x 3 root root      0 Oct 19 11:34 devicetree
-r-------- 1 root root 163840 Oct 19 11:34 fdt

其中的devicetree文件夹,描述加载设备树信息,根节点对应base目录,每一个设备树节点对应一个目录,每个属性对应一个文件。 fdt文件是原始dtb文件,是uboot传给内核的设备树文件,可以使用hexdump -C查看。

1.1.4. fs目录

这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点,描述已注册的文件系统视图, 但目前只有 fuse,ext4 等少数文件系统支持 sysfs 接口,一些传统的虚拟文件系统(VFS)层次控制参数仍然在sysctl(/proc/sys/fs) 接口中。 目录结构如下:

cat@lubancat:/sys/fs$ ls -l
total 0
drwx-----T  2 root root   0 Oct 19 14:41 bpf
drwxr-xr-x 10 root root 280 Oct 19 14:41 cgroup
drwxr-xr-x  5 root root   0 Oct 19 16:58 ext4
drwxr-xr-x  3 root root   0 Oct 19 14:41 fuse
drwxr-x---  2 root root   0 Oct 19 14:41 pstore
drwxr-xr-x  3 root root   0 Oct 19 16:58 xfs

有兴趣可以去了解下cgroup,bpf文件。

1.1.5. kernel目录

该目录是内核所有可调整参数的位置,有些内核可调整参数仍然位于sysctl(/proc/sys/kernel)接口中。

1.1.6. module目录

该目录有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(.ko文件), 都可能会出现在/sys/module目录下。

编译为外部模块(.ko文件)在加载后,会/sys/module/出现对应的模块文件夹,在这个文件夹下会出现一些属性文件和属性目录, 表示此外部模块的一些信息,如版本号、加载状态、所提供的驱动程序等。

编译进内核的模块则只在当它有非0属性的模块参数时会出现对应的/sys/module/, 这些模块的可用参数会出现在/sys/modules/parameters/中, 如:/sys/module/printk/parameters/time 这个可读写参数控制着内联模块printk在打印内核消息时是否加上时间前缀。

cat@lubancat:/sys/module/printk/parameters$ ls -al
total 0
drwxr-xr-x 2 root root    0 Oct 19 14:34 .
drwxr-xr-x 3 root root    0 Oct 18 15:12 ..
-rw-r--r-- 1 root root 4096 Oct 19 14:36 always_kmsg_dump
-rw-r--r-- 1 root root 4096 Oct 19 14:36 console_suspend
-rw-r--r-- 1 root root 4096 Oct 19 14:36 ignore_loglevel
-rw-r--r-- 1 root root 4096 Oct 19 14:36 time
cat@lubancat:/sys/module/printk/parameters$ cat time
Y
cat@lubancat:/sys/module/printk/parameters$ echo 0 | sudo tee time
0
cat@lubancat:/sys/module/printk/parameters$ cat time
N
cat@lubancat:/sys/module/printk/parameters$

所有内联模块的参数也可以由 “.=” 的形式写在内核启动参数上,如启动内核时加上参数 “printk.time=1”与向 “/sys/module/printk/parameters/time” 写入1的效果相同, 没有非0属性参数的内联模块不会出现于此。

1.1.7. power目录

该目录下是系统中电源选项,包含电源管理子系统提供的统一接口文件。 一些属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令进行关机、重启等操作。

1.2. Sysfs使用

sysfs模型的核心是kobject。把kobject对象与目录项紧密连接在一起,将kobject映射到目录上。 由struct Kobject表示,并在<linux/Kobject.h>中定义。struct kobject表示一个内核对象,可能是一个设备,例如在sysfs文件系统中显示为目录的内容。

这个里简单介绍下设备模型核心数据结构kobject:

kobject(include/linux/kobject.h)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct kobject {
    const char              *name;
    struct list_head        entry;
    struct kobject          *parent;
    struct kset             *kset;
    struct kobj_type        *ktype;
    struct kernfs_node      *sd; /* sysfs directory entry */
    struct kref             kref;
    /*....*/
}
  • name:kobj的名字,在/sys下的目录名使用这个创建

  • parent:父kobj节点,在/sys下创建目录会在该父目录下

  • kset: 对kobj进行分类,具有相同属性的kobject在一个集合等

  • ktype: kobj相关属性,对象的操作方法等,该结构体详细参考内核include/linux/kobject.h

  • kref:引用计数,为零时调用析构函数

  • sd:使用kobject_create_and_add函数时,会关联到sysfs_dirent,实现映射。

1.2.1. 创建一个目录

struct kobject * kobject_create_and_add ( const char * name, struct kobject * parent);
  • name:创建kobj的名字

  • parent:指定父kobject,实际就是在那个目录下创建一个目录。比如为kernel_kobj,将在/sys/kernel目录下创建目录,如果为NULL,将在/sys下创建。

1.2.2. 创建一个文件

int sysfs_create_file ( struct kobject *  kobj, const struct attribute * attr);
  • kobj:我们创建的kobject

  • attr:属性描述

更多的函数可以参考内核源码include/linux/sysfs.h文件。

1.3. 简单实验

根据前面小节的函数接口,我们简单编写个驱动程序,在/sys目录下创建一个sysfs_test目录,并在该目录下创建一个文件。

示例配套代码目录:linux_driver/Sysfs

1.3.1. 程序源码

linux_driver/Sysfs/sys_test.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include<linux/sysfs.h>
#include<linux/kobject.h>
#include <linux/err.h>

volatile int test_value = 0;
struct kobject *kobj_test;

/*该函数被调用在sysfs文件被读时*/
static ssize_t sysfs_show(struct kobject *kobj,
                struct kobj_attribute *attr, char *buf)
{
    pr_info("读sysfs!\n");
    return sprintf(buf, "test_value = %d\n", test_value);
}

/* 该函数被调用在sysfs文件被写时*/
static ssize_t sysfs_store(struct kobject *kobj,
                struct kobj_attribute *attr,const char *buf, size_t count)
{
    pr_info("写sysfs!\n");
    sscanf(buf,"%d",&test_value);
    return count;
}

/*使用__ATTR宏初始化sysfs_test_attr结构体,该宏定义在include/linux/sysfs.h*/
struct kobj_attribute sysfs_test_attr = __ATTR(test_value, 0664, sysfs_show, sysfs_store);

/*模块初始化函数*/
static int __init sysfs_test_driver_init(void)
{
    /*创建一个目录在/sys下 */
    kobj_test = kobject_create_and_add("sysfs_test",NULL);

    /*在sysfs_test目录下创建一个文件*/
    if(sysfs_create_file(kobj_test,&sysfs_test_attr.attr)){
            pr_err("创建sysfs文件失败.....\n");
            goto error_sysfs;
    }

    pr_info("驱动模块初始化完成!\n");
    return 0;

error_sysfs:
        kobject_put(kobj_test);
        sysfs_remove_file(kernel_kobj, &sysfs_test_attr.attr);
        return -1;

}

/*模块退出函数*/
static void __exit sysfs_test_driver_exit(void)
{
    kobject_put(kobj_test);
    sysfs_remove_file(kernel_kobj, &sysfs_test_attr.attr);
    pr_info("设备驱动模块移除!\n");
}

module_init(sysfs_test_driver_init);
module_exit(sysfs_test_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedfire");
MODULE_DESCRIPTION("一个简单的使用sysfs的驱动程序");

在实际上层驱动模型中(cdev、bus、device等),都内嵌了object结构体,直接使用,内核在对应目录下创建文件。

1.3.2. 测试

使用交叉编译器编译,生成sys_test.ko模块,传输到板卡,,然后使用命令: sudo insmod sys_test.ko加载内核模块。 之后读写在/sys/sysfs_test下创建的文件:

应用程序测试效果

1.4. 总结

设备驱动程序中需要与用户层的接口,一般有多种方法。可以注册虚拟的字符设备文件,用户层可以编程通过read/write/ioctl交互; 注册proc接口支持,这个是操作系统本身和应用程序之间的通信提供了一个安全的接口,主要显示进程信息和内核各子系统的信息等,现在设备驱动一般不放到这; 注册sysfs属性,直接添加目录和属性文件,用户层可以通过脚本或者命令直接操作。

关于Sysfs目录下的一些文件的详细说明,可以参考内核源码Documentation/ABI/下的文档,这些文档描述了/sys下文件的属性以及使用。

1.5. 参考

内核文档: Documentation/ABI/*

Documentation/filesystems/sysfs.txt

Documentation/kobject.txt

samples/kobject/kobject-example.c