linux usb 驱动 - configfs 文件系统
linux usb hcd 驱动框架
- 一、数据结构
- 1. configfs_subsystem
- 2. config_group
- 3. config_item
- 4. config_item_type
- 5. configfs_attribute
- 6. configfs_dirent
- 7. 数据结构关系
- 二、根目录的注册
- 1. configfs_register_subsystem
- 1) configfs_create
- 2) configfs_create_dir
- 2. 编程实验
- 3. populate_attrs
- 三、创建子目录
- 四、创建属性文件
作者: baron
个人博客: baron-z.cn
usb 驱动集合:
linux usb 驱动 - hcd 驱动框架
linux usb 驱动 - configfs 文件系统
linux usb 驱动 - udc 驱动框架
configfs 是 Linux 内核中的一个虚拟文件系统(vfs),他和 sysfs 类似, 都是以目录和文件的形式导出到用户空间和用户交互, 虚拟表示 configfs 中的"目录"只是一个用户界面的表现形式, 没有实际的物理存储空间分配, 目录结构只是内核对象的一种层次化展示方式. 每个"目录"实际上映射到一个 config_group, 目录操作被转换为对应的 config_group 操作, 比如 mkdir 实际上是调用 make_group 来创建新的内核配置项. 文件系统操作都被转换为对应的内核对象操作,通过这些回调接口来实现用户空间和内核空间的交互.
由于 configfs 是 usb udc 驱动的前置部分, 因此虽然是文件系统的内容, 但是还是分类 usb 在驱动中. 在内核中 configfs 几乎只用于配置 usb.
这种设计的优点:
- 提供了一个直观的用户接口(通过目录结构), linux 和核心理念一切皆文件
- 实现了用户空间和内核空间的优雅交互
- 便于管理和配置内核对象
这就是为什么它叫 configfs (配置文件系统) 而不是普通的文件系统,它的主要目的是配置而不是存储.下面命令用于挂载 configfs 文件系统.
mount -t configfs none /sys/kernel/config
在 vfs(虚拟文件系统) 文件系统中, 文件和目录的描述主要由 dentry(目录项) 和 inode(索引节点) 描述. 目录/文件 = dentry + inode
struct dentry {
......
struct inode *d_inode; // 其关联的 inode
void *d_fsdata; // 在configfs 中指向 configfs_dirent
......
};
dentry 是目录树的结构化表示, 用于描述文件系统中的路径信息, 它是文件路径的一个缓存, 记录了目录树中的父子关系, 用于高效地解析路径和快速定位文件.
struct inode {
......
umode_t i_mode; // 文件的类型和访问权限(如 S_IFREG 表示普通文件,S_IFDIR 表示目录)
const struct inode_operations *i_op; // VFS 文件系统的回调接口,用于实现目录和文件操作,如 mkdir(创建目录)、rmdir(删除目录)、create(创建文件)、lookup(查找目录项)等。
const struct file_operations *i_fop; // 文件的操作接口,用于处理打开文件后的操作,如 read(读取文件)、write(写入文件)、release(关闭文件)等。
......
};
inode 是文件或目录的核心元数据结构,
- 它用于描述文件系统中的一个实际对象(文件或目录).
- 它存储了文件的属性(如大小、权限、时间戳)和数据块位置等信息.
- 每个文件或目录在文件系统中都有一个唯一的 inode.
- dentry 可以有多个例如链接文件, 而 inode 只能有一个唯一的.
- i_op 表示目录操作后面, i_fop 表示是文件操作, 目录也可以支持文件操作.
下表描述了 i_mode 的文件/目录等定义
文件类型 | 宏定义 | 值(八进制) | 描述 |
---|---|---|---|
普通文件 | S_IFREG | 0100000 | 标识普通文件 |
目录 | S_IFDIR | 0040000 | 标识目录 |
符号链接 | S_IFLNK | 0120000 | 标识符号链接 |
字符设备 | S_IFCHR | 0020000 | 标识字符设备文件 |
块设备 | S_IFBLK | 0060000 | 标识块设备文件 |
FIFO(管道) | S_IFIFO | 0010000 | 标识命名管道(FIFO) |
套接字 | S_IFSOCK | 0140000 | 标识套接字文件 |
此外,i_mode
还包括权限标志位,与文件类型标志位结合使用,用于表示文件的权限信息。
权限类型 | 宏定义 | 值(二进制) | 描述 |
---|---|---|---|
读权限 | S_IRUSR | 0000400 | 文件所有者读权限 |
写权限 | S_IWUSR | 0000200 | 文件所有者写权限 |
执行权限 | S_IXUSR | 0000100 | 文件所有者执行权限 |
读权限(组) | S_IRGRP | 0000040 | 所属组读权限 |
写权限(组) | S_IWGRP | 0000020 | 所属组写权限 |
执行权限(组) | S_IXGRP | 0000010 | 所属组执行权限 |
读权限(其他) | S_IROTH | 0000004 | 其他用户读权限 |
写权限(其他) | S_IWOTH | 0000002 | 其他用户写权限 |
执行权限(其他) | S_IXOTH | 0000001 | 其他用户执行权限 |
一、数据结构
1. configfs_subsystem
用来描述 congfs 子系统的根节点, 每一个 configfs 文件系统都有且只有一个 configfs_subsystem 结构, 用来描述 /sys/kernel/config
目录下的第一个节点. 它的 su_group
成员就是根目录.
struct configfs_subsystem {
struct config_group su_group; // 根目录
struct mutex su_mutex;
};
2. config_group
用来描述 configfs 中的目录管理结构, 包含真正的目录回调接口的描述结构 cg_item 和 default_groups 等数据结构, 用来管理当前目录以及当前目录包含的子目录. 其中 default_groups 用来描述当前目录下的子目录的管理结构.
struct config_group {
struct config_item cg_item; // 真正的目录回调接口的描述结构
struct list_head cg_children; // 连接子目录对应的 config_item
struct configfs_subsystem *cg_subsys;
struct config_group **default_groups; // 当前config_group 下的子 config group
};
3. config_item
真正的目录回调接口描述结构, 可以理解为每一个 config_item 结构都对应着 configs 的一个目录/文件, 他抽象了真正的目录/文件的底层操作, 例如 mkdir 等操作最终都被映射成对这个数据结构的操作.
struct config_item {
char *ci_name; // 目录名称
char ci_namebuf[CONFIGFS_ITEM_NAME_LEN];
struct kref ci_kref; // 引用计数
struct list_head ci_entry; // 连接到所属的 group 的 cg_children
struct config_item *ci_parent; // 连接到父目录
struct config_group *ci_group; // 所属的 group
struct config_item_type *ci_type; //
struct dentry *ci_dentry; // 连接到对应的目录结构 dentry
}
4. config_item_type
他有两个部分的内容
- 目录操作:
ct_group_ops
用于描述目录的常见操作 mkdir, rmdir 等操作的回调接口,ct_item_ops
则是让目录也支持show
和store
的属性操作. - 属性文件描述数组:
ct_attrs
则表示当前目录下的属性文件的数组.当前文件下的属性文件都保存在这个数组中.
struct config_item_type {
struct module *ct_owner;
struct configfs_item_operations *ct_item_ops; // 用于描述目录的常见操作 mkdir, rmdir 等操作的回调接口
struct configfs_group_operations *ct_group_ops; // 目录也支持 show 和 store 的属性操作
struct configfs_attribute **ct_attrs; // 属性文件描述数组, 每一个成员都是一个属性文件回调接口.
};
5. configfs_attribute
属性文件回述结构, 每一个结构包含一个属性文件的名字和回调函数.
struct configfs_attribute {
const char *ca_name; // 属性文件的名字
struct module *ca_owner; // 所属模块
umode_t ca_mode; // 操作权限
ssize_t (*show)(struct config_item *, char *); // 对应的 show 回调函数
ssize_t (*store)(struct config_item *, const char *, size_t); // 对应的 store 回调结构
};
6. configfs_dirent
他是目录描述结构 dentry 和 configfs 中的各个数据结构的桥梁.内核通过 VFS(虚拟文件系统) 层访问 configfs 中的对象.
struct configfs_dirent {
atomic_t s_count;
int s_dependent_count;
struct list_head s_sibling;
struct list_head s_children;
struct list_head s_links;
void * s_element;
int s_type;
umode_t s_mode;
struct dentry * s_dentry;
struct iattr * s_iattr;
#ifdef CONFIG_LOCKDEP
int s_depth;
#endif
};
7. 数据结构关系
文件和目录都是由相同的数据结构组成, 怎么区分文件和目录呢, 通过两个部分, 一是 inode 的 i_mode 标志, 另一个就是 inode 的回调.
- i_mode 设置 S_IFDIR
- inode 设置回调
inode->i_op = &configfs_dir_inode_operations;
inode->i_fop = &configfs_dir_operations;
- i_mode 设置 S_IFREG
- inode 设置回调
inode->i_size = PAGE_SIZE;
inode->i_fop = &configfs_file_operations;
下图整理了configfs 文件系统的数据结构的关系
二、根目录的注册
1. configfs_register_subsystem
configfs_register_subsystem
接口用于注册根目录, 调用该接口后, 我们将在 configfs 中创建 configfs_subsystem 数据结构描述的目录.
整个注册过程就是填充 config_group
、config_item
、config_item_type
、configfs_attribute
以及分配 configfs_dirent
、dentry
、inode
等数据结构, 并将其关联起来.调用流程如下图所示.
整个注册过程大体分可以分为两个步骤, 关键的函数有三个 configfs_create
configfs_create_dir
、 populate_attrs
. .
1) configfs_create
configfs_create
该接口用于创建 configfs 文件系统的的目录/文件/链接文件, 具体创建的是什么由 mode 中的标志位决定, 传入的 init
函数则用来设置对应目录/文件/链接文件的回调函数.
//fs/configfs/dir.c
int configfs_create(struct dentry * dentry, umode_t mode, void (*init)(struct inode *))
{
int error = 0;
struct inode *inode = NULL;
struct configfs_dirent *sd;
struct inode *p_inode;
if (!dentry) // dentry 为空返回 err
return -ENOENT;
// 当前目录已存在返回 err
if (d_really_is_positive(dentry))
return -EEXIST;
// 获取 dentry 对应的 configfs_dirent
sd = dentry->d_fsdata;
// 分配一个 inde, 初始化其 mode、sd 等
inode = configfs_new_inode(mode, sd, dentry->d_sb);
if (!inode)
return -ENOMEM;
p_inode = d_inode(dentry->d_parent); // 获取父目录的 inode
p_inode->i_mtime = p_inode->i_ctime = CURRENT_TIME; // 更新时间戳
configfs_set_inode_lock_class(sd, inode);
init(inode); // 初始化 inode
d_instantiate(dentry, inode); // 将 inode 和 dentry 关联起来
// 为目录或符号链接的 dentry 增加引用计数,防止被内核回收
if (S_ISDIR(mode) || S_ISLNK(mode))
dget(dentry);
return error;
}
2) configfs_create_dir
该接口用于在 configfs
文件系统中创建目录并设置读写权限, 其中 init_dir
接口用于设置底层回调接口.创建的目录具有的默认权限如下:
- 用户 (User) 的权限,包含读、写、执行
- 用户组 (Group) 和其他人 (Others) 对文件的读权限和执行权限
//fs/configfs/dir.c
static int configfs_create_dir(struct config_item *item, struct dentry *dentry)
{
int error;
// 核心代码设置
// S_IFDIR: 表示当前inode 注册进内核后以目录的形式展现出来
// S_IRWXU: 用户 (User) 的权限,包含读、写、执行
// S_IRUGO: 用户组 (Group) 和其他人 (Others) 对文件的读权限
// S_IXUGO: 用户组 (Group) 和其他人 (Others) 对文件的执行权限
umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO;
struct dentry *p = dentry->d_parent; // 获取父目录的 dentry
BUG_ON(!item);
// 如果这个目录已经存在则返回 err
error = configfs_dirent_exists(p->d_fsdata, dentry->d_name.name);
if (unlikely(error))
return error;
// 创建 configfs_dirent 并初始化
error = configfs_make_dirent(p->d_fsdata, dentry, item, mode,
CONFIGFS_DIR | CONFIGFS_USET_CREATING);
if (unlikely(error))
return error;
// 初始化默认的目录深度
configfs_set_dir_dirent_depth(p->d_fsdata, dentry->d_fsdata);
// 创建目录, 即调用 init_dir 设置目录的回调
error = configfs_create(dentry, mode, init_dir);
if (!error) {
inc_nlink(d_inode(p));
item->ci_dentry = dentry;
} else {
struct configfs_dirent *sd = dentry->d_fsdata;
if (sd) {
spin_lock(&configfs_dirent_lock);
list_del_init(&sd->s_sibling);
spin_unlock(&configfs_dirent_lock);
configfs_put(sd);
}
}
return error;
}
init_dir 接口用于设置目录操作的底层的回调接口.
static void init_dir(struct inode * inode)
{
inode->i_op = &configfs_dir_inode_operations;
inode->i_fop = &configfs_dir_operations;
inc_nlink(inode);
}
// 在当前目录下的操作
const struct inode_operations configfs_dir_inode_operations = {
.mkdir = configfs_mkdir, // 创建目录时的回调接口
.rmdir = configfs_rmdir, // 删除目录时的回调接口
.symlink = configfs_symlink, // 创建链接文件的回调接口
.unlink = configfs_unlink, // 删除链接文件的回调接口
.lookup = configfs_lookup, // 这个接口是用来设置属性文件的 inode 接口回调函数的.
.setattr = configfs_setattr, // 设置附加属性回调接口
};
// 对当前目录的 open/read 等操作映射到这里
const struct file_operations configfs_dir_operations = {
.open = configfs_dir_open, // 打开目录
.release = configfs_dir_close, // 关闭目录
.llseek = configfs_dir_lseek, // 改变文件偏移量(用于读取目录内容)
.read = generic_read_dir, // 读取目录内容
.iterate = configfs_readdir, // 遍历目录项
};
2. 编程实验
这个实验很简单在内核中创建一个configfs 的根目录 myconfig.需要注意的是内核中只能有一个 configfs 文件系统, 所以要把原来默认的去掉.
// drivers/usb/Makefile
#obj-$(CONFIG_USB_GADGET) += gadget/ //把这个注释掉
编写代码如下
#include <linux/module.h>
#include <linux/configfs.h>
static struct config_item_type myconfig_type = { // 必须提供一个默认值
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem myconfig_subsys = {
.su_group = {
.cg_item = {
.ci_name = "myconfig", // 目录名
.ci_type = &myconfig_type,
},
},
};
static int __init myconfig_init(void)
{
int ret;
config_group_init(&myconfig_subsys.su_group);
mutex_init(&myconfig_subsys.su_mutex);
// 注册一个 configfs 文件系统
ret = configfs_register_subsystem(&myconfig_subsys);
if (ret) {
pr_err("Failed to register myconfig subsystem: %d\n", ret);
} else {
pr_info("myconfig subsystem registered successfully\n");
}
return ret;
}
static void __exit myconfig_exit(void)
{
configfs_unregister_subsystem(&myconfig_subsys);
pr_info("myconfig subsystem unregistered\n");
}
module_init(myconfig_init);
module_exit(myconfig_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("baron");
MODULE_DESCRIPTION("Simple ConfigFS Example: myconfig");
内核打印下面日志表示注册成功.
[ 0.143302] myconfig subsystem registered successfully
mount 到 /sys/kernel/config
路径
# mount -t configfs none /sys/kernel/config // 挂载到 /sys/kernel/config
#
# cd /sys/kernel/config/
# ls
myconfig // 注册的 configfs 文件系统.
3. populate_attrs
这个函数的功能是遍历 config_item_type
中的 ct_attrs
数组, 为每一个属性文件分配 configfs_dirent
数据结构并初始化.
configfs
中的属性文件和目录的创建是不一样的, 属性文件是不支持动态创建的, 需要提前想好, 当前目录下需要哪些属性文件, 然后提前放到 config_item_type
的 ct_attrs
数组里面.
注意了这里并没有设置属性文件的 inode 回调接口, 这里设置了 CONFIGFS_ITEM_ATTR
标志位. 在 lookup 检测这个标志位, 如果设置了这个标志位, 则会设置 inode 的回调函数, 不是 mkdir 的 looup 回调, 这里提一下, 后文详述.
// fs/configfs/dir.c
static int populate_attrs(struct config_item *item)
{
struct config_item_type *t = item->ci_type;
struct configfs_attribute *attr;
int error = 0;
int i;
if (!t)
return -EINVAL;
if (t->ct_attrs) { // 遍历 ct_attrs 数组, 为每一个属性文件分配 configfs_dirent 数据结构并初始化
for (i = 0; (attr = t->ct_attrs[i]) != NULL; i++) {
if ((error = configfs_create_file(item, attr)))
break;
}
}
if (error)
detach_attrs(item);
return error;
}
// 为属性文件分配 configfs_dirent 数据结构设置 CONFIGFS_ITEM_ATTR 标志位并初始化
int configfs_create_file(struct config_item * item, const struct configfs_attribute * attr)
{
struct dentry *dir = item->ci_dentry;
struct configfs_dirent *parent_sd = dir->d_fsdata;
umode_t mode = (attr->ca_mode & S_IALLUGO) | S_IFREG;
int error = 0;
mutex_lock_nested(&d_inode(dir)->i_mutex, I_MUTEX_NORMAL);
error = configfs_make_dirent(parent_sd, NULL, (void *) attr, mode, CONFIGFS_ITEM_ATTR);
mutex_unlock(&d_inode(dir)->i_mutex);
return error;
}
别忘了(后面会用):
configfs_attach_group
=configfs_create_dir
+populate_attrs
三、创建子目录
configfs 的核心功能是配置, 也就是能够动态的配创建并置子项, 所谓的子项包括主要包括三个他们分别是目录/文件/链接文件.首先来看一下子目录的创建, 文件系统中通过 mkdir 来创建子目录, 我们在 configfs 文件系统中 mkdir 的流程如下所示.
可以看出 mkdir 的过程会自动的帮助我们调用 configfs_attach_group 函数注册目录, 并且所属的属性文件分配 configfs_dirent 数据结构.我们只需要在 make_group 中填充我们需要的目录配置项就行了.优化前面的代码, 增加创建目录的功能吧.
#include <linux/module.h>
#include <linux/configfs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/fs.h>
static struct config_group *myconfig_make_group(struct config_group *group, const char *name)
{
struct config_group *new_group;
pr_info("Creating new group: %s\n", name);
/* 分配 config_group 内存 */
new_group = kzalloc(sizeof(struct config_group), GFP_KERNEL);
if (!new_group)
return ERR_PTR(-ENOMEM);
/* 初始化新组, 这里使用父目录的配置项, 通过这样的方式实现递归创建*/
config_group_init_type_name(new_group, name, group->cg_item.ci_type);
return new_group;
}
struct configfs_group_operations my_group_ops = {
.make_group = myconfig_make_group,
};
static struct config_item_type myconfig_type = {
.ct_owner = THIS_MODULE,
.ct_group_ops = &my_group_ops,
};
static struct configfs_subsystem myconfig_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "myconfig", // 目录名
.ci_name = "myconfig",
.ci_type = &myconfig_type,
},
},
};
static int __init myconfig_init(void)
{
int ret;
printk("%s\n", __func__);
config_group_init(&myconfig_subsys.su_group);
mutex_init(&myconfig_subsys.su_mutex);
// 注册一个 configfs 文件系统
ret = configfs_register_subsystem(&myconfig_subsys);
if (ret) {
pr_err("Failed to register myconfig subsystem: %d\n", ret);
} else {
pr_info("myconfig subsystem registered successfully\n");
}
return ret;
}
static void __exit myconfig_exit(void)
{
configfs_unregister_subsystem(&myconfig_subsys);
pr_info("myconfig subsystem unregistered\n");
}
module_init(myconfig_init);
module_exit(myconfig_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("baron");
MODULE_DESCRIPTION("Simple ConfigFS Example: myconfig");
验证结果:
# mount -t configfs none /sys/kernel/config/
# cd /sys/kernel/config/myconfig/
# ls
#
# mkdir dir
[ 60.577645] configfs_mkdir make group: dir
[ 60.581934] Creating new group: dir
#
# ls
dir
#
# cd dir/
#
# mkdir aaa
[ 241.614193] configfs_mkdir make group: aaa
[ 241.618429] Creating new group: aaa
# ls
aaa
四、创建属性文件
前面提到过属性文件的创建是不能动态创建的, 需要提前想好当前目录下需要哪些属性文件, 然后提前放到 config_item_type
的 ct_attrs
数组中
在 mkdir 的时候会调用自动 configfs_attach_group 接口, 这个接口中的 populate_attrs
会遍历config_item_type
的 ct_attrs
数组中每一个 attr
, 为他们置了 CONFIGFS_ITEM_ATTR
标志位, 并且分配 configfs_dirent
数据结构.
属性文件的底层回调接口是在读写的时候设置的, 在 open 文件的候, 会调用 configfs_lookup , 在这里设置属性文件的回调, 然后再 read/write 的的时候回调这里设置的回调接口. 流程如下图所示.
由于 write 和 read 基本上是一样的就不给出流程了, 在 configfs_create 中会回调 configfs_init_file 设置属性接口的操作函数.
static void configfs_init_file(struct inode * inode)
{
inode->i_size = PAGE_SIZE;
inode->i_fop = &configfs_file_operations;
}
const struct file_operations configfs_file_operations = {
.read = configfs_read_file,
.write = configfs_write_file,
.llseek = generic_file_llseek,
.open = configfs_open_file,
.release = configfs_release,
};
这整个流程都是自动进行的对于我们来说只需要填充数据结构然, 然后放到 ct_attrs 数组, 然后就会自动生成对应的属性文件, 读写的时候就会调用到对应的, show 和 store 接口.优化前面的接口添加属性文件吧.
#include <linux/module.h>
#include <linux/configfs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/fs.h>
static struct config_group *myconfig_make_group(struct config_group *group, const char *name)
{
struct config_group *new_group;
pr_info("Creating new group: %s\n", name);
/* 分配 config_group 内存 */
new_group = kzalloc(sizeof(struct config_group), GFP_KERNEL);
if (!new_group)
return ERR_PTR(-ENOMEM);
/* 初始化新组 */
config_group_init_type_name(new_group, name, group->cg_item.ci_type);
return new_group;
}
struct configfs_group_operations my_group_ops = {
.make_group = myconfig_make_group,
};
static ssize_t test_show(struct config_item *item, char *buf)
{
return snprintf(buf, PAGE_SIZE, "Hello, ConfigFS!\n");
}
// 写入 test 属性
static ssize_t test_store(struct config_item *item, const char *buf, size_t count)
{
pr_info("test 属性被写入: %s", buf);
return count;
}
// 定义 test 属性
static struct configfs_attribute test_attr = {
.ca_name = "test", // 属性名
.ca_mode = S_IRUGO | S_IWUSR, // 可读写权限
.show = test_show,
.store = test_store,
};
// 配置文件操作:显示和写入
static struct configfs_attribute *myconfig_attrs[] = {
&test_attr, // 将 test_attr 添加到属性数组
NULL, // 结束标志
};
static struct config_item_type myconfig_type = {
.ct_owner = THIS_MODULE,
.ct_group_ops = &my_group_ops,
.ct_attrs = myconfig_attrs, // 添加属性
};
static struct configfs_subsystem myconfig_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "myconfig", // 目录名
.ci_name = "myconfig",
.ci_type = &myconfig_type,
},
},
};
static int __init myconfig_init(void)
{
int ret;
printk("%s\n", __func__);
config_group_init(&myconfig_subsys.su_group);
mutex_init(&myconfig_subsys.su_mutex);
// 注册一个 configfs 文件系统
ret = configfs_register_subsystem(&myconfig_subsys);
if (ret) {
pr_err("Failed to register myconfig subsystem: %d\n", ret);
} else {
pr_info("myconfig subsystem registered successfully\n");
}
return ret;
}
static void __exit myconfig_exit(void)
{
configfs_unregister_subsystem(&myconfig_subsys);
pr_info("myconfig subsystem unregistered\n");
}
module_init(myconfig_init);
module_exit(myconfig_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("baron");
MODULE_DESCRIPTION("Simple ConfigFS Example: myconfig");
验证结果:
# mount -t configfs none /sys/kernel/config/
#
#
# cd /sys/kernel/config/myconfig/
#
# ls
test
#
# echo 123 > test
[ 44.786252] configfs_lookup: name:test // 这是我自己加的打印
[ 44.793639] test 属性被写入: 123
#
# cat test
[ 67.814740] configfs_lookup: name:test
Hello, ConfigFS!