[置顶] 泰晓 RISC-V 实验箱,配套 30+ 讲嵌入式 Linux 系统开发公开课
[置顶] Linux Lab v1.4 升级部分内核到 v6.10,新增泰晓 RISC-V 实验箱支持,新增最小化内核配置支持大幅提升内核编译速度,在单终端内新增多窗口调试功能等Linux Lab 发布 v1.4 正式版,升级部分内核到 v6.10,新增泰晓实验箱支持
[置顶] 泰晓社区近日发布了一款儿童益智版 Linux 系统盘,集成了数十个教育类与益智游戏类开源软件国内首个儿童 Linux 系统来了,既可打字编程学习数理化,还能下棋研究数独提升智力
SYSFS 读写流程简析
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。
所以,创建目录的步骤如下:
- 分配 sysfs_dirent,设置此 sysfs_dirent 类型是 SYSFS_DIR,表示它是一个目录
- 调用 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_inode
、sysfs_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 类型的。
猜你喜欢:
- 我要投稿:发表原创技术文章,收获福利、挚友与行业影响力
- 知识星球:独家 Linux 实战经验与技巧,订阅「Linux知识星球」
- 视频频道:泰晓学院,B 站,发布各类 Linux 视频课
- 开源小店:欢迎光临泰晓科技自营店,购物支持泰晓原创
- 技术交流:Linux 用户技术交流微信群,联系微信号:tinylab
支付宝打赏 ¥9.68元 | 微信打赏 | |
请作者喝杯咖啡吧 |