kernfs 是 kernel 3.14 引入的内存文件系统。在Linux内核中,kernfs 提供内核子系统内部伪文件系统所需的功能,源于拆分 sysfs 使用的部分内部逻辑,它通过将有关硬件设备和相关设备驱动程序的信息从内核的设备模型导出到用户空间,提供一组虚拟文件,从而实现独立且可重用的功能。 其他内核子系统可以更容易,更一致地实现自己的伪文件系统。
kernfs_node 表示 kernfs 层次的构成部分(building block),每个 kernfs 节点由单个 kernfs_node 表示。大多数字段都是 kernfs 专用的,不应该由 kernfs 用户直接访问。
初始化的时候创建 kernfs_node_cache
的 cache,只有在函数 __kernfs_new_node
中使用。
有两个函数会调用:
1)kernfs_new_node
;
2)sysfs_init
通过 kernfs_create_root(struct kernfs_syscall_ops *scops, unsigned int flags, void *priv)
一个新的 kernfs 层次,然后将其保存在静态全局变量 sysfs_root 中,供各处使用。然后通过 register_filesystem
将其注册为名为 sysfs 的文件系统。
int __init sysfs_init(void)
{
int err;
sysfs_root = kernfs_create_root(NULL,
KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK,
NULL);
if (IS_ERR(sysfs_root))
return PTR_ERR(sysfs_root);
sysfs_root_kn = sysfs_root->kn;
err = register_filesystem(&sysfs_fs_type);
if (err) {
kernfs_destroy_root(sysfs_root);
return err;
}
return 0;
}
这时,我们看以看到一个完整的目录层次结构(sys目录下有10个子目录,子目录下又有各个子目录),以及这个目录结构的创建方式。
# ls /sys
block class devices fs module
bus dev firmware kernel power
在系统中,通过 kobject_create_and_add
和 kset_create_and_add
创建这10个目录的逻辑结构,在这个逻辑层次它们都没有父节点,它们的 parent_kobj
参数都为空:
fs_kobj = kobject_create_and_add("fs", NULL);
power_kobj = kobject_create_and_add("power", NULL);
kernel_kobj = kobject_create_and_add("kernel", NULL);
dev_kobj = kobject_create_and_add("dev", NULL);
firmware_kobj = kobject_create_and_add("firmware", NULL);
block_depr = kobject_create_and_add("block", NULL);
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
class_kset = kset_create_and_add("class", NULL, NULL);
module_kset = kset_create_and_add("module", &module_uevent_ops, NULL);
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
kset_create_and_add
函数最终都调用到了相同的函数 kobject_add_internal
, 在这个函数中最终会调用到 sysfs_create_dir_ns
,创建 kernfs_node 保存到 struct kobject 的 sd 字段中:
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
struct kernfs_node *parent, *kn;
BUG_ON(!kobj);
if (kobj->parent)
parent = kobj->parent->sd;
else //sys目录下的10个子目录这个逻辑层次的kobj时,parent都设置为空
parent = sysfs_root_kn;
if (!parent)
return -ENOENT;
kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
S_IRWXU | S_IRUGO | S_IXUGO, 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;
}
在代码中可以看到:if (kobj->parent)
,如果为空,在 kernfs_node 这个逻辑层次的父节点,设置为:sysfs_root_kn。
如上文所述 kobject 在 sysfs 中对应的是目录(dir)。当我们注册一个 kobject 时,会调用 kobject_add
。于是:
kobject_add
=> kobject_add_varg
=> kobject_add_internal
=> create_dir
=> sysfs_create_dir_ns
如果 kobj 有 parent ,则它的父节点为 kobj->parent->sd
,否则为根目录节点 sysfs_root_kn 。于是将其作为参数调用 kernfs_create_dir_ns(parent, kobject_name(kobj), S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns)
; 在父节点目录下创建一个名为 kobj->name
的目录。
“属性” 在 sysfs 中对应的是文件(file)。当需要为设备添加属性时,可以调用 device_create_file
,于是:
device_create_file => sysfs_create_file => sysfs_create_file_ns => sysfs_add_file_mode_ns => __kernfs_create_file
为属性创建文件,并根据 kobj->ktype->sysfs_ops
为文件绑定对应的读写函数。
创建的文件大小即为存放该属性值的长度,对于普通属性来说,大小为 PAGE_SIZE(4K),而对于二进制属性来说,大小由属性自定义,即 bin_attribute.size 指定。
当用户对属性文件进行读写时,会调用绑定的读写函数,比如对于 mode 为 SYSFS_PREALLOC 且 kobj->ktype->sysfs_ops
定义了 show 和 store 函数的属性,绑定是 sysfs_prealloc_kfops_rw 。这里的 kobj 指的是该属性的父节点,也就是属性所属设备的 kobj。
于是在读文件时,调用 sysfs_kf_read
,它会根据属性文件找到其父节点类型对应的 sysfs_ops ,然后调用 sysfs_ops.show 。show 需要将输出写到传入的 buf 缓冲区中,并返回写入的长度。
在写文件时,调用 sysfs_kf_write
,它会根据属性文件找到其父节点类型对应的 sysfs_ops ,然后调用 sysfs_ops.store 。 store 可以从传入的 buf 缓冲区中,读取用户写入的长度为 len 的内容。
但是需要注意的是, sysfs_ops 中的 show 和 store 函数并非是读写我们属性所需要的 show 和 store。因为一个设备只有一个类型,因此 sysfs_ops 到 show 和 store 只有一种实现,但实际上 show 和 store 应该根据属性的不同而不同。怎么办呢?绕个弯子:在调用 sysfs_ops.show 和 sysfs_ops.store 时传入属性 attribute 的指针,然后在函数中将指针转换为设备类型对应属性的指针后调用属性的 show 和 store 函数。这也就是 device_attribute 、 class_attribute 或一些设备自定义属性(比如 cpuidle_driver_attr) 中定义有 show 和 store 函数的原因。
让我们通过设备模型 class.c 中有关 sysfs 的实现,来总结一下 sysfs 的应用方式。
首先,在 class.c 中,定义了 Class 所需的 ktype 以及 sysfs_ops 类型的变量,如下:
1: /* kernel 4.14: drivers/base/class.c, line 74 */
2: static const struct sysfs_ops class_sysfs_ops = {
3: .show = class_attr_show,
4: .store = class_attr_store,
5: .namespace = class_attr_namespace,
6: };
7:
8: static struct kobj_type class_ktype = {
9: .sysfs_ops = &class_sysfs_ops,
10: .release = class_release,
11: .child_ns_type = class_child_ns_type,
12: };
如文章【Linux kernel 文件系统入门及渐进 1 – sysfs 介绍】可知,所有 class_type
的 Kobject
(/sys/class/) 下面的 attribute
文件的读写操作,都会交给 class_attr_show
和 class_attr_store
两个接口处理。以 class_attr_show
为例:
/* drivers/base/class.c, line 24 */
#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);--->(1)
struct subsys_private *cp = to_subsys_private(kobj); ------->(2)
ssize_t ret = -EIO;
if (class_attr->show)
ret = class_attr->show(cp->class, class_attr, buf);
return ret;
}
(1) 找出包含这个 attr 的 struct class_attribute *
指针, 使用 container_of
从 struct attribute
类型的指针中取得一个 class 模块的自定义指针:struct class_attribute
,该指针中包含了class 模块自身的 show 和 store 接口。
(2) 找出包含这个 kobj 的 struct class *
指针,struct class 没有直接包含 kobj,通过subsys.kobj 间接找到 struct class *
指针。
下面是 struct class_attribute 的声明:
/* include/linux/device.h, line 399 */
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, struct class_attribute *attr, char *buf);
ssize_t (*store)(struct class *class, struct class_attribute *attr, const char *buf, size_t count);
const void *(*namespace)(struct class *class, const struct class_attribute *attr);
};
因此,所有需要使用 attribute 的模块,都不会直接定义 struct attribute
变量,而是通过一个自定义的数据结构,该数据结构的一个成员是 struct attribute
类型的变量,并提供 show 和 store 回调函数。然后在该模块 ktype 所对应的 struct sysfs_ops
变量中,实现该本模块整体的 show
和 store
函数,并在被调用时,转接到自定义数据结构(struct class_attribute
)中的 show
和 store
函数中。这样,每个 atrribute 文件,实际上对应到一个自定义数据结构变量中了。
参考:
http://www.wowotech.net/device_model/dm_sysfs.html
https://blog.csdn.net/zhoudawei/article/details/86669868