linux usb 驱动 - configfs 文件系统

    configfs 是 Linux 内核中的一个虚拟文件系统(vfs),他和 sysfs 类似, 都是以目录文件的形式导出到用户空间和用户交互, 虚拟表示 configfs 中的"目录"只是一个用户界面的表现形式, 没有实际的物理存储空间分配, 目录结构只是内核对象的一种层次化展示方式. 每个"目录"实际上映射到一个 config_group, 目录操作被转换为对应的 config_group 操作, 比如 mkdir 实际上是调用 make_group 来创建新的内核配置项. 文件系统操作都被转换为对应的内核对象操作,通过这些回调接口来实现用户空间和内核空间的交互.

    由于 configfs 是 usb hcd 驱动的前置部分, 因此虽然是文件系统的内容, 但是还是分类 usb 在驱动中. 在内核中 configfs 几乎只用于配置 usb.

这种设计的优点:

  • 提供了一个直观的用户接口(通过目录结构), linux 和核心理念一切皆文件
  • 实现了用户空间和内核空间的优雅交互
  • 便于管理和配置内核对象

这就是为什么它叫 configfs (配置文件系统) 而不是普通的文件系统,它的主要目的是配置而不是存储.下面命令用于挂载 configfs 文件系统.

1
mount -t configfs none /sys/kernel/config

    在 vfs(虚拟文件系统) 文件系统中, 文件和目录的描述主要由 dentry(目录项) 和 inode(索引节点) 描述. 目录/文件 = dentry + inode

1
2
3
4
5
6
7
struct dentry {

......
struct inode *d_inode; // 其关联的 inode
void *d_fsdata; // 在configfs 中指向 configfs_dirent
......
};

    dentry 是目录树的结构化表示, 用于描述文件系统中的路径信息, 它是文件路径的一个缓存, 记录了目录树中的父子关系, 用于高效地解析路径和快速定位文件.

1
2
3
4
5
6
7
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 成员变量表明了这是个目录还是文件, 以及操作权限, 所以注册目录还是文件就是设置 i_mode 这个成员变量, 无论是目录还是文件对应的底层操作都是一样的. 只是回调的接口不一样而已.

下表描述了 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 成员就是根目录.

1
2
3
4
struct configfs_subsystem {
struct config_group su_group; // 根目录
struct mutex su_mutex;
};

2. config_group

    用来描述 configfs 中的目录管理结构, 包含真正的目录回调接口的描述结构 cg_item 和 default_groups 等数据结构, 用来管理当前目录以及当前目录包含的子目录. 其中 default_groups 用来描述当前目录下的子目录的管理结构.

1
2
3
4
5
6
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 等操作最终都被映射成对这个数据结构的操作.

1
2
3
4
5
6
7
8
9
10
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 则是让目录也支持 showstore 的属性操作.
  • 属性文件描述数组: ct_attrs 则表示当前目录下的属性文件的数组.当前文件下的属性文件都保存在这个数组中.
1
2
3
4
5
6
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

    属性文件回述结构, 每一个结构包含一个属性文件的名字和回调函数.

1
2
3
4
5
6
7
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 中的对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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_groupconfig_itemconfig_item_typeconfigfs_attribute 以及分配 configfs_direntdentryinode 等数据结构, 并将其关联起来.调用流程如下图所示.

    整个注册过程大体分可以分为两个步骤, 关键的函数有三个 configfs_create configfs_create_dirpopulate_attrs. .

1) configfs_create

    configfs_create 该接口用于创建 configfs 文件系统的的目录/文件/链接文件, 具体创建的是什么由 mode 中的标志位决定, 传入的 init 函数则用来设置对应目录/文件/链接文件的回调函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//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) 对文件的读权限和执行权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//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 接口用于设置目录操作的底层的回调接口.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 文件系统, 所以要把原来默认的去掉.

1
2
// drivers/usb/Makefile
#obj-$(CONFIG_USB_GADGET) += gadget/ //把这个注释掉

编写代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#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");

内核打印下面日志表示注册成功.

1
[    0.143302] myconfig subsystem registered successfully

mount 到 /sys/kernel/config 路径

1
2
3
4
5
# 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_typect_attrs 数组里面.

    注意了这里并没有设置属性文件的 inode 回调接口, 这里设置了 CONFIGFS_ITEM_ATTR 标志位. 在 lookup 检测这个标志位, 如果设置了这个标志位, 则会设置 inode 的回调函数, 不是 mkdir 的 looup 回调, 这里提一下, 后文详述.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 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 中填充我们需要的目录配置项就行了.优化前面的代码, 增加创建目录的功能吧.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#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");

验证结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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_typect_attrs 数组中

    在 mkdir 的时候会调用自动 configfs_attach_group 接口, 这个接口中的 populate_attrs 会遍历config_item_typect_attrs 数组中每一个 attr, 为他们置了 CONFIGFS_ITEM_ATTR 标志位, 并且分配 configfs_dirent 数据结构.

    属性文件的底层回调接口是在读写的时候设置的, 在 open 文件的候, 会调用 configfs_lookup , 在这里设置属性文件的回调, 然后再 read/write 的的时候回调这里设置的回调接口. 流程如下图所示.

    由于 write 和 read 基本上是一样的就不给出流程了, 在 configfs_create 中会回调 configfs_init_file 设置属性接口的操作函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
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 接口.优化前面的接口添加属性文件吧.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

#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");

验证结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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!