Sysfs 是一个基于 RAM 的文件系统,它和 kobject 一起,可以将 kernel 的数据结构导出到用户空间,以文件目录结构的形式,提供对这些数据结构(以及数据结构的属性)的访问支持。
每一个 kobject,都会对应 sysfs 中的一个目录。因此在将 kobject 添加到 kernel时,create_dir
接口会调用 sysfs 文件系统的创建目录接口,创建和 kobject 对应的目录。kobject 也是组成设备模型的基本结构。
接下来看下 Linux kobject 形成的历史背景及作用。
Linux 设备模型的核心是使用 Bus、Class、Device、Driver 四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便 kernel的统一管理。
而硬件设备的数量、种类是非常多的,这就决定了 kernel 中将会有大量的有关设备模型的数据结构。这些数据结构一定有一些共同的功能,需要抽象出来统一实现,否则就会不可避免的产生冗余代码。这就是 kobject 诞生的背景。
目前为止, kobject 主要提供如下功能:
在描述数据结构 kobject 之前,有必要说明一下 kobject
, kset
和 ktype
这三个概念:
/sys/
“ 文件系统中以目录的形式出现。struct kref
字段用于对 kobject 进行引用计数,当计数值为 0
时,就会调用 kobj_type 中的 release 函数对 kobject 进行释放。/sys/
“ 文件系统中以目录的形式出现),它用来集合相似的 kobject(这些 kobject 可以是相同属性的,也可以不同属性的),比如 devices_kset
,在 sysfs 中对应目录 “/sys/devices
”, 注册设备或驱动时就将 kobject 添加到对应的 kset 中;通过下图来看这三者的关系: (网图1)
来自网图 2:
kernel 源代码中, kobject 由如下两个文件实现:
其中 kobject.h 为 kobject 的头文件,包含所有的数据结构定义和接口声明, kobject.c 为核心功能的实现。
先看下 struct kobject
的实现:
struct kobject {const char *name;struct list_head entry;struct kobject *parent;struct kset *kset;struct kobj_type *ktype;struct sysfs_dirent *sd;struct kref kref;unsigned int state_initialized:1;unsigned int state_in_sysfs:1;unsigned int state_add_uevent_sent:1;unsigned int state_remove_uevent_sent:1;unsigned int uevent_suppress:1;
};
kobject_rename
接口,该接口会主动处理 sysfs 的相关事宜;NULL
。如果存在,且没有指定 parent,则会把 kset 作为 parent;kernelfs_node
;sysfs 中的每一个目录或者文件都有一个 kernel_node
来描述,就像 VFS 中的 inode 一样;struct kref
” 类型(在 include/linux/kref.h 中定义)的变量,为一个可用于原子操作的引用计数;1
,则表示忽略所有上报的 uevent 事件。Uevent 提供了“用户空间通知”的功能实现,通过该功能,当内核中有 kobject 的增加、删除、修改等动作时,会通知用户空间。
再看下 struct kset
的实现:
struct kset {struct list_head list;spinlock_t list_lock;struct kobject kobj;const struct kset_uevent_ops *uevent_ops;};
最后看下 struct ktype
的实现:
struct kobj_type {void (*release)(struct kobject *kobj);const struct sysfs_ops *sysfs_ops;struct attribute **default_attrs;const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);const void *(*namespace)(struct kobject *kobj);
在 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,这里主要介绍普通的 attribute, 如下:
struct attribute {const char *name;umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOCbool ignore_lockdep:1;struct lock_class_key *key;struct lock_class_key skey;#endif
};
name: 表示文件名称;
mode: 表示文件模式;
其它的字段: 都是内核用于debug kernel Lock的。
attribute 生成的 sysfs 文件,只能用字符串的形式读写
说完基本概念,我们要问两个问题:
1. kernel 怎么把 attribute 变成 sysfs 中的文件呢?
2. 用户空间对 sysfs 的文件进行的读写操作,怎么传递给 kernel 呢?
在内核中,sysfs 的属性文件一般都是由 xxx_ATTR
系列的宏来实现, 如:
DEVICE_ATTR
;BUS_ATTR
;DRIVER_ATTR
;CLASS_ATTR
。例如:Linux 会使用 DEVICE_ATTR
宏,在 /sys/devices/
下面生成 attribute 文件节点,驱动开发工程师只需要实现 show
和 store
函数即可。然后在 userspace 就能通过 cat
和 echo
命令来对 “/sys/” 下创建出来的属性文件进行读写操作, 从而实现和底层的交互。
在 linux 内核中,attibute 文件是由 fs/sysfs/file.c 中 sysfs_create_file
通过kobject 创建:
static inline int __must_check sysfs_create_file(struct kobject *kobj,const struct attribute *attr)
{return sysfs_create_file_ns(kobj, attr, NULL);
}
下面的 3 个函数内部都会调用到 sysfs_create_file
:
DEVICE_ATTR() 宏定义:
DEVICE_ATTR()
定义位于 include/linux/device.h 中, 定义如下所示:
#define DEVICE_ATTR(_name, _mode, _show, _store) \struct device_attribute dev_attr_##_name = \ __ATTR(_name, _mode, _show, _store)
其中 _mode 定义如下:
当然也可以用 S_IWUSR
(用户可写),S_IRUSR
(用户可读)等宏代替。
下面用具体代码展示如何创建 attribute 文件:
static DEVICE_ATTR(my_test, S_IWUSR | S_IRUSR, show_my_device, \show_my_device, set_my_device);static ssize_t show_my_device(struct device *dev, struct device_attribute *attr, char *buf)
{return sprintf(buf, "test result:0x%x\n", 0xffff);
}static ssize_t set_my_device(struct device *dev,struct device_attribute *attr,const char *buff, size_t count)
{unsigned int input, ret_val;if (sscanf(buff, "%x", &input) < 0x1) {PR_ERR(storing information failed\n);return count;}/* TODO */return count;
}
最后会将宏展开为:
struct device_attribute dev_attr_my_test ={.attr = {.name = "my_test", .mode = S_IWUSR|S_IRUSR },.show = show_my_device,.store = set_my_device,
}
然后再通过 device_create_file()
创建上面 my_test 设备文件。
ret = device_create_file(dev, &dev_attr_my_test);if (ret < 0) {dev_err(dev, "failed to create sysfs!\n");return ret;}
函数 device_create_file
会调用到 sysfs_add_file_mode_ns
,最后是调用kernfs_new_node()
创建了一个 kernfs_node
结构。sysfs 中的每一个目录或者文件都有一个 kernfs_node
来描述,就像 VFS 中的 inode 一样。
sysfs 和 kobject 实现为两套独立的框架。一个 kobject 在 sysfs 中表现为一个目录,属性在 sysfs 中表现为一个文件。kernfs_node->priv
指向 kobject
;kobject->sd
指向 kernfs_node
,这实现了 kobject 框架和 sysfs 的连接;总的来说:
通过上节内容我们知道了如何创建 attribute 文件,那文件操作的接口在哪里呢?
所有的文件系统,都会定义一个 struct file_operations
变量,用于描述本文件系统的操作接口,sysfs 也不例外,从 linux/fs/kernfs/file.c 看看 sysfs 相关的代码逻辑:
const struct file_operations kernfs_file_fops = {.read = kernfs_fop_read,.write = kernfs_fop_write,.llseek = generic_file_llseek,.mmap = kernfs_fop_mmap,.open = kernfs_fop_open,.release = kernfs_fop_release,.poll = kernfs_fop_poll,.fsync = noop_fsync,
};
attribute 文件的 read 操作,会由 VFS 转到 kernfs_file_fops 的 read。
推荐阅读:
http://www.wowotech.net/linux_kenrel/ kobject.html
http://www.wowotech.net/device_model/dm_sysfs.html
https://zhuanlan.zhihu.com/p/530283012
https://blog.csdn.net/chenying126/article/details/78079942/