系统相关
首页 > 系统相关> > Linux设备模型(三) sysfs 与 attribute

Linux设备模型(三) sysfs 与 attribute

作者:互联网

参考文章:http://www.wowotech.net/linux_kenrel/dm_sysfs.html

目录

1. 前言

sysfs是一个基于RAM的文件系统,它和kobject一起,可以将Kernel的数据结构导出到用户空间,以文件目录结构的形式,提供对这些数据结构(以及数据结构的属性)的访问支持。

Linux设备模型(二) 基本数据结构 Kobject 已经讲过sysfs和Kobject相关数据结构的关系了,本文不再赘述,本文主要讨论属性和sysfs与普通文件系统的关系。

跟属性相关的关键数据结构有:

struct attribute
struct attribute_group
struct bin_attribute
struct sysfs_ops

2. sysfs 与 attribute

在sysfs中,为什么会有attribute的概念呢?其实它是对应kobject而言的,指的是kobject的“属性”。我们知道,sysfs中的目录描述了kobject,而kobject是特定数据类型变量(如struct device)的体现。因此kobject的属性,就是这些变量的属性。它可以是任何东西,名称、一个内部变量、一个字符串等等。而attribute,在sysfs文件系统中以文件的形式存在,即:kobject的所有属性,都在它对应的sysfs目录下以文件的形式呈现。这些文件一般是可读、写的,而kernel中定义了这些属性的模块,会根据用户空间的读写操作,记录和返回这些attribute的值。

总结:所谓的attibute,就是内核空间和用户空间进行信息交互的一种方法。例如某个driver定义了一个变量,却希望用户空间程序可以修改该变量,以控制driver的运行行为,那么就可以将该变量以sysfs attribute的形式开放出来。

2.1. attribute基础知识

Linux内核中,attribute分为普通attribute,attribute_group,bin_attribute

源码版本:Kernel 3.10
源码路径:
         include/linux/sysfs.h
         fs/sysfs/file.c

// include/linux/sysfs.h, line 26
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,属性的名字,对应sysfs中文件的名字。
mode,应用于属性(文件)的保护位,与文件的权限相同。
ignore_lockdepkeyskey,内核用于debug Kernel Lock(一般用不到)。

// include/linux/sysfs.h, line 57
struct attribute_group {
	const char		*name;
	umode_t			(*is_visible)(struct kobject *,
					      struct attribute *, int);
	struct attribute	**attrs;
};

name,属性组的名字,不为空的话对应一个sysfs的文件夹。
attrs,属性组里的普通属性列表。
is_visible,返回组中属性的读写权限。

// include/linux/sysfs.h, line 100
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);
};

attr,包含的普通属性。
size,二进制属性的最大长度,如果没有最大值可以设为0。
private,私有数据,与linux中大部分私有数据的用途一样,用于传递私有数据结构。
readwritemmap,操作二进制属性的函数。

二进制属性不能作为默认属性被设置,只能显式地创建。

struct attribute 为普通的attribute,使用该attribute生成的sysfs文件,只能用字符串的形式读写(第4节中说明)。
struct attribute_group 为普通的attributes的集合,可以在不借助Kobject创建一个目录,并在该目录下创建多个属性文件。
struct bin_attribute 在struct attribute的基础上,增加了read、write等函数,因此它所生成的sysfs文件可以用任何方式读写。固件一般使用bin_attribute属性。

2.2. attibute文件的创建

普通attibute文件的创建是由fs/sysfs/file.c中sysfs_create_file()接口完成的。
attribute_group为普通attributes的集合,由fs/sysfs/group.c中sysfs_create_group()接口来完成创建。
bin_attribute文件的创建是由fs/sysfs/bin.c中sysfs_create_bin_file()接口完成的。

注:上述三个接口的实现没有什么特殊之处,大多是文件系统相关的操作,和设备模型没有太多的关系,这里先略过不提。

2.3. 普通attibute文件的read和write

看到2.1章节struct attribute的原型时,也许我们会犯嘀咕,该结构很简单啊,name表示文件名称,mode表示文件模式,其它的字段都是内核用于debug Kernel Lock的,那文件操作的接口在哪里呢?

不着急,我们去fs/sysfs目录下看看sysfs相关的代码逻辑。

所有的文件系统,都会定义一个struct file_operations变量,用于描述本文件系统的操作接口,sysfs也不例外:

// fs/sysfs/file.c, line 479
const struct file_operations sysfs_file_operations = {
	.read		= sysfs_read_file,
	.write		= sysfs_write_file,
	.llseek		= generic_file_llseek,
	.open		= sysfs_open_file,
	.release	= sysfs_release,
	.poll		= sysfs_poll,
};

attribute文件的read操作,会由VFS转到sysfs_file_operations的read(也就是sysfs_read_file)接口上,让我们大概看一下该接口的处理逻辑。

