Linux SysFs文件系统分析4(基于Linux6.6)---sysfs文件处理介绍
一、前情回顾
在开始介绍文件创建之前,将之前说明的kobject、sysfs_dirent、sysfs_open_dirent、sysfs_buffer、sysfs_ops、attribute、file、dentry等结构体关联的简略图提供出来,而sysfs文件系统进行目录及文件的创建,就是完成这些结构体之间的关联操作。
如下图所示,主要涉及linux内核三大子模块,分别为VFS模块、进程处理模块(该模块通过文件描述符与sysfs、VFS关联)、sysfs模块。
这三个模块的关联说明如下:
- VFS模块主要通过struct dentry结构体变量,获取sysfs的sysfs_dirent结构体变量,而该变量为sysfs中目录与文件创建的关键变量,sysfs中以该类型变量表示目录与文件。
- 而进程处理模块则通过文件描述符变量与sysfs中的sysfs_buffer变量关联,通过该变量可实现对属性文件的读写操作。
- sysfs模块中主要涉及两部分,其中
- kobject则对应目录部分,包括了kobj sysfs diernt类型变量以及kobject对应的处理方式kobj_type(提供kobject的store/show与release接口等);
- kobject属性在sysfs中被抽象为普通文件,而该普通文件也包含了sysfs_dirent、sysfs_open_dirent、sysfs attribute、 具体模块属性xxx_attribute、sysfs_buffer(主要与文件描述符关联,并表示每一个通过进程打开该属性文件相关的私有信息:如pos等)
通过该图,可帮助快速建立sysfs 目录与文件相关的信息,其实目录与文件的创建流程也就是实现下图这些结构体变量之间的关联操作。在linux内核代码阅读中,只要把各模块结构体变量之间的关联理清,则基本上即熟悉了该模块相关的实现流程。
二、sysfs文件创建相关接口说明
sysfs在内核层提供了创建sysfs文件的接口,供kobject相关的接口调用,从而实现内核层创建sysfs文件的功能。
针对sysfs文件系统创建文件以及文件操作相关的系统调用,均定义在fs/sysfs/file.c中。针对这个接口文件的创建,其接口为sysfs_add_file(该接口通过调用sysfs_add_file_mode实现文件的创建操作)。分析下sysfs_add_file_mode这个接口,该接口的流程图如下所示。
该接口的功能如下:
1.通过父节点的kobject,获取namespace;
2.设置创建文件的类型(bin文件或者attr文件);
3.调用sysfs_new_dirent创建一个新的sysfs_dirent变量,并插入至其父节点sd的红黑节点中(若已存在相同namespace、相同名称的文件,则返回失败)。
文件的创建流程与目录的创建类型均是调用sysfs_new_dirent与sysfs_add_one实现sd的创建与插入节点操作。
在创建文件时,会让上层调用接口传递struct attribute类型的变量,并赋值给sd->s_attr.attr,而该属性变量即可实现属性文件的读写操作(通过container_of,获取包含该成员struct attribute的变量,从而获取该变量的show/store)。
该接口为最里层的实现,而在外层,针对bin文件与attr文件,又分别进行了封装,这两个接口的定义如下:
fs/sysfs/file.c
static inline int __must_check sysfs_create_file(struct kobject *kobj,
const struct attribute *attr)
{
return sysfs_create_file_ns(kobj, attr, NULL);
}
/**
* sysfs_create_file_ns - create an attribute file for an object with custom ns
* @kobj: object we're creating for
* @attr: attribute descriptor
* @ns: namespace the new file should belong to
*/
int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr,
const void *ns)
{
kuid_t uid;
kgid_t gid;
if (WARN_ON(!kobj || !kobj->sd || !attr))
return -EINVAL;
kobject_get_ownership(kobj, &uid, &gid);
return sysfs_add_file_mode_ns(kobj->sd, attr, attr->mode, uid, gid, ns);
}
EXPORT_SYMBOL_GPL(sysfs_create_file_ns);
/**
* sysfs_create_bin_file - create binary file for object.
* @kobj: object.
* @attr: attribute descriptor.
*/
int sysfs_create_bin_file(struct kobject *kobj,
const struct bin_attribute *attr)
{
kuid_t uid;
kgid_t gid;
if (WARN_ON(!kobj || !kobj->sd || !attr))
return -EINVAL;
kobject_get_ownership(kobj, &uid, &gid);
return sysfs_add_bin_file_mode_ns(kobj->sd, attr, attr->attr.mode, uid,
gid, NULL);
}
EXPORT_SYMBOL_GPL(sysfs_create_bin_file);
而针对属性文件创建接口sysfs_ceate_file,内核的其他模块,则对该接口进行封装分别实现创建不同文件的功能,比如class_create_file、bus_create_file接口等,这些接口其实也就是根据自定义的属性变量不同来分别的,如bus_create_file则根据结构体变量struct bus_attribute ,实现bus属性文件私有的store/show接口,而class_create_file则根据结构体变量struct class_attribute来实现class下属性文件的store/show接口
include/linux/device/bus.h
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(const struct bus_type *bus, char *buf);
ssize_t (*store)(const struct bus_type *bus, const char *buf, size_t count);
};
include/linux/device/class.h
struct class_attribute {
struct attribute attr;
ssize_t (*show)(const struct class *class, const struct class_attribute *attr,
char *buf);
ssize_t (*store)(const struct class *class, const struct class_attribute *attr,
const char *buf, size_t count);
};
在sysfs文件系统中,针对属性文件,其文件操作接口的定义如上所示,主要实现open、read、write、poll接口。
在 sysfs
文件系统中,属性文件通过文件操作接口提供对内核属性的访问。对于每个 sysfs
属性文件,内核会根据其类型和功能,定义相应的 file_operations
结构体。这些接口通常包括 open
、read
、write
和 poll
等操作。这些操作的定义允许用户空间与内核空间的交互。下面详细介绍这些操作接口的作用及如何在内核中实现它们。
2.1、接口说明
1. open
接口
open
操作会在用户进程打开 sysfs
文件时被调用。通常情况下,sysfs
的属性文件不需要特别的处理,但是对于某些特定的属性文件,可能需要执行一些初始化操作。
-
原型:
-
int (*open) (struct inode *inode, struct file *file);
-
参数:
inode
:指向文件的 inode 结构,包含该文件的元数据。file
:指向file
结构,表示打开的文件。
-
功能:
- 对于
sysfs
文件,open
函数会在文件被打开时执行,通常是做一些资源的初始化,或者确保属性文件的可用性。 sysfs
中的open
操作一般不会做任何复杂的逻辑,因为sysfs
文件的内容直接由内核对象的属性值提供。
- 对于
-
示例: 通常,
sysfs
属性文件不需要定义专门的open
操作,默认的open
操作就可以工作。但是如果需要特殊处理,可以通过为属性定义open
操作。
2. read
接口
read
操作会在用户空间读取 sysfs
文件时被调用。它允许用户空间获取内核对象的属性值。
-
原型:
-
ssize_t (*read) (struct file *file, char __user *buf, size_t count, loff_t *ppos);
-
参数:
file
:指向file
结构,表示当前打开的文件。buf
:用户空间缓冲区,用于存储从sysfs
读取的数据。count
:要读取的字节数。ppos
:指向文件偏移量的指针,通常用于支持多次读取同一文件时更新偏移量。
-
功能:
- 在
read
操作中,内核会将与该属性文件相关联的内核对象的值读取到用户空间缓冲区。内核可以根据需要格式化输出或直接返回属性值。
- 在
-
示例: 对于
sysfs
属性文件,read
操作通常是通过show
函数来实现的。例如,读取某个设备的属性时,可以将设备的状态或配置值写入buf
中,并返回读取的字节数。
-
ssize_t my_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { return sprintf(buf, "%d\n", my_device->some_attribute); }
3. write
接口
write
操作会在用户空间写入数据到 sysfs
文件时被调用。它允许用户空间修改内核对象的属性值。
-
原型:
-
ssize_t (*write) (struct file *file, const char __user *buf, size_t count, loff_t *ppos);
-
参数:
file
:指向file
结构,表示当前打开的文件。buf
:包含用户空间写入的数据。count
:要写入的字节数。ppos
:指向文件偏移量的指针。
-
功能:
- 在
write
操作中,内核会处理来自用户空间的数据并修改相应的内核对象的属性。这个操作通常用于更新设备的配置或状态信息。 write
操作需要解析buf
中的数据,并根据这些数据更新内核中的相应状态。
- 在
-
示例: 对于
sysfs
属性文件,write
操作通常是通过store
函数来实现的。例如,写入某个设备的属性时,可以根据传入的值来设置设备的配置。
-
ssize_t my_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { int value; if (sscanf(buf, "%d", &value) != 1) return -EINVAL; my_device->some_attribute = value; return count; }
4. poll
接口
poll
操作用于检查 sysfs
文件是否可以进行读写操作,通常用于支持非阻塞的文件操作。对于 sysfs
文件而言,这个接口并不是非常常见,因为 sysfs
通常是同步操作,通常不涉及像设备文件那样的阻塞行为。然而,在某些需要支持事件驱动或轮询的场景中,poll
可能会被用来检查文件状态。
-
原型:
-
unsigned int (*poll) (struct file *file, struct poll_table_struct *pts);
-
参数:
file
:指向file
结构,表示当前打开的文件。pts
:用于记录轮询信息的结构体。
-
功能:
- 在
poll
操作中,内核会检查文件的状态(如可读、可写、错误等),并将相应的事件通知给poll_table
。 - 对于
sysfs
文件,通常并不需要实现poll
操作,除非文件涉及到异步事件或数据的变化。
- 在
2.2、sysfs
文件的 open
操作
将详细介绍 Linux 6.x 中如何实现 sysfs
文件的 open
操作。
1. sysfs
文件系统的实现框架
sysfs
是 Linux 内核中的虚拟文件系统,它为内核对象(如设备、驱动等)提供了一个文件系统接口,允许用户空间通过文件操作接口(例如 open
、read
、write
等)访问内核数据。sysfs
文件系统的核心结构体是 sysfs_ops
和 sysfs_attr
,它们控制着文件操作的行为。
在 Linux 6.x 中,sysfs
文件操作的实现基本上是通过 sysfs_file_operations
结构体来管理的,该结构体定义了对 sysfs
文件的操作行为,包括 open
、read
、write
等操作。
2. open
操作的流程
在 Linux 6.x 中,当一个 sysfs
文件被打开时,通常会调用 sysfs
的 open
操作,过程如下:
sysfs
文件的 open
操作
sysfs
文件的 open
操作通常会涉及一个自定义的 sysfs_file_operations
结构体,该结构体会为每个特定的文件操作(如 open
、read
、write
等)定义具体的处理函数。在默认情况下,sysfs
属性文件的 open
操作的实现逻辑会涉及到如下几个步骤:
- 权限检查:检查当前进程是否具有访问该文件的权限,通常是基于文件的
mode
和进程的用户权限进行的检查。 - 初始化文件私有数据:例如,如果文件与设备或某个内核对象相关,可能需要将设备指针或相关数据结构与文件绑定,以便后续操作。
- 检查文件的状态:确认文件是否处于有效状态,确保可以进行进一步的读写操作。
具体实现
在 Linux 6.x 中,sysfs
文件的 open
实际上是通过以下函数进行处理的:
int sysfs_open_file(struct inode *inode, struct file *file)
{
struct sysfs_dirent *sd;
int ret;
/* 通过 inode 获取 sysfs 目录项 */
sd = sysfs_get_dirent(inode);
if (!sd)
return -ENOENT;
/* 权限检查:确保用户有权限访问该文件 */
ret = sysfs_permission(sd, file);
if (ret)
return ret;
/* 初始化文件私有数据(通常是设备或内核对象的指针) */
file->private_data = sd;
/* 可扩展的逻辑,完成文件打开后的初始化工作 */
return 0;
}
3. 关键的内部函数和结构
sysfs_get_dirent
sysfs_get_dirent()
函数从 inode
中获取 sysfs
的目录项(sysfs_dirent
),这是一个表示文件系统中文件的结构体。通过该结构体可以访问该文件的相关元数据和操作。
权限检查
权限检查通常通过 sysfs_permission()
函数来实现,它会检查当前进程是否具备访问该 sysfs
文件的适当权限。如果没有权限访问,将返回错误。
file->private_data
file->private_data
是 struct file
中的一个成员,用于存储文件的私有数据。对 sysfs
文件来说,private_data
一般会被用来存储指向 sysfs_dirent
或相关内核对象的指针,这样后续的文件操作(如读取或写入)可以访问到相应的内核数据。
返回值
如果 open
操作成功,函数返回 0
或 0
(表示成功),否则返回负数的错误码。例如,如果没有权限访问该文件,则会返回 -EPERM
,如果文件不存在,则返回 -ENOENT
。
分析下属性文件的open接口sysfs_open_file,该接口的流程图如下:
主要实现的功能即建立上述结构体之间的关联:
- 通过父对象的ktype->sysfs_ops获取该kobject对象的store/show接口,如针对bus目录,其sysfs_ops的定义如下:
drivers/base/bus.c
static const struct sysfs_ops bus_sysfs_ops = {
.show = bus_attr_show,
.store = bus_attr_store,
};
- 做合法性检查,即是否正确拥有对该文件的操作权限;
- 创建sysfs_buffer类型的变量,作为本文件描述符的私有变量,存储该文件的store/show接口以及文件操作信息(pos、event、是否需要更新读信息标志needs_read_fill等)
- 获取/创建sysfs_open_dirent类型变量,并将上述3中创建的sysfs_buffer变量插入其buffer链表中。
2.3、以bus为例
以bus为例,在创建bus类型的kobject时,设置kobject->ktype->sys_ops为bus_sysfs_ops ,bus_sysfs_ops 中的show、store定义如下,这两个接口通过传递struct attribute *attr类型变量,通过container_of接口获取struct bus_attribute类型的变量,然后再根据获取的struct bus_attribute类型的变量,再调用属性的文件的store、show接口,实现对属性文件中各属性的读写操作。
drivers/base/bus.c
/*
* sysfs bindings for buses
*/
static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->show)
ret = bus_attr->show(subsys_priv->bus, buf);
return ret;
}
static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct bus_attribute *bus_attr = to_bus_attr(attr);
struct subsys_private *subsys_priv = to_subsys_private(kobj);
ssize_t ret = 0;
if (bus_attr->store)
ret = bus_attr->store(subsys_priv->bus, buf, count);
return ret;
}
创建bus属性文件时,只要创建struct bus_attribute类型的变量,并通过设置该struct bus_attribute变量的show、store接口,然后在创建属性文件时,将bus_attribute.attr传递给文件对应sysfs_dirent->s_attr.attr,然后在bus_attr_store/bus_attr_show接口中,即可根据sysfs_dirent->s_attr.attr,通过container_of获取到定义的struct bus_attribute类型的变量,即可实现该属性文件的读写接口的定义。内核中各模块通过bus_add_attrs接口,实现创建bus相关的属性文件。
2.3、sysfs的read系统调用
Linux 6.x 的 sysfs
文件操作
在 Linux 6.x 中,sysfs
文件的操作并不像以前那样直接依赖于 sysfs_file_operations
或类似的接口。现在,sysfs
文件的读写操作是通过 sysfs_ops
和 sysfs_attribute
等机制进行处理的。
1. sysfs
文件操作的基本结构
在 Linux 6.x 内核中,sysfs
文件的操作(如读取)是通过一个与文件相关联的 sysfs_ops
结构体来定义的。sysfs_ops
是一个包含 show
和 store
操作的结构体,允许内核处理文件的读取和写入。
sysfs_ops
结构体的定义如下:
struct sysfs_ops {
ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buf);
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count);
};
show
函数用于实现sysfs
文件的读取。store
函数用于实现sysfs
文件的写入。
2. sysfs
文件读取过程
在 Linux 6.x 内核中,sysfs
文件的读取操作实际上是由内核的 vfs
层和相关的 kobject
和 attribute
机制协同完成的。
具体来说,读取一个 sysfs
文件时,内核会调用一个通用的文件操作函数,而不是像旧版本中那样直接调用 sysfs_read_file
。以下是读取过程的大致描述:
- 用户通过
open()
系统调用打开一个sysfs
文件。 - 内核根据文件的路径定位到相应的
sysfs
目录项,并与一个kobject
绑定。 - 当用户调用
read()
系统调用时,内核会通过kobject
查找与该文件属性相关联的show
函数。 show
函数会被调用以获取属性值,并将其存入一个缓冲区。- 数据通过
copy_to_user
被传送到用户空间。
在这一过程中,内核并没有直接暴露出类似 sysfs_read_file
的单独函数,而是通过更底层的 sysfs
操作机制来处理。
3. sysfs
操作的实现
假设我们有一个 sysfs
文件属性 status
,它代表一个设备的状态,并且我们想要为该文件实现 show
操作。在 Linux 6.x 中,show
函数的实现大致如下:
ssize_t my_device_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct my_device *dev = container_of(kobj, struct my_device, kobj);
return scnprintf(buf, PAGE_SIZE, "%d\n", dev->status);
}
该 show
函数会将设备的 status
状态格式化并复制到提供的缓冲区 buf
。
然后,该函数会通过 vfs_read
等内核机制将数据传递到用户空间。
4. sysfs
文件操作的变动
随着 Linux 内核版本的迭代,特别是从 Linux 5.x 到 6.x,sysfs
文件的操作变得更加模块化和抽象化。具体来说:
-
不再有
sysfs_read_file
:Linux 6.x 的实现不再暴露像sysfs_read_file
这样的显式函数,而是通过一组内核操作函数(例如vfs_read
)和sysfs_ops
结构体来处理读取操作。 -
更多依赖
sysfs_ops
结构体:读取和写入操作被更加封装在sysfs_ops
中,而不需要显式的sysfs_file_operations
结构。
5. 如何读取 sysfs
文件(内核层)
在内核层,读取 sysfs
文件的过程通常通过以下机制完成:
vfs_read
:当用户调用read()
系统调用时,内核会通过vfs_read()
来处理文件的读取操作。在此过程中,内核会根据文件的kobject
结构体找到与之关联的show
操作,并执行数据的读取。
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct sysfs_attribute *attr = to_sysfs_attr(file->f_inode);
ssize_t len;
// 调用相应的 show 函数
len = attr->show(attr->kobj, &attr->attr, buf);
if (len < 0)
return len;
*ppos += len;
return len;
}
6. 总结
Linux 6.x 的 sysfs
实现已经摒弃了像 sysfs_read_file
这样的显式函数,而是通过 sysfs_ops
和相关机制来处理文件的读写操作。在用户层,仍然可以使用标准的文件操作(如 open
、read
)来访问 sysfs
文件,而在内核层,通过更细粒度的操作来管理这些文件。