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; void *d_fsdata; ...... };
dentry 是目录树的结构化表示, 用于描述文件系统中的路径信息, 它是文件路径的一个缓存, 记录了目录树中的父子关系, 用于高效地解析路径和快速定位文件.
1 2 3 4 5 6 7 struct inode { ...... umode_t i_mode; const struct inode_operations *i_op; const struct file_operations *i_fop; ...... };
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; struct configfs_subsystem *cg_subsys; struct config_group **default_groups; };
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; struct config_item *ci_parent; struct config_group *ci_group; struct config_item_type *ci_type; struct dentry *ci_dentry; }
4. config_item_type
他有两个部分的内容
目录操作 : ct_group_ops
用于描述目录的常见操作 mkdir, rmdir 等操作的回调接口, ct_item_ops
则是让目录也支持 show
和 store
的属性操作.
属性文件描述数组 : ct_attrs
则表示当前目录下的属性文件的数组.当前文件下的属性文件都保存在这个数组中.
1 2 3 4 5 6 struct config_item_type { struct module *ct_owner; struct configfs_item_operations *ct_item_ops; struct configfs_group_operations *ct_group_ops; 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 *); ssize_t (*store)(struct config_item *, const char *, size_t ); };
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_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
函数则用来设置对应目录 /文件 /链接文件 的回调函数.
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 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) return -ENOENT; if (d_really_is_positive (dentry)) return -EEXIST; sd = dentry->d_fsdata; inode = configfs_new_inode (mode, sd, dentry->d_sb); if (!inode) return -ENOMEM; p_inode = d_inode (dentry->d_parent); p_inode->i_mtime = p_inode->i_ctime = CURRENT_TIME; configfs_set_inode_lock_class (sd, inode); init (inode); d_instantiate (dentry, inode); 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 static int configfs_create_dir (struct config_item *item, struct dentry *dentry) { int error; umode_t mode = S_IFDIR| S_IRWXU | S_IRUGO | S_IXUGO; struct dentry *p = dentry->d_parent; BUG_ON (!item); error = configfs_dirent_exists (p->d_fsdata, dentry->d_name.name); if (unlikely (error)) return error; 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); 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, .setattr = configfs_setattr, }; 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 #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); 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 # # cd /sys/kernel/config/ # ls myconfig
3. populate_attrs
这个函数的功能是遍历 config_item_type
中的 ct_attrs
数组, 为每一个属性文件分配 configfs_dirent
数据结构并初始化 .
configfs
中的属性文件和目录的创建是不一样的, 属性文件是不支持动态创建的 , 需要提前想好, 当前目录下需要哪些属性文件, 然后提前放到 config_item_type
的 ct_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 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) { 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; } 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); 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); 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_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 设置属性接口的操作函数.
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); 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" ); } static ssize_t test_store (struct config_item *item, const char *buf, size_t count) { pr_info ("test 属性被写入: %s" , buf); return count; } 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, 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); 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!