// fs/sysfs/file.c, line 127
static ssize_t
sysfs_read_file(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
	struct sysfs_buffer * buffer = file->private_data;
	ssize_t retval = 0;

	mutex_lock(&buffer->mutex);
	if (buffer->needs_read_fill || *ppos == 0) {
		retval = fill_read_buffer(file->f_path.dentry,buffer);
		if (retval)
			goto out;
	}
    ......
}
// fs/sysfs/file.c, line 67
static int fill_read_buffer(struct dentry * dentry, struct sysfs_buffer * buffer)
{
	struct sysfs_dirent *attr_sd = dentry->d_fsdata;
	struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
	const struct sysfs_ops * ops = buffer->ops;
    ......
	count = ops->show(kobj, attr_sd->s_attr.attr, buffer->page);
    ......
}

read处理看着很简单,sysfs_read_file从file指针中取一个私有指针(注:大家可以稍微留一下心,私有数据的概念,在VFS中使用是非常普遍的),转换为一个struct sysfs_buffer类型的指针,以此为参数(buffer),转身就调用fill_read_buffer接口。

而fill_read_buffer接口,直接从buffer指针中取出一个struct sysfs_ops指针,调用该指针的show函数,即完成了文件的read操作。

那么后续呢?当然是由ops->show接口接着处理咯。而具体怎么处理,就是其它模块(例如某个driver)的事了,sysfs不再关心(其实,Linux大多的核心代码,都是只提供架构和机制,具体的实现,也就是苦力,留给那些码农吧!这就是设计的魅力)。

不过还没完,这个struct sysfs_ops指针哪来的?好吧,我们再看看open(sysfs_open_file)接口吧。

// fs/sysfs/file.c, line 326
static int sysfs_open_file(struct inode *inode, struct file *file)
{
	struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata;
	struct kobject *kobj = attr_sd->s_parent->s_dir.kobj;
	struct sysfs_buffer *buffer;
	const struct sysfs_ops *ops;
    ......
	/* every kobject with an attribute needs a ktype assigned */
	if (kobj->ktype && kobj->ktype->sysfs_ops)
		ops = kobj->ktype->sysfs_ops;
	else {
		WARN(1, KERN_ERR "missing sysfs attribute operations for "
		       "kobject: %s\n", kobject_name(kobj));
		goto err_out;
	}
    ......
	buffer = kzalloc(sizeof(struct sysfs_buffer), GFP_KERNEL);
	if (!buffer)
		goto err_out;

	mutex_init(&buffer->mutex);
	buffer->needs_read_fill = 1;
	buffer->ops = ops;
	file->private_data = buffer;
    ......
}

哦,原来和ktype有关系。这个指针是从该attribute所从属的kobject中拿的,而Kobject中ktype有一个struct sysfs_ops的指针。

我们注意一下第9行的注释以及其后代码逻辑,如果从属的kobject(就是attribute文件所在的目录)没有ktype,或者没有ktype->sysfs_ops指针,是不允许它注册任何attribute的!

经过确认后,sysfs_open_file从ktype中取出struct sysfs_ops指针,并在随后的代码逻辑中,(第18行)分配一个struct sysfs_buffer类型的指针(buffer),(第24行)并把struct sysfs_ops指针保存在其中,随后(注意哦),(第25行)把buffer指针交给file的private_data,随后read/write等接口便可以取出使用

现在来看一下已经登场多次的struct sysfs_ops,这是一个重要的数据结构。

// include/linux/sysfs.h, line 124
struct sysfs_ops {
	ssize_t	(*show)(struct kobject *, struct attribute *,char *);
	ssize_t	(*store)(struct kobject *,struct attribute *,const char *, size_t);
	const void *(*namespace)(struct kobject *, const struct attribute *);
};

attribute文件的write过程和read类似,这里就不再多说。
通过attribute_group创建attribute文件,本质上依然是创建普通attribute文件(通过sysfs_create_group()接口),只不过将这些attribute文件统一放在了同一目录(attribute_group.name)下。
另外,上面只分析了普通attribute的逻辑,而二进制类型的呢?也类似,去看看fs/sysfs/bin.c吧,这里也不说了。


讲到这里,应该已经结束了,事实却不是如此。上面read/write的数据流,只到kobject(也就是目录)级别哦,而真正需要操作的是attribute(文件)啊!这中间一定还有一层转换!确实,不过又交给其它模块了。 下面我们通过一个例子,来说明如何转换的。

3. sysfs_ops实例分析(device_attribute)

从上面的分析可以看出,sysfs_ops在attribute文件访问中起到了关键作用。但是,确切地讲是中转的作用。
kobj_attribute、bus_attribute、class_attribute、device_attribute、driver_attribute 的具体实现中,均使用到了struct sysfs_ops。
下面将以device_attribute为例,详细讲解struct sysfs_ops是如何访问attribute文件的。先来看一下struct device_attribute。

