泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

基于泰晓RISC-V实验箱的Linux公开课
请稍侯

SYSFS 读写流程简析

Liu Lichao 创作于 2020/08/06

By 法海 of [TinyLab.org][1] Aug 06, 2020

sysfs 文件系统简介

sysfs 是一个内存文件系统,可以将内核数据暴露给用户态。因为它比较简单,且与设备模型有紧密联系。同时,不涉及 pagecache,IO 调度,块设备等概念,是初学 VFS 概念的绝佳目标。

本篇文章介绍:

  • 文件读写在内核中的基本流程
  • inode/dentry 的概念
  • 设备模型在 kobject/ktype/kset 概念

sysfs 目录/文件创建

//创建目录
int sysfs_create_dir(struct kobject * kobj)

Linux 的设备模型中,每一个 kobject 对应 sysfs 文件系统的一个目录,所以入参 kobject 表示创建一个名称为 kobj->name 的目录。其父目录是 kobj->parent,如果 parent 不存在,就在根目录创建。

每一个 sysfs 文件系统内的目录或文件,都对应一个 sysfs_dirent。

所以,创建目录的步骤如下:

  1. 分配 sysfs_dirent,设置此 sysfs_dirent 类型是 SYSFS_DIR,表示它是一个目录
  2. 调用 sysfs_add_one,根据 dirent 的 hash 值,把 sysfs_dirent 添加到 sd->s_parent->s_dir.children.rb_node(红黑树),即添加到父目录的 children 红黑树中

创建文件的 API 是 sysfs_create_file,与 sysfs_create_dir 类似,就是 sysfs_dirent 类型是 SYSFS_KOBJ_ATTR,表示此 sysfs_dirent 是一个属性文件。

可以看到创建目录或文件的核心操作是新建 sys_dirent,并用红黑树组织起来。

sysfs 文件 open

大家都知道 Linux 下一切皆文件。文件的常规操作包括:open/read/write,open/read/write 系统调用后,通过VFS层,最后执行相应文件 inode->i_fop(file_operations)指向的函数指针。

但是从上述的文件创建过程,我们并没有看到文件 inode 的创建,那 sysfs 文件系统中,inode 在哪里创建的呢?

答:在第一次 open 时创建对应文件的 inode。

文件系统每个文件或目录对应一个 inode,inode 描述文件元信息,包括文件属性,文件存储位置,还包括文件操作函数集。Linux 系统使用 dentry 描述文件与 inode 的对应关系,俗称 dcache。如果文件是一个目录,那其对应的 dentry 描述其包含的文件信息。

划重点:

  • inode 描述文件(包括目录)的元信息
  • dentry 描述目录内文件信息与文件对应的 inode 信息

open 操作对应的系统调用是 sys_open,其核心步骤包括:路径解析、执行真正的 open。

路径解析,获取目标文件 dentry/inode 信息

操作 /sys/class/mem/mem/dev 的路径解析步骤:

  • 从 / 开始解析,/ 是 rootfs,在 mount rootfs 的时候,根目录的 inode/dentry 就创建好了。读取 / 的 dentry 信息,寻找子目录 sys 的信息
  • sys 是 sysfs 的挂载点,它的 inode/dentry 信息,在 mount sysfs 的时候创建(称 sysfs 的根目录 inode 为 root_inode)
  • class 是 sysfs 的一个目录,默认没有它的 inode/dentry 信息,路径解析的时候执行 root_inode->i_op->lookup函数指针(指向sysfs_lookup),进行路径查找。查找的本质就是在父目录中查找是否存在我们寻找的文件,如果存在,就创建对应文件的 inode 并初始化(sysfs_get_inodesysfs_init_inode)。同时,创建 dentry,建立文件与 inode 的对应关系,更新 dentry 缓存,以便下次路径查找时直接访问 dentry 缓存即可
  • 依次类推,直到为创建 dev 文件对应的 inode 和 dentry

inode 初始化之后,文件就有了对应的 inode,就有了 inode->i_fop。

执行真正的 open

执行:

