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:
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. 程序源码¶
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