Skip to content

LV100-属性文件简介

一、属性文件是什么?

1. 属性文件简介

属性(attribute)文件就是就是内核空间和用户空间进行信息交互的一种方法。它是对应 kobject 而言的,指的是 kobject 的“属性”。

我们知道每一个注册的 kobject 都会在 /sys 中以目录的形式呈现,也就是 bus 等数据结构可以利用嵌入 kobject 可以使它显示在 /sys 中。而 attribute 又会以文件的形式出现在目录中, 即 kobject 的所有属性,都在它对应的 sysfs 目录下以文件的形式呈现。通过这些属性文件, 我们就能够获取/修改内核中的变量,字符串等信息。

例如某个 driver 中定义了一个变量,希望用户空间程序可以修改该变量,以控制 driver 的运行行为,那么就可以将该变量以 sysfs attribute 的形式开放出来。

Linux 内核中,attribute 分为普通的 attribute 和二进制 attribute。

2. 数据结构

会接触到的属性相关的结构体大概有以下几个。

2.1 struct attribute

struct attribute 定义如下:

c
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
};

其中 name 表示文件名称,mode 表示文件模式(也就是权限,比如 0644、0666 等)。其他几个是调试用的。

2.2 struct bin_attribute

struct bin_attribute 定义如下:

c
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);
};

2.3 struct attribute_group

struct attribute_group 定义如下:

c
struct attribute_group {
	const char		*name;
	umode_t			(*is_visible)(struct kobject *,
					      struct attribute *, int);
	umode_t			(*is_bin_visible)(struct kobject *,
						  struct bin_attribute *, int);
	struct attribute	**attrs;
	struct bin_attribute	**bin_attrs;
};

2.4 struct kobj_attribute

struct kobj_attribute 定义如下:

c
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);
};

这个结构体包含一个 attr 成员,它就是前面的属性结构体,后面的 show 和 store 函数指针就是为每个属性指定不同的回调函数的。

3. 相关 API

3.1 sysfs_create_file()

sysfs_create_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);
}

3.2 sysfs_create_files()

sysfs_create_files() 函数定义如下:

c
int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr)
{
	int err = 0;
	int i;

	for (i = 0; ptr[i] && !err; i++)
		err = sysfs_create_file(kobj, ptr[i]);
	if (err)
		while (--i >= 0)
			sysfs_remove_file(kobj, ptr[i]);
	return err;
}
EXPORT_SYMBOL_GPL(sysfs_create_files);

3.3 sysfs_remove_file()

sysfs_remove_file() 函数定义如下:

c
static inline void sysfs_remove_file(struct kobject *kobj,
				     const struct attribute *attr)
{
	sysfs_remove_file_ns(kobj, attr, NULL);
}

3.4 sysfs_remove_files()

sysfs_remove_files() 函数定义如下:

c
int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr)
{
	int err = 0;
	int i;

	for (i = 0; ptr[i] && !err; i++)
		err = sysfs_create_file(kobj, ptr[i]);
	if (err)
		while (--i >= 0)
			sysfs_remove_file(kobj, ptr[i]);
	return err;
}
EXPORT_SYMBOL_GPL(sysfs_create_files);

3.5 sysfs_create_group()

sysfs_create_group() 函数定义如下:

c
int sysfs_create_group(struct kobject *kobj,
		       const struct attribute_group *grp)
{
	return internal_create_group(kobj, 0, grp);
}
EXPORT_SYMBOL_GPL(sysfs_create_group);

用于在 sysfs 中创建一个组(group)。组是一组相关的属性文件的集合,可以将它们放在同一个目录下提供更好的组织性和可读性。此函数有俩个参数,分别为如下所示:

  • kobj: 指向包含目标组的 kobject 的指针。
  • grp: 指向 struct attribute_group 结构体的指针,该结构体定义了组中的属性文件。

struct attribute_group 定义如下:

c
struct attribute_group {
    // 组的名称,将用作 sysfs 目录的名称。
	const char		*name;
    // 可选的回调函数,用于决定每个属性文件是否可见。如果为 NULL,则所有属性文件都可见。
	umode_t			(*is_visible)(struct kobject *,
					      struct attribute *, int);
	umode_t			(*is_bin_visible)(struct kobject *,
						  struct bin_attribute *, int);
    // 指向属性文件数组的指针
	struct attribute	**attrs; 
	struct bin_attribute	**bin_attrs;
};

3.6 __ATTR()

__ATTR() 是一个宏,定义如下:

c
#define __ATTR(_name, _mode, _show, _store) {				\
	.attr = {.name = __stringify(_name),				\
		 .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },		\
	.show	= _show,						\
	.store	= _store,						\
}

该宏可以用于简化属性文件(attribute file)的定义。它用于定义一个 struct attribute 结构体,同时设置该属性的名称、权限以及关联的 showstore 操作函数。

  • _name:属性的名称,将出现在 /sys 文件系统中的文件名,这个名称可以不带 " ",里面会自己处理成字符串。
  • _mode:属性文件的权限模式,通常是八进制数,比如 0644、0666 等,表示文件的读写权限。
  • _show:属性文件的读取函数指针,当用户读取该文件时将调用此函数。
  • _store:属性文件的写入函数指针,当用户写入该文件时将调用此函数。

4. 属性文件的典型用例

一个典型的例子是 /sys/class/net/eth0/ 目录下的属性文件,它们允许用户查看或配置网络接口 eth0 的一些属性,比如 speed、mtu、operstate 等。

image-20250107095722933

我们可以用以下命令读取相关信息:

shell
cat /sys/class/net/eth0/speed       # 读取速度
echo 1500 > /sys/class/net/eth0/mtu # 配置 MTU

二、属性文件的创建与调用逻辑

1. sysfs_create_file()

接下来看一下属性文件怎么被创建的,我们从 sysfs_create_file() 函数入手,从这个函数入手,看一下是怎么个逻辑:

image-20250107104840696

可以看到,我们创建 kobject 的时候,会调用到 sysfs_create_file() 函数来创建属性文件。

1.1 sysfs_create_file_ns()

sysfs_create_file() 函数内部调用了 sysfs_create_file_ns(),我们看一下 sysfs_create_file_ns() 函数:

c
int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr,
			 const void *ns)
{
	kuid_t uid;
	kgid_t gid;

	BUG_ON(!kobj || !kobj->sd || !attr);

	kobject_get_ownership(kobj, &uid, &gid);
	return sysfs_add_file_mode_ns(kobj->sd, attr, false, attr->mode,
				      uid, gid, ns);

}
EXPORT_SYMBOL_GPL(sysfs_create_file_ns);

这里一共调用了两个函数,我们主要关注这个 sysfs_add_file_mode_ns() 函数。

1.2 sysfs_add_file_mode_ns()

sysfs_add_file_mode_ns() 函数定义如下:

c
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
			   const struct attribute *attr, bool is_bin,
			   umode_t mode, kuid_t uid, kgid_t gid, const void *ns)
{
	struct lock_class_key *key = NULL;
	const struct kernfs_ops *ops;
	struct kernfs_node *kn;
	loff_t size;

	if (!is_bin) {
		struct kobject *kobj = parent->priv;
		const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;

		/* every kobject with an attribute needs a ktype assigned */
		if (WARN(!sysfs_ops, KERN_ERR
			 "missing sysfs attribute operations for kobject: %s\n",
			 kobject_name(kobj)))
			return -EINVAL;

		if (sysfs_ops->show && sysfs_ops->store) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_rw;
			else
				ops = &sysfs_file_kfops_rw;
		} else if (sysfs_ops->show) {
			//......
		} else if (sysfs_ops->store) {
			// ......
		} else
			ops = &sysfs_file_kfops_empty;

		size = PAGE_SIZE;
	} else {
		//......
	}

	//......
	return 0;
}

这里大概得逻辑就是从 kobj-> ktype 获取 sysfs_ops ,这个成员是 struct sysfs_ops 类型,它的定义如下:

c
struct sysfs_ops {
	ssize_t	(*show)(struct kobject *, struct attribute *, char *);
	ssize_t	(*store)(struct kobject *, struct attribute *, const char *, size_t);
};

从后面的一堆 if 中,可以知道,当我们定义了这两个函数的时候,ops 指针就会指向 sysfs_prealloc_kfops_rw 或者 sysfs_file_kfops_rw,具体指向哪一个,受到 mode 的影响,这个 mode 其实就是创建 kobject 的时候的属性的 mode 成员,表示读写权限,我们一般想要读写的话,这里会赋值为 0664,这是一个 8 进制数,SYSFS_PREALLOC 为 010000,这也是八进制,所以这里&运算是不成立的,所以最后指向的是 sysfs_file_kfops_rw,这个变量定义如下:

c
static const struct kernfs_ops sysfs_file_kfops_rw = {
	.seq_show	= sysfs_kf_seq_show,
	.write		= sysfs_kf_write,
};

这里后面的逻辑我就没有去详细分析了,但是大概就是,当对属性文件进行读写的时候就会去调用 sysfs_file_kfops_rw 中的这两个函数,这里相当于是告诉读写的时候要用哪两个函数,就像驱动注册的时候我们会提供操作函数集一样。

1.3 总结

上面大概就是属性文件的创建过程,其实并没有分析的很完整,但是其实也没啥必要,大概知道个流程就行了,再分析就涉及很多东西了,这里就不花费时间去分析了。但是足够我们后面去找属性文件的读写逻辑了。

2. 属性文件的读写逻辑

前面呢,分析了一堆,大概可以知道,创建文件的时候,为这个文件的操作注册了对应的读写函数,就是 sysfs_file_kfops_rw 这个变量中的两个函数。但是最终是怎么操作的?我们以读的函数为例来看一下。

2.1 sysfs_kf_seq_show()

sysfs_kf_seq_show() 函数定义如下:

c
static int sysfs_kf_seq_show(struct seq_file *sf, void *v)
{
	struct kernfs_open_file *of = sf->private;
	struct kobject *kobj = of->kn->parent->priv;
	const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
	ssize_t count;
	char *buf;
	//......
	if (ops->show) {
		count = ops->show(kobj, of->kn->priv, buf);
		if (count < 0)
			return count;
	}
	//......
	return 0;
}

这里可以看到定义了一个 struct sysfs_ops 类型指针,指向了 sysfs_file_ops() 的返回值,最后调用了其中的 show 函数,具体这个私有信息是怎么来的,应该文件系统那边去做的这里暂时就不详细去分析了。我们目标只有一个,就是找到这个 show 函数。

2.2 sysfs_file_ops()

sysfs_file_ops() 函数定义如下:

c
static const struct sysfs_ops *sysfs_file_ops(struct kernfs_node *kn)
{
	struct kobject *kobj = kn->parent->priv;

	if (kn->flags & KERNFS_LOCKDEP)
		lockdep_assert_held(kn);
	return kobj->ktype ? kobj->ktype->sysfs_ops : NULL;
}

到这里其实大概就比较清楚了,sysfs_file_ops() 函数返回的其实就是 ktype 中的 sysfs_ops 操作函数集。

2.3 总结

这里不再深入去分析了,内核太庞大了,以后有机会再说。到这里,我们大概知道了属性文件大概的调用逻辑。处理过程大概就是:

在 cat/echo 属性文件时(读/写属性文件写数据时)

  • (1)先调用 sysfs_file_ops() 获取到 kobj->ktype->sysfs_ops 指针
  • (2)调用对应内核的 show/store 函数。

从这里可以得出两条结论:

  • 对于用户空间来讲,只负责把数据丢给内核的 store 以及从内核的 show 函数获取数据,至于 store 的数据用来做什么和 show 获取到数据表示什么意思则由内核决定。

  • 如果从属的 kobject(就是 attribute 文件所在的目录)没有 ktype,或者没有 ktype-> sysfs_ops 指针,是不允许它注册任何 attribute 的。

参考资料:

【1】linux 设备驱动(9)attribute 详解 - Action_er - 博客园

【2】linux 驱动-设备驱动模型(属性文件 kobject )_linux 设备模型 kobject-CSDN 博客

【3】Linux 驱动开发—设备模型框架 kobject 创建属性文件_修改 kobject 创建的文件操作权限-CSDN 博客

【4】内核里 printk 打印指针打印出(____ ptrval ____) - 简书

【5】如何获得正确的printk格式占位符 — The Linux Kernel documentation