vfs_open
    -> do_dentry_open
        --> inode->i_fop->open

inode->i_fop->open 函数指针指向 sysfs_open_file,负责分配 sysfs_buffer,为读写做准备。注意:此 sysfs_buffer 并不是读写数据的缓存,而是向读写操作传递控制信息。

sysfs_open_file 函数调用栈:

sysfs_open_file+0x50/0x298
do_dentry_open+0x1c4/0x2f8
do_last+0x358/0x1028
path_openat+0xb0/0x538
do_filp_open+0x38/0xd0
do_sys_open+0x118/0x218
syscall_common+0x18/0x3c

sysfs 文件读写前奏

文件 open 之后,文件有了如下信息:

  • inode
  • file_operation,对应 inode->i_fop

就像有了屠龙刀与倚天剑,想读就读,想写就写即可,但疏不知,sysfs 文件系统的设计意图还有隐含的东西。

sysfs 文件系统是一种内存文件系统,可以将内核数据暴露给内核态,在它的设计上,sysfs 与 Linux 设备模型有紧密联系,其目录结构与 kobject 的层级结构相同,一个 kobject 在 sysfs 中表现为一个目录,目录内的文件对应的数据结构为 attribute。attribute 结构一般被更高级的结构包含,比如 koj_attribute,device_attribute。

可以把 kobject 理解为内核中“对象”,一般被更高级的数据结构包含,比如 device/driver/bus 等。

创建文件的参考代码:

//相关结构体定义
struct kobj_attribute {
    struct attribute attr;
    ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
    ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count);
};

int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)

static struct kobject *example_kobj;

static struct kobj_attribute bar_attribute =
    __ATTR(bar, 0666, b_show, b_store);

static int __init example_init(void)
{
    //在 /sys/kernel 目录创建 kobject_example 目录
    example_kobj = kobject_create_and_add("kobject_example", kernel_kobj);
    
    //在 kobject_example 目录创建 bar 文件
    //请注意,bar_attribute 的类型与 example_kobj 的 ktype 要配套
    sysfs_create_file(example_kobj, &bar_attribute.attr);
}

读写 sysfs 文件其实是调用对应的 kobj_attribute 的 show/store 函数。

sysfs 文件读写

如上所说,设备模型中一个 kobject 对应一个 sysfs 目录。不同的 kobject 可以属于同一个 ktype(类型),不同的 kobject 可以属于同一个 kset (集合)。

kset 收集了相同属性的 kobject,比如 /sys/class 路径中的 class 表现在内核中就是一个 kset,它可以看成很多 kobject 的容器。属于同一 kset 的 kobject 可以属于不同的 ktype。

ktype 定义了同类型 kobject 的操作函数集,比如 sysfs_ops。

struct kobj_type {
    void (*release)(struct kobject *kobj);
    const struct sysfs_ops *sysfs_ops;
    ...
};  

读一个文件的流程

vfs_read
    -> sysfs_read_file
        --> kobj->ktype->syfs_ops->read
            ----> kobj_attribute->show
  • vfs_read 调用 inode->i_fop->read,即 sysfs_read_file
  • 文件是 attribute,它所在目录对应一个 kobject,它的读写方法在 kobj->ktype->sysfs_ops 指定。read 方法对应 kobj->ktype->syfs_ops->read,比如我们示例代码中的 kobj_attribute,它的读函数是 kobj_attr_show
  • 执行 attribute 对应的 show 函数

调用栈:

b_show+0x28/0x68
kobj_attr_show  //这个函数被编译优化了
sysfs_read_file+0xb0/0x1a0
vfs_read+0xa0/0x1a0
SyS_read+0x64/0x158
handle_sys+0x11c/0x140

写一个文件的流程

与读一个文件流程类似,只不过调用函数为 kobj_attribute 的 store。

注意

读写分析中使用的 kobj_attribute 只是一个例子,内核中还有 device_attribute 等其他的 attribute 定义实现。 使用 device_attribute 其所在目录的 kobject 所属 ktype 应该是 dev_ktype 类型的。



Read Related:

Read Latest: