Linux设备模型4(基于Linux6.6)---attribute介绍
一、前情回顾
sysfs是一个基于RAM的文件系统,它和kobject一起,可以将kernel的数据结构导出到用户空间,以文件目录结构的形式,提供对这些数据结构(以及数据结构的属性)的访问支持。
sysfs具备文件系统的所有属性,而本文主要侧重其设备模型的特性,因此不会涉及过多的文件系统实现细节,而只介绍sysfs在Linux设备模型中的作用和使用方法。具体包括:
- sysfs和kobject的关系
- attribute的概念
- sysfs的文件系统操作接口
二、sysfs和Kobject的关系
sysfs
和 kobject
是 Linux 内核中两个密切相关的概念,它们共同提供了内核与用户空间之间的交互接口。kobject
是内核对象的基础数据结构,而 sysfs
则是一个文件系统,它提供了一种通过文件接口访问内核对象的方式。
2.1 kobject(内核对象)
kobject
是 Linux 内核中的一个数据结构,用于表示内核中的对象(例如,设备、驱动程序、内核模块等)。它是内核对象系统的一部分,用来管理内核对象的生命周期、属性和关系。
- 作用:
kobject
代表一个内核对象,并且提供了一个通用的接口,允许内核对象在内核中进行管理和交互。 - 属性管理:
kobject
允许内核对象定义和管理自己的属性,这些属性通常以键值对的形式存储,可以通过sysfs
进行访问和修改。 - 通知机制:
kobject
可以通过kobject_uevent()
函数发送事件,通知用户空间的程序(如udev
)有关对象状态的变化。
2.2 sysfs(系统文件系统)
sysfs
是一个虚拟文件系统,用于在用户空间和内核空间之间进行交互。它暴露了内核对象的属性,并允许用户空间程序通过文件操作(如读取、写入)来访问和修改这些属性。
-
作用:
sysfs
提供了一个统一的接口,使得用户空间程序能够访问内核对象的属性、状态及配置信息。它为内核对象的属性提供了一个文件系统接口,可以通过简单的文件操作(如cat
、echo
等)来获取和设置属性值。 -
目录结构:
sysfs
中的目录结构通常与内核对象的层级结构相对应。例如,设备驱动程序、网络接口、PCI 设备等对象都会在/sys
下有对应的目录,用户可以通过路径访问它们。典型路径示例:
/sys/class/net/eth0/
:表示一个网络设备(例如eth0
)的相关信息。/sys/devices/pci0000:00/0000:00:1f.2/
:表示一个 PCI 设备的信息。
2.3 kobject 与 sysfs 的关系
-
创建和管理:当内核创建一个新的
kobject
时,通常会在sysfs
中为该对象创建一个对应的目录,并通过该目录暴露kobject
的属性。这个过程通过kobject_add()
和sysfs_create_group()
等函数实现。 -
属性的表示:
kobject
本身可以包含多个属性,每个属性对应一个文件。这些文件会出现在sysfs
中的相应目录下。当用户通过sysfs
文件访问属性时,内核会调用kobject
中定义的回调函数来处理这些操作。 -
属性与文件系统的交互:
sysfs
文件系统通过将kobject
中的属性与文件系统中的文件进行映射,允许用户空间通过读取和写入文件来操作内核对象。例如,当用户通过cat /sys/class/net/eth0/operstate
命令读取网络接口状态时,内核会访问对应的kobject
,获取该属性的值并返回。 -
事件通知:当
kobject
的状态发生变化时,内核可以通过kobject_uevent()
向用户空间发送 uevent 事件,通知相关的用户空间程序(如udev
)。sysfs
则可以通过文件系统接口提供有关事件的进一步信息。
2.4 示例
假设有一个内核对象,比如一个网络设备 eth0
。当内核为这个设备创建一个 kobject
时,内核会在 sysfs
中为 eth0
创建一个目录,类似于 /sys/class/net/eth0/
,并将该设备的相关属性(如状态、MAC 地址等)映射到该目录下的文件。用户可以通过访问这些文件来查询或修改设备的属性。
例如:
cat /sys/class/net/eth0/address 会显示该网络接口的 MAC 地址。
echo "up" > /sys/class/net/eth0/operstate 会将网络接口的状态更改为 "up"
2.5 代码说明
在设备模型kobject文章中,有提到过,每一个kobject,都会对应sysfs中的一个目录。因此在将Kobject添加到Kernel时,create_dir接口会调用sysfs文件系统的创建目录接口,创建和kobject对应的目录,相关的代码如下:
lib/kobject.c
static int create_dir(struct kobject *kobj)
{
const struct kobj_type *ktype = get_ktype(kobj);
const struct kobj_ns_type_operations *ops;
int error;
error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
if (error)
return error;
if (ktype) {
error = sysfs_create_groups(kobj, ktype->default_groups);
if (error) {
sysfs_remove_dir(kobj);
return error;
}
}
/*
* @kobj->sd may be deleted by an ancestor going away. Hold an
* extra reference so that it stays until @kobj is gone.
*/
sysfs_get(kobj->sd);
/*
* If @kobj has ns_ops, its children need to be filtered based on
* their namespace tags. Enable namespace support on @kobj->sd.
*/
ops = kobj_child_ns_ops(kobj);
if (ops) {
BUG_ON(!kobj_ns_type_is_valid(ops->type));
BUG_ON(!kobj_ns_type_registered(ops->type));
sysfs_enable_ns(kobj->sd);
}
return 0;
}
fs/sysfs/dir.c
/**
* sysfs_create_dir_ns - create a directory for an object with a namespace tag
* @kobj: object we're creating directory for
* @ns: the namespace tag to use
*/
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
struct kernfs_node *parent, *kn;
kuid_t uid;
kgid_t gid;
if (WARN_ON(!kobj))
return -EINVAL;
if (kobj->parent)
parent = kobj->parent->sd;
else
parent = sysfs_root_kn;
if (!parent)
return -ENOENT;
kobject_get_ownership(kobj, &uid, &gid);
kn = kernfs_create_dir_ns(parent, kobject_name(kobj), 0755, uid, gid,
kobj, ns);
if (IS_ERR(kn)) {
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(parent, kobject_name(kobj));
return PTR_ERR(kn);
}
kobj->sd = kn;
return 0;
}
三、 attribute分析
在 Linux 内核中,attribute(属性) 是与内核对象相关的值,它们可以被用来表示内核对象的状态、配置信息或其他重要数据。每个属性通常会对应一个文件,这些文件会暴露在 sysfs
文件系统中,允许用户空间通过读取或写入这些文件来获取或修改内核对象的状态。属性的功能和作用可以从以下几个方面进行概述:
1. 定义和功能
-
属性的定义:在 Linux 内核中,属性是与
kobject
(内核对象)关联的数据项。每个kobject
可以拥有多个属性,每个属性通常表示一个关键的配置或状态值。属性值可以是数字、字符串、布尔值等数据类型。 -
功能:属性用于描述内核对象的状态或配置,通常通过文件接口暴露在
sysfs
中。用户空间程序可以通过对这些文件的操作(如读取、写入)来查看或修改属性的值。例如,网络设备的状态、磁盘设备的读写权限、温度传感器的温度值等,都可以通过属性来管理。
2. 与 sysfs 的关系
-
sysfs 文件系统:
sysfs
是一个虚拟文件系统,它通过将内核对象的属性暴露为文件来与用户空间进行交互。每个属性通常都会映射到sysfs
中的一个文件,用户可以通过文件系统接口访问这些属性。通过sysfs
,属性变得容易访问、读取和修改,用户可以像操作普通文件一样操作内核对象的属性。 -
创建属性:内核通过
sysfs_create_file()
等函数创建属性文件,并将其与对应的kobject
关联。属性文件会出现在与该kobject
相关的目录下。例如,对于一个网络设备eth0
,可以通过/sys/class/net/eth0/
目录访问该设备的属性,如address
(MAC 地址)、operstate
(接口状态)等。
3. 属性类型和操作
-
只读属性:某些属性可能仅允许读取,而不允许写入。读取这些属性通常会返回当前状态或配置信息。例如,
/sys/class/net/eth0/address
文件会返回网络接口eth0
的 MAC 地址。用户可以读取该文件,但不能直接修改它。 -
可写属性:其他属性可以被修改,通常用于配置或控制设备行为。比如,通过写入
/sys/class/net/eth0/operstate
来改变网络接口的状态。例如,可以将eth0
的状态设置为 "up" 或 "down"。 -
回调函数:当属性的值被读取或修改时,内核会通过回调函数(例如
show()
和store()
函数)来处理相应的操作。回调函数实现了对属性的访问控制和数据转换。
4. 属性的应用场景
-
设备管理:属性广泛应用于设备驱动程序中,表示设备的各种状态和配置。例如,网络设备的速率、硬件错误状态、I/O 控制权限等都可以通过属性来管理。
-
系统监控:系统中的许多监控工具和应用程序(如
top
、iotop
)通过读取sysfs
中的属性来获取系统资源的使用情况。例如,CPU 温度、内存使用情况、硬盘健康状态等都可以通过属性获取。 -
动态配置:用户空间应用程序可以通过写入属性文件来动态调整内核对象的行为或配置。例如,可以通过写入
/sys/class/net/eth0/operstate
文件来启用或禁用网络接口,或者通过修改/sys/class/power_supply/BAT0/charging_enabled
来控制电池的充电状态。
3.1、 attribute的功能概述
在sysfs中,为什么会有attribute的概念呢?其实它是对应kobject而言的,指的是kobject的“属性”。我们知道,
sysfs中的目录描述了kobject,而kobject是特定数据类型变量(如struct device)的体现。因此kobject的属性,就是这些变量的属性。它可以是任何东西,名称、一个内部变量、一个字符串等等。而attribute,在sysfs文件系统中是以文件的形式提供的,即:kobject的所有属性,都在它对应的sysfs目录下以文件的形式呈现。这些文件一般是可读、写的,而kernel中定义了这些属性的模块,会根据用户空间的读写操作,记录和返回这些attribute的值。
总结一下:所谓的attibute,就是内核空间和用户空间进行信息交互的一种方法。例如某个driver定义了一个变量,却希望用户空间程序可以修改该变量,以控制driver的运行行为,那么就可以将该变量以sysfs attribute的形式开放出来。
Linux内核中,attribute分为普通的attribute和二进制attribute,如下:
include/linux/sysfs.h
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
struct bin_attribute {
struct attribute attr;
size_t size;
void *private;
ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
struct vm_area_struct *vma);
};
struct attribute为普通的attribute,使用该attribute生成的sysfs文件,只能用字符串的形式读写(后面会说为什么)。而struct bin_attribute在struct attribute的基础上,增加了read、write等函数,因此它所生成的sysfs文件可以用任何方式读写。
说完基本概念,两个问题:
kernel怎么把attribute变成sysfs中的文件呢?
用户空间对sysfs的文件进行的读写操作,怎么传递给kernel呢?
3.2、 attibute文件的创建
在Linux内核中,attibute文件的创建是由fs/sysfs/file.c中sysfs_create_file接口完成的,该接口的实现没有什么特殊之处,大多是文件系统相关的操作,和设备模型没有太多的关系,这里先略过不提。
include/linux/sysfs.h
static inline int __must_check sysfs_create_file(struct kobject *kobj,
const struct attribute *attr)
{
return sysfs_create_file_ns(kobj, attr, NULL);
}
3.3、 attibute文件的read和write
以下是详细的源码分析,重点介绍 read
和 write
的实现。
1. 核心概念:struct attribute
和 sysfs
sysfs
是一个虚拟文件系统,用于暴露内核对象及其属性给用户空间。每个 attribute
文件通过 struct attribute
结构体定义,文件的读取和写入操作通过 sysfs_ops
结构体中的 show
和 store
函数来实现。
struct attribute
结构体
struct attribute
是一个基础结构体,表示单个 sysfs
属性,它包含属性的名称和文件的访问权限。
struct attribute {
const char *name; // 属性名称(文件名)
umode_t mode; // 文件权限,定义文件的读写权限
};
name
:属性的名称,文件系统中显示的文件名。mode
:文件的权限,通常使用 Linux 标准权限位,如S_IRUGO
(可读),S_IWUSR
(可写)等。
sysfs_ops
结构体
sysfs_ops
结构体定义了如何处理 sysfs
文件的读取和写入操作:
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
文件时,会调用show
函数。该函数将内核对象的数据格式化后写入buf
中,返回值为实际写入的字节数。store
:当向该sysfs
文件写入数据时,会调用store
函数。该函数将用户提供的数据(通过buf
)传递给内核处理,并返回处理的字节数。
2. attribute
文件的 read
和 write
流程
read
操作:当用户空间读取sysfs
文件时,内核会调用该文件对应的show
函数,将内核对象的值写入到用户提供的缓冲区,并返回数据的大小。write
操作:当用户空间写入sysfs
文件时,内核会调用该文件对应的store
函数,并将用户提供的数据传递给内核进行处理。
read
的实现流程:
- 用户通过
cat
或其他工具读取/sys
下的某个属性文件。 sysfs
内部会通过vfs_read
调用文件的f_op->read
操作(即sysfs
的read
操作)。read
调用会触发sysfs_ops.show
函数。show
函数将属性数据格式化并写入缓冲区。- 数据从缓冲区返回给用户空间。
write
的实现流程:
- 用户通过
echo
或其他工具写入数据到/sys
下的某个属性文件。 sysfs
内部会通过vfs_write
调用文件的f_op->write
操作(即sysfs
的write
操作)。write
调用会触发sysfs_ops.store
函数。store
函数将用户提供的数据处理后,更新内核对象的状态。- 返回处理的字节数。
3. 内核源码详细分析:Linux 6.1 内核
在 Linux 6.1 内核中,sysfs
的核心代码位于 fs/sysfs/
目录下。特别是,关于 read
和 write
操作的实现可以在以下源文件中找到:
fs/sysfs/file.c
:这个文件负责sysfs
文件的处理,包括读取和写入操作。
关键函数
-
sysfs_read_file
sysfs_read_file
是处理sysfs
文件读取请求的函数,它会调用show
函数来获取属性的值。
-
ssize_t sysfs_read_file(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct kobject *kobj = filp->f_path.dentry->d_parent->d_inode->i_private; struct attribute *attr = filp->f_path.dentry->d_inode->i_private; ssize_t retval = 0; if (*ppos > 0) return 0; // EOF retval = attr->show(kobj, attr, buf); if (retval >= 0) *ppos = retval; return retval; }
- 这个函数通过
filp->f_path.dentry->d_inode->i_private
获取到与该文件相关的kobject
和attribute
。 - 然后,它调用
attribute->show
来读取数据,将内核数据写入到用户提供的缓冲区buf
中。 - 最后,返回实际读取的字节数。
- 这个函数通过
-
sysfs_write_file
sysfs_write_file
是处理sysfs
文件写入请求的函数,它会调用store
函数来更新内核对象的属性值。
-
ssize_t sysfs_write_file(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { struct kobject *kobj = filp->f_path.dentry->d_parent->d_inode->i_private; struct attribute *attr = filp->f_path.dentry->d_inode->i_private; ssize_t retval; if (*ppos > 0) return -EINVAL; retval = attr->store(kobj, attr, buf, count); if (retval >= 0) *ppos = retval; return retval; }
- 这个函数通过
filp->f_path.dentry->d_inode->i_private
获取到与该文件相关的kobject
和attribute
。 - 然后,它调用
attribute->store
函数,将用户空间的输入数据存储到内核中。 - 最后,返回处理的字节数。
- 这个函数通过
4. show
和 store
函数实现
在实际的设备驱动程序中,show
和 store
函数通常会根据设备的实际状态来定义。这些函数的返回值表示操作的字节数,并且必须遵循一些规范:
-
show
函数:通常会将内核中的数据格式化为字符串,并将其写入到用户提供的缓冲区(buf
)中,返回值是写入的字节数。示例:
-
static ssize_t my_device_show(struct kobject *kobj, struct attribute *attr, char *buf) { return sprintf(buf, "%d\n", my_device_state); }
-
store
函数:通常会解析用户空间传入的数据,并更新内核中的相应状态,返回值是处理的字节数。示例:
-
static ssize_t my_device_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { if (buf[0] == '1') { my_device_state = 1; } else { my_device_state = 0; } return count; }
小结
sysfs
文件的read
和write
操作通过sysfs_read_file
和sysfs_write_file
函数实现,它们分别调用show
和store
函数来读取和写入内核对象的属性。show
函数负责读取内核数据并将其格式化后写入用户缓冲区,而store
函数则负责从用户空间获取数据并更新内核状态。sysfs
提供了一个灵活的机制,使得内核可以通过简单的文件操作与用户空间进行交互。
四、sysfs在设备模型中的应用总结
让我们通过设备模型class.c中有关sysfs的实现,来总结一下sysfs的应用方式。
首先,在class.c中,定义了Class所需的ktype以及sysfs_ops类型的变量,如下:
drivers/base/class.c
static const struct sysfs_ops class_sysfs_ops = {
.show = class_attr_show,
.store = class_attr_store,
};
/* */
static struct kobj_type class_ktype = {
.sysfs_ops = &class_sysfs_ops,
.release = class_release,
.child_ns_type = class_child_ns_type,
};
由前面章节的描述可知,所有class_type的Kobject下面的attribute文件的读写操作,都会交给class_attr_show和class_attr_store两个接口处理。以class_attr_show为例:
drivers/base/class.c
#define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)
static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct class_attribute *class_attr = to_class_attr(attr);
struct class_private *cp = to_class(kobj);
ssize_t ret = -EIO;
if (class_attr->show)
ret = class_attr->show(cp->class, class_attr, buf);
return ret;
}
该接口使用container_of从struct attribute类型的指针中取得一个class模块的自定义指针:struct class_attribute,该指针中包含了class模块自身的show和store接口。下面是struct class_attribute的声明:
include/linux/sysfs.h
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
-
show
函数:当用户通过cat
命令或类似方式读取sysfs
文件时,内核会调用show
函数。该函数负责将数据格式化并返回给用户空间。例如,返回一个字符串表示设备的当前状态。 -
store
函数:当用户通过echo
命令或类似方式向sysfs
文件写入数据时,内核会调用store
函数。该函数负责解析用户写入的数据,并更新内核中的状态或设置。
以 led-class
驱动为例,讲解 attribute
如何使用。
1. 背景:led-class
驱动
led-class
驱动是一个简单的示例,演示如何通过 sysfs
文件操作 LED 设备的状态。它通过 sysfs
提供两个基本操作:读取 LED 的状态(例如,是否开启)和设置 LED 的状态(例如,开启或关闭)。这些操作通常通过 attribute
结构体来实现。
2. attribute
结构体
在 led-class
驱动中,我们使用 attribute
结构体定义每个设备的属性。这些属性会映射到 sysfs
文件系统中的文件。
struct attribute {
const char *name; // 属性名称
umode_t mode; // 属性的访问权限
};
每个 attribute
结构体代表一个单一的属性,例如 LED 的状态、亮度等。
3. 创建 sysfs
属性
以下是一个简单的 led-class
驱动中如何使用 attribute
的示例:
(1) 定义 LED 状态的 attribute
首先,我们定义一个 attribute
来表示 LED 的状态,通常这个属性可以是一个读取操作(如显示当前 LED 是否开启)和一个写入操作(如开启或关闭 LED)。
static struct attribute led_attribute = {
.name = "led_state", // 属性名称,表示 LED 状态
.mode = S_IRUGO | S_IWUSR, // 允许读取和写入
};
name
是该属性的名称,在sysfs
中对应的文件名。mode
表示权限,S_IRUGO
表示所有用户可读,S_IWUSR
表示只有用户(写入者)可写。
(2) 创建对应的 show
和 store
函数
为了处理对 sysfs
文件的读写,我们需要定义 show
和 store
函数。show
函数用于返回 LED 当前状态,store
函数用于设置 LED 的状态。
static ssize_t led_state_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
// 假设我们有一个名为 "led_on" 的标志来表示 LED 是否开启
bool led_on = true; // 假设 LED 当前是开启状态
// 将 LED 状态输出到用户空间,"1" 表示开启,"0" 表示关闭
return sprintf(buf, "%d\n", led_on ? 1 : 0);
}
static ssize_t led_state_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
// 假设 LED 的状态是通过一个变量控制
bool led_on = (buf[0] == '1'); // 如果用户输入 "1",则开启 LED
// 更新 LED 状态(此处仅为示例,实际操作可能涉及硬件控制)
if (led_on)
printk(KERN_INFO "LED turned on\n");
else
printk(KERN_INFO "LED turned off\n");
return count; // 返回写入的字节数
}
led_state_show
用于将 LED 当前状态以字符串形式返回给用户空间。led_state_store
用于将用户空间传入的数据解析并设置 LED 状态。
(3) 关联 attribute
与 sysfs_ops
接下来,我们将 show
和 store
函数与 attribute
关联起来。为此,我们需要创建一个 sysfs_ops
结构体,并将其应用于相应的 attribute
。
static struct sysfs_ops led_sysfs_ops = {
.show = led_state_show,
.store = led_state_store,
};
(4) 创建 kobject
并注册属性
一旦 attribute
和 sysfs_ops
设置好,我们就可以创建一个 kobject
,并将属性添加到它。kobject
是 sysfs
中的基本实体,代表一个设备或子系统。
static struct kobject *led_kobj;
static int __init led_module_init(void)
{
int retval;
// 创建一个 kobject,表示 LED 设备
led_kobj = kobject_create_and_add("led_device", kernel_kobj);
if (!led_kobj)
return -ENOMEM;
// 使用 sysfs_ops 创建属性并添加到 kobject 中
retval = sysfs_create_file(led_kobj, &led_attribute);
if (retval)
kobject_put(led_kobj);
return retval;
}
kobject_create_and_add
创建一个kobject
对象,并将其添加到kernel_kobj
(即内核的根 kobject)中。sysfs_create_file
用于将led_attribute
作为文件创建在led_kobj
下,用户空间可以通过此文件来读取或修改 LED 的状态。
4. 访问 sysfs
属性
通过以上的步骤,我们可以在 /sys
文件系统中看到一个新的文件 /sys/led_device/led_state
。用户空间可以通过读取或写入该文件来操作 LED 状态。例如:
- 读取 LED 状态:
cat /sys/led_device/led_state
如果 LED 开启,输出可能是:
1
- 设置 LED 状态:
echo 1 > /sys/led_device/led_state # 开启 LED
echo 0 > /sys/led_device/led_state # 关闭 LED