0%

Linux驱动之文件系统

seq_file

概述

seq_file只是在普通文件read中加入了内核缓冲的功能,从而实现顺序多次遍历读取大数据量的简单接口, seq_file一般只提供只读接口。

1
2
3
4
5
6
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};

核心功能

seq_file接口可通过 <linux/seq_file.h> 获得。 seq_file包含三个方面:

  • An iterator interface which lets a virtual file implementation step through the objects it is presenting
  • Some utility functions for formatting objects for output without needing to worry about things like output buffers
  • A set of canned file_operations which implement most operations on the virtual file

实现文件在fs/seq_file.c和include/linux/seq_file.h中。

总结

seq_file是为proc文件系统设计的,但不只是proc,在需要创建一个由一系列数据顺序组合而成的虚拟文件或一个较大的虚拟文件时,推荐使用seq_file接口。这正是符合bin_attribute的场景。用户空间通过open、read等函数操作文件时通过系统的VFS调用到内核层,其中file结构体的private指针指向seq_file结构体,然后调用该结构体里的op指针指向的相应函数。

kernfs

概述

在Linux内核中,kernfs提供内核子系统内部虚拟文件系统所需的功能,源于拆分sysfs使用的部分内部逻辑,它通过将有关硬件设备和相关设备驱动程序的信息从内核的设备模型导出到用户空间,提供一组虚拟文件,从而实现独立且可重用的功能。 其他内核子系统可以更容易,更一致地实现自己的文件系统。

数据结构

先整体看下数据结构,如下图:原图

该图展示了各个数据结构之间的关系以及实现方式,kernfs是fs抽象出来公共的属性操作,sysfs基本就是调用该结构的方法来实现,同时kobject的目录和属性文件在用户空间的文件组织也是通过该数据结构实现的。

实现

所有文件系统的实现都是通过file_operations结构体实现具体的操作的,kernfs也是,它同样实现了file_operations结构体相关操作,如下:

1
2
3
4
5
6
7
8
9
10
const struct file_operations kernfs_file_fops = {
.read = kernfs_fop_read,
.write = kernfs_fop_write,
.llseek = generic_file_llseek,
.mmap = kernfs_fop_mmap,
.open = kernfs_fop_open,
.release = kernfs_fop_release,
.poll = kernfs_fop_poll,
.fsync = noop_fsync,
};

这个可以关注下open成员的实现函数kernfs_fop_open,

  • ((struct seq_file *)file->private_data)->private = of
    可以看到kernfs_open_file结构体是挂在seq_file结构体的private指针下的,现在可以梳理下这三者之间的关系,file结构体的private_data指向seq_file,seq_file的private指向kernfs_open_file,这样他们就都联系起来的,当然这里是分析特定文件系统下的实现,在其他情况下可以指向其他数据结构

  • seq_open(file, &kernfs_seq_ops)
    seq_file的实现就使用了kernfs结构,kernfs实现了seq_file的file_operations结构成员,如下:

    1
    2
    3
    4
    5
    6
    static const struct seq_operations kernfs_seq_ops = {
    .start = kernfs_seq_start,
    .next = kernfs_seq_next,
    .stop = kernfs_seq_stop,
    .show = kernfs_seq_show,
    };

    然后通过调用seq_open将该数据结构连入seq_file结构体的op成员中。

sysfs

概述

sysfs是非持久性虚拟文件系统,它提供系统的全局视图,并通过它们的kobiect 显示内核对象的层次结构(拓扑)。每个kobiect显示为目录和目录中的文件,目录代表相关kobject导出的内核变量。这些文件称为属性,可以读取或写入。

对系统上的每个块设备,block都包含一个目录,目录下包含设备上分区的子目录。bus包含系统上注册的总线。dev以原始方式(无层次结构)包含已注册的设备节点,每个节点都是/sys/devices目录中真实设备的符号链接。devices给出系统内设备的拓扑结构视图。firmware显示系统相关的低层子系统树,如ACPI、EFI、OF (DT)。fs列出系统上实际使用的文件系统。kernel保存内核配置选项和状态信息。module是加载的模块列表。

kernel_kobj:对应于/sys/kernel
power_kobj:对应于/sys/power
firmware_kobi :对应于/sys/firmware,导出在drivers/baselirmware.c源文件中
hvpervisor_kobj:对应于/sys/hypervisor,导出在drivers/base hypervisor.c中
fs_kobj:对应于/sys/fs,导出在fs/namespace.c文件中

然而,class/、dev/、devices/是在启动期间由内核源代码内drivers_base/core.c中的devices_init函数创建的,block/在block/genhd.c中创建,bus/ 在drivers/base/bus.c中被创建为kset。

kobject目录被添加到sysfs(使用kobject_add)时,其添加位置取决于kobject的父项。如果其父指针已设置,则它将被添加为父目录内的子目录。如果父指针为NULL,则将其添加为kset->kobj内的子目录。如果其父和kset字段都未设置,它将映射到sysfs内的根目录(/sys)。

挂载sysfs

1
mount -t sysfs sysfs /sys

属性文件使用

属性文件定义

裸属性不包含读取或写入属性值的方法。 鼓励子系统定义自己的属性结构和包装函数,以便为特定对象类型添加和删除属性。 例如驱动模型定义struct device_attribute如下:

1
2
3
4
5
6
7
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};

它还定义了用于定义设备属性的helper程序:

1
2
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

例如做如下声明:

1
static DEVICE_ATTR(foo, S_IWUSR | S_IRUGO, show_foo, store_foo);

相当于定义:

1
2
3
4
5
6
7
8
static struct device_attribute dev_attr_foo = {
.attr = {
.name = "foo",
.mode = S_IWUSR | S_IRUGO,
},
.show = show_foo,
.store = store_foo,
};

子系统定义回调

当子系统定义新的属性类型时,它必须实现一组sysfs操作,以将读取和写入调用转发到属性所有者的show和store 方法。读取或写入文件时,sysfs会调用该类型的适当方法。 然后该方法将通用struct kobject和struct attribute指针转换为适当的指针类型,并调用关联的方法。

阐明:

1
2
#define to_dev(obj) container_of(obj, struct device, kobj)
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

这里通过to_dev_attr宏定义(container_of方法)通过kobject结构体找到子系统或自定义的数据结构指针,这里以device_attribute为例,该数据结构要实现自己的show和store方法。得到device_attribute数据地址后调用该数据的show/store方法。

可以看到kobject实际上实现了一套机制,该机制帮组用户方便的创建文件和删除文件并提供访问文件的方法,而它本身并未实现具体的操作。

一个简单的例子,来看看具体的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);

参考文献

窝窝科技相-设备驱动模型
https://blog.csdn.net/chenying126/article/details/78079942
https://www.kernel.org/doc/html/latest/filesystems/seq_file.html
https://lwn.net/Articles/22359/
《linux设备驱动开发》
《linux内核文档》