// include/linux/device.h line 478
/* interface for exporting device attributes */
struct device_attribute {
	struct attribute	attr;
	ssize_t (*show)(struct device *dev, struct device_attribute *attr,
			char *buf);
	ssize_t (*store)(struct device *dev, struct device_attribute *attr,
			 const char *buf, size_t count);
};

attr,属性,对应sysfs里的文件。
show、store,访问属性文件时的read、write函数。

真正创建device_attribute时,通常使用如下 DEVICE_ATTR 宏。DEVICE_ATTR 宏声明有四个参数,分别是_name、_mode、_show、_store。这4个参数均需要用户自定义并传入。

// include/linux/device.h line 505
#define DEVICE_ATTR(_name, _mode, _show, _store) \
	struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
// include/linux/sysfs.h line 71
#define __ATTR(_name,_mode,_show,_store) { \
	.attr = {.name = __stringify(_name), .mode = _mode },	\
	.show	= _show,					\
	.store	= _store,					\
}

_name,用户自定义attribute name,即sysfs文件系统里文件名。
_mode,属性文件访问权限,与普通文件相同,UGO的格式。
_show,读函数,cat该文件时,此函数被调用。
_store,写函数,echo内容到该文件时,此函数被调用。

注:
       对设备(device)使用 DEVICE_ATTR()
       对驱动(driver)使用 DRIVER_ATTR()
       对总线(bus)使用 BUS_ATTR()
       对类(class)使用 CLASS_ATTR()


现在该struct sysfs_ops 登场了。

// drivers/base/core.c line 88
#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)
// drivers/base/core.c line 90
static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
	struct device_attribute *dev_attr = to_dev_attr(attr);
	struct device *dev = kobj_to_dev(kobj);
    ......
	if (dev_attr->show)
		ret = dev_attr->show(dev, dev_attr, buf);
    ......
}
// drivers/base/core.c line 106
static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
	struct device_attribute *dev_attr = to_dev_attr(attr);
	struct device *dev = kobj_to_dev(kobj);
	......
	if (dev_attr->store)
		ret = dev_attr->store(dev, dev_attr, buf, count);
	return ret;
}
// drivers/base/core.c line 118
static const struct sysfs_ops dev_sysfs_ops = {
	.show	= dev_attr_show,
	.store	= dev_attr_store,
};
// drivers/base/core.c line 243
static struct kobj_type device_ktype = {
	.release	= device_release,
	.sysfs_ops	= &dev_sysfs_ops,
	.namespace	= device_namespace,
};

上述代码块第31行,在device_ktype中设置了 sysfs_ops 为 dev_sysfs_ops。结合上面2.3小节对 fill_read_buffer() 的分析,其中的 ops->show() 在本例中就是 dev_sysfs_ops.dev_attr_show()。

dev_sysfs_ops(struct sysfs_ops)的 dev_attr_show()/dev_attr_store()(.show/.store)函数使用宏container_of()通过struct attribute类型的指针取得一个device模块的自定义指针 struct device_attribute(上述代码块第6行),该指针中包含了device模块自身的show和store接口。上述代码块第10行,才真正执行DEVICE_ATTR(device_attribute)中的 _show 接口。

例如:
a. 使用宏DEVICE_ATTR初始化device_attribute;

static DEVICE_ATTR(status, S_IRUGO | S_IWUSR, status_show, status_store);
static struct attribute *dev_attrs[] = {
    &dev_attr_status.attr,
    NULL,
};

b. 配置attribute_group;

static struct attribute_group dev_attr_grp = {
    .name  = "dev_group",
    .attrs = dev_attrs,
};

c. 在module_init里调用sysfs_create_group()创建属性文件;

retval = sysfs_create_group(&pdev->dev.kobj, &dev_attr_grp);
if (retval) {
     ......
}

通过以上简单的三个步骤,就可以在adb shell 终端查看到属性文件。当我们将数据 echo 到文件中时,在上层实际上完成了一次 write 操 作,对应到 kernel ,调用了驱动中的 “store”。同理,当我们cat 一个文件时则会调用 “show” 。

因此,所有需要使用attribute的模块,都不会直接定义struct attribute变量,而是通过一个自定义的数据结构,该数据结构的一个成员是struct attribute类型的变量,并提供show和store回调函数。然后在该模块ktype所对应的struct sysfs_ops变量中,实现该本模块整体的show和store函数,并在被调用时,通过container_of()转接到自定义数据结构中的show和store函数中。这样,每个atrribute文件,实际上对应到一个自定义数据结构变量中了。

4. 再谈attribute文件访问

悬案一:
细心的读者可能发现,在2.3.小节的标题是“普通attribute文件的read和write”。没错,通过sysfs_file_operations提供的接口只能访问普通attribute文件。
bin_attribute文件的访问是另外一条code flow。

悬案二:
2.1小节中,有提到普通attribute文件只能用字符串的形式读写。为什么呢 ?

4.1. bin_attribute文件是如何read和write的 ?

其实,两个悬案是有联系的,我们来看一下创建普通attribute和bin_attribute的操作有哪些不同。如下代码段可以看出差异在调用sysfs_add_file()时的第三个入参:
创建普通attribute使用的是SYSFS_KOBJ_ATTR
创建bin_attribute使用的是SYSFS_KOBJ_BIN_ATTR

// fs/sysfs/file.c line 571
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
{
	BUG_ON(!kobj || !kobj->sd || !attr);
	return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR);
}
// fs/sysfs/bin.c line 485
int sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
{
	BUG_ON(!kobj || !kobj->sd || !attr);
	return sysfs_add_file(kobj->sd, &attr->attr, SYSFS_KOBJ_BIN_ATTR);
}

再来看一下不同类型attribute文件的file_operations有何不同。在sysfs_init_inode()里,可以看出访问普通attribute文件是通过sysfs_file_operations,而访问bin_attribute文件是通过bin_fops。对于悬案二,答案就在bin_fops,bin_attribute文件是通过bin_fops read和write的。有兴趣的读者可以自行研究一下 bin_fops,这里不再赘述。

// fs/sysfs/inode.c line 238
static void sysfs_init_inode(struct sysfs_dirent *sd, struct inode *inode)
{
	struct bin_attribute *bin_attr;
	......
	/* initialize inode according to type */
	switch (sysfs_type(sd)) {
	......
	case SYSFS_KOBJ_ATTR:
		inode->i_size = PAGE_SIZE;
		inode->i_fop = &sysfs_file_operations;
		break;
	case SYSFS_KOBJ_BIN_ATTR:
		bin_attr = sd->s_bin_attr.bin_attr;
		inode->i_size = bin_attr->size;
		inode->i_fop = &bin_fops;
		break;
    ......
}

简单列一下const struct file_operations bin_fops,方便读者在这里简单比较 sysfs_file_operations (2.3.小节已经给出) 和 bin_fops。

// fs/sysfs/bin.c
const struct file_operations bin_fops = {
	.read		= read,
	.write		= write,
	.mmap		= mmap,
	.llseek		= generic_file_llseek,
	.open		= open,
	.release	= release,
};
4.2. 普通attribute文件为什么只能用字符串的形式读写 ?

在2.3小节中,我们讲过普通attribute文件的读写是通过 sysfs_ops 中转的。再来看一下 sysfs_ops 的数据结构定义。
悬案二的答案就在 show、store 两个函数指针的第三个形参:char *

// include/linux/sysfs.h, line 124
struct sysfs_ops {
	ssize_t	(*show)(struct kobject *, struct attribute *,char *);
	ssize_t	(*store)(struct kobject *,struct attribute *,const char *, size_t);
	const void *(*namespace)(struct kobject *, const struct attribute *);
};

5. 说明

5.1. 关于sysfs文件系统中目录的说明

sysfs只是内核对象在用户空间的一个视图,我们在内核里创建一个kobject或者kset,Linux内核就会帮我们在用户空间建立一个对应的目录结构,使得我们可以很方便的在用户空间访问内核的信息。
而反过来,在sysfs里的一个目录,到底在内核里对应的是kset抑或是kobject,就不是那么重要了。比如在本文中,用sysfs_create_group也可以在sysfs中创建一个目录,而这个目录并没有对应内核里的kobject或kset,使用attribute_group只是为了更清晰的组织我们的内核数据结构。
在sysfs中判断一个目录对应的到底是kset(kobject)还是group是很难做到的,而且也没有必要。如果你去查看内核代码,应该很容易看到sysfs_create_group是调用sysfs_create_subdir来完成的,sysfs_create_subdir只是在kobj对应的sysfs_dirent下面建立一个子目录,这个子目录并没有kobject与之对应。

5.2. 关于attribute文件内容的说明

这里截取一段原文 (https://www.kernel.org/doc/html/latest/filesystems/sysfs.html)
Attributes should be ASCII text files, preferably with only one value per file. It is noted that it may not be efficient to contain only one value per file, so it is socially acceptable to express an array of values of the same type.

Mixing types, expressing multiple lines of data, and doing fancy formatting of data is heavily frowned upon. Doing these things may get you publicly humiliated and your code rewritten without notice.

标签:attr,ops,attribute,sysfs,file,Linux,struct
来源: https://blog.csdn.net/yangjizhen1533/article/details/111290451