LV06-12-ConfigFS-04-参考资料解析

这里有一篇内核提供的configfs参考文档,我们来学习一下。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、参考资料

参考的文档可以看这里:configfs.txt - Documentation/filesystems/configfs/configfs.txt - Linux source code v4.19.17

二、内容翻译

1. 什么是 configfs?

configfs 是一种基于 RAM 的文件系统, 提供了与 sysfs 功能相反的功能。 sysfs 是内核对象的基于文件系统的视图, 而 configfs 是内核对象(或 config_items) 的基于文件系统的管理器。

使用 sysfs 时, 在内核中创建一个对象(例如, 当发现设备时) , 并将其注册到 sysfs 中。 然后, 它的属性会出现在 sysfs 中, 允许用户空间通过 readdir(3)/read(2)读取属性。 它可能允许通过 write(2)修改某些属性。 重要的是, 对象在内核中创建和销毁, 内核控制 sysfs 表示的生命周期, 而 sysfs 只是对所有这些的一种窗口。

通过显式的用户空间操作(例如 mkdir(2)) , 可以创建一个 configfs config_item。 通过 rmdir(2)销毁它。属性在 mkdir(2)时出现, 并且可以通过 read(2)和 write(2)读取或修改。 与 sysfs 类似, readdir(3)查询项目和/或属性的列表。 可以使用 symlink(2)将项目组合在一起。 与 sysfs 不同, 表示的生命周期完全由用户空间驱动。 支持项目的内核模块必须响应此操作。

sysfs 和 configfs 可以并且应该在同一系统上同时存在。 它们不是彼此的替代品。

2. 使用 configfs

configfs 可以作为模块编译或集成到内核中。 您可以通过以下方式访问它:

1
mount -t configfs none /config

除非也加载了客户端模块, 否则 configfs 树将为空。 这些模块将其项目类型注册为 configfs 的子系统。一旦加载了客户端子系统, 它将显示为/config 下的一个或多个子目录。 与 sysfs 一样, configfs 树始终存在,无论是否挂载在/config 上。

可以通过 mkdir(2)创建项目。 项目的属性也将同时出现。 readdir(3)可以确定属性是什么, read(2)可以查询其默认值, write(2)可以存储新值。 不要在一个属性文件中混合多个属性。

configfs 有两种类型的属性:

普通属性(Normal attributes)类似于 sysfs 属性, 是小型的 ASCII 文本文件, 最大大小为一页(PAGE_SIZE,在 i386 上为 4096)。 最好每个文件只使用一个值, 并且与 sysfs 相同的注意事项也适用。 configfs 期望 write(2)一次存储整个缓冲区。 当写入普通 configfs 属性时, 用户空间进程应首先读取整个文件, 修改要更改的部分, 然后将整个缓冲区写回。

二进制属性(Binary attributes)与 sysfs 二进制属性类似, 但语义上有一些细微的变化。 不适用 PAGE_SIZE限制, 但整个二进制项必须适应单个内核 vmalloc 的缓冲区。 来自用户空间的 write(2)调用是缓冲的, 并且在最终关闭时将调用属性的 write_bin_attribute 方法, 因此用户空间必须检查 close(2)的返回代码以验证操作是否成功完成。 为避免恶意用户 OOM(Out of Memory) 内核, 有每个二进制属性的最大缓冲区值。

当需要销毁项目时, 使用 rmdir(2)将其删除。 如果任何其他项目通过 symlink(2)链接到它, 则无法销毁该项目。 可以使用 unlink(2)删除链接。

3. 配置 FakeNBD:一个示例

假设有一个网络块设备( Network Block Device, NBD) 驱动程序, 允许您访问远程块设备。 将其称为FakeNBD。 FakeNBD 使用 configfs 进行配置。 显然, 将有一个方便的程序供系统管理员使用来配置 FakeNBD,但某种方式下, 该程序必须告知驱动程序。 这就是 configfs 的用武之地。

加载 FakeNBD 驱动程序时, 它会向 configfs 注册自己。 readdir(3)可以看到这一点:

1
2
# ls /config
fakenbd

可以使用 mkdir(2)创建 fakenbd 连接。 名称是任意的, 但工具可能会对名称进行一些处理。 也许它是一个 UUID 或磁盘名称:

1
2
3
# mkdir /config/fakenbd/disk1
# ls /config/fakenbd/disk1
target device rw

target 属性包含 FakeNBD 将连接到的服务器的 IP 地址。 device 属性是服务器上的设备。 可预测的是, rw 属性确定连接是只读还是读写。

1
2
3
# echo 10.0.0.1 > /config/fakenbd/disk1/target
# echo /dev/sda1 > /config/fakenbd/disk1/device
# echo 1 > /config/fakenbd/disk1/rw

target 属性包含 FakeNBD 将连接到的服务器的 IP 地址。 device 属性是服务器上的设备。 可预测的是,rw 属性确定连接是只读还是读写。

4. 使用 configfs 进行编码

configfs 中的每个对象都是一个 config_item。 config_item 反映了子系统中的一个对象。 它具有与该对象上的值相匹配的属性。 configfs 处理该对象及其属性的文件系统表示, 使得子系统只需关注基本的show/store 交互。

项目是在 config_group 内创建和销毁的。 组是共享相同属性和操作的项目集合。 项目通过 mkdir(2)创建, 并通过 rmdir(2)移除, 但 configfs 会处理这些操作。 组具有一组操作来执行这些任务。

子系统是客户端模块的顶层。 在初始化过程中, 客户端模块向 configfs 注册子系统, 该子系统将在configfs 文件系统的顶部显示为一个目录。 子系统也是一个 config_group, 并且可以执行 config_group 的所有功能。

4.1 struct config_item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct config_item {
char *ci_name;
char ci_namebuf[UOBJ_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;
};

void config_item_init(struct config_item *);
void config_item_init_type_name(struct config_item *,
const char *name,
struct config_item_type *type);
struct config_item *config_item_get(struct config_item *);
void config_item_put(struct config_item *);

通常, struct config_item 被嵌入在一个容器结构中, 这个结构实际上代表了子系统正在做的事情。 该结构中的 config_item 部分是对象与 configfs 进行交互的方式。

无论是在源文件中静态定义还是由父 config_group 创建, config_item 都必须调用其中一个_init()函数。这将初始化引用计数并设置适当的字段。

所有使用 config_item 的用户都应该通过 config_item_get()对其进行引用, 并在完成后通过config_item_put()释放引用。

单独一个 config_item 不能做更多的事情, 只能出现在 configfs 中。 通常, 子系统希望该项显示和/或存储属性, 以及其他一些操作。 为此, 它需要一个类型。

4.2 struct config_item_type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct configfs_item_operations {
void (*release)(struct config_item *);
int (*allow_link)(struct config_item *src,
struct config_item *target);
void (*drop_link)(struct config_item *src,
struct config_item *target);
};

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;
struct configfs_bin_attribute **ct_bin_attrs;
};

config_item_type 的最基本功能是定义可以在 config_item 上执行的操作。 所有动态分配的项目都需要提供 ct_item_ops->release()方法。 当 config_item 的引用计数达到零时, 将调用此方法。

4.3 struct configfs_attribute

1
2
3
4
5
6
7
struct configfs_attribute {
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);
};

当 config_item 希望将属性显示为项目的 configfs 目录中的文件时, 它必须定义一个描述该属性的configfs_attribute。 然后将属性添加到以 NULL 结尾的 config_item_type->ct_attrs 数组中。 当项目出现在configfs 中时, 属性文件将显示为 configfs_attribute->ca_name 文件名。 configfs_attribute->ca_mode 指定文件权限。

如果属性是可读的并提供了一个->show 方法, 每当用户空间请求对属性进行 read(2)时, 该方法将被调用。 如果属性是可写的并提供了一个->store 方法, 每当用户空间请求对属性进行 write(2)时, 该方法将被调用。

4.4 struct configfs_bin_attribute

1
2
3
4
5
struct configfs_attribute {
struct configfs_attribute cb_attr;
void *cb_private;
size_t cb_max_size;
};

当需要使用二进制数据块作为文件内容显示在项目的 configfs 目录中时, 可以使用二进制属性(binaryattribute) 。 为此, 将二进制属性添加到以 NULL 结尾的 config_item_type->ct_bin_attrs 数组中, 当项目出现在 configfs 中时, 属性文件将显示为 configfs_bin_attribute->cb_attr.ca_name 文件名。configfs_bin_attribute->cb_attr.ca_mode 指定文件权限。

b_private 成员供驱动程序使用, 而 cb_max_size 成员指定要使用的 vmalloc 缓冲区的最大大小。

如果二进制属性是可读的, 并且 config_item 提供了 ct_item_ops->read_bin_attribute()方法, 那么每当用户空间请求对属性进行 read(2)时, 该方法将被调用。 对于 write(2), 也会发生相反的情况。 读取/写入是缓冲的, 因此只会发生单个读取/写入; 属性本身不需要关心这一点。

4.5 struct config_group

config_item 不能独立存在。 创建 config_item 的唯一方法是通过在 config_group 上执行 mkdir(2)操作。这将触发创建子项。

1
2
3
4
5
6
7
8
9
10
11
12
struct config_group {
struct config_item cg_item;
struct list_head cg_children;
struct configfs_subsystem *cg_subsys;
struct list_head default_groups;
struct list_head group_entry;
};

void config_group_init(struct config_group *group);
void config_group_init_type_name(struct config_group *group,
const char *name,
struct config_item_type *type);

config_group 结构包含一个 config_item。 适当配置该项意味着组可以作为一个独立的项进行操作。 然而, 它还可以做更多的事情: 它可以创建子项或子组。 这是通过在组的 config_item_type 上指定的组操作来实现的。

1
2
3
4
5
6
7
8
9
10
11
struct configfs_group_operations {
struct config_item *(*make_item)(struct config_group *group,
const char *name);
struct config_group *(*make_group)(struct config_group *group,
const char *name);
int (*commit_item)(struct config_item *item);
void (*disconnect_notify)(struct config_group *group,
struct config_item *item);
void (*drop_item)(struct config_group *group,
struct config_item *item);
};

组通过提供 ct_group_ops->make_item()方法来创建子项。 如果提供了该方法, 它将在组的目录中的mkdir(2)操作中调用。 子系统分配一个新的 config_item(或更常见的是其容器结构) , 对其进行初始化,并将其返回给 configfs。 然后, configfs 将填充文件系统树以反映新的项。

如果子系统希望子项本身成为一个组, 子系统将提供 ct_group_ops->make_group()。 其他操作与之相同,在组上使用组的_init()函数。

最后, 当用户空间对项或组调用 rmdir(2)时, 将调用 ct_group_ops->drop_item()。 由于 config_group 也是一个 config_item, 因此不需要单独的 drop_group()方法。 子系统必须对在项分配时初始化的引用执行config_item_put()。 如果子系统没有其他工作要执行, 可以省略 ct_group_ops->drop_item()方法, configfs将代表子系统对项执行 config_item_put()。

重要提示: drop_item()是 void 类型的, 因此无法失败。 当调用 rmdir(2)时, configfs 将从文件系统树中删除该项(假设没有子项) 。 子系统负责对此作出响应。 如果子系统在其他线程中引用该项, 则内存是安全的。 实际上, 该项从子系统的使用中消失可能需要一些时间。 但它已经从 configfs 中消失了。

当调用 drop_item()时, 项的链接已经被拆除。 它不再引用其父项, 并且在项层次结构中没有位置。 如果客户端在此拆除发生之前需要进行一些清理工作, 子系统可以实现 ct_group_ops->disconnect_notify()方法。 该方法在 configfs 将项从文件系统视图中删除之后、 但在将项从其父组中删除之前调用。 与 drop_item()一样, disconnect_notify()是 void 类型的, 不会失败。 客户端子系统不应在此处删除任何引用, 因为它们仍然必须在 drop_item()中执行。

只要 config_group 仍然具有子项, 就无法删除它。 这在 configfs 的 rmdir(2)代码中实现。 不会调用->drop_item(), 因为项尚未被删除。 rmdir(2)将失败, 因为目录不为空。

4.6 struct configfs_subsystem

子系统通常在 module_init 时间注册自身。 这告诉 configfs 将子系统显示在文件树中。

1
2
3
4
5
6
7
struct configfs_subsystem {
struct config_group su_group;
struct mutex su_mutex;
};

int configfs_register_subsystem(struct configfs_subsystem *subsys);
void configfs_unregister_subsystem(struct configfs_subsystem *subsys);

一个子系统由一个顶级的 config_group 和一个互斥锁组成。 config_group 是创建子 config_item 的地方。对于子系统来说, 这个组通常是静态定义的。 在调用 configfs_register_subsystem()之前, 子系统必须通过常规的 group_init()函数对组进行初始化, 并且还必须初始化互斥锁。

当注册调用返回时, 子系统将处于活动状态, 并且将通过 configfs 可见。 此时, 可以调用 mkdir(2), 子系统必须准备好接收该调用。

5. 示例

这些基本概念的最佳示例是在 configfs_sample.c - samples/configfs/configfs_sample.c - Linux source code v4.19.17 中的 simple_children 子系统/组和simple_child 项。 它展示了一个简单的对象, 显示和存储属性, 以及一个简单的组来创建和销毁这些子项。

层次结构导航和子系统互斥锁

configfs 提供了一个额外的功能。 由于 config_groups 和 config_items 出现在文件系统中, 它们按层次结构排列。 子系统永远不会触及文件系统的部分, 但子系统可能对此层次结构感兴趣。 因此, 层次结构通过config_group->cg_children 和 config_item->ci_parent 结构成员进行镜像。

子系统可以通过 cg_children 列表和 ci_parent 指针遍历子系统创建的树。 这可能与 configfs 对层次结构的管理发生竞争, 因此 configfs 使用子系统互斥锁来保护修改操作。 每当子系统想要遍历层次结构时, 必须在子系统互斥锁的保护下进行。

在新分配的项尚未链接到该层次结构时, 子系统将无法获取互斥锁。 类似地, 在放弃项尚未取消链接时, 它也无法获取互斥锁。 这意味着项的 ci_parent 指针在项位于 configfs 中时永远不会为 NULL, 并且项仅在其父项的 cg_children 列表中存在相同的时间段。 这允许子系统在持有互斥锁时信任 ci_parent 和cg_children。

通过 symlink(2)进行项聚合

configfs 通过 group->item 父/子关系提供了一个简单的组。 然而, 通常需要在父/子连接之外进行聚合。这是通过 symlink(2)实现的。

config_item 可以提供 ct_item_ops->allow_link()和 ct_item_ops->drop_link()方法。 如果存在->allow_link()方法, 可以使用 config_item 作为链接源调用 symlink(2)。 这些链接仅允许在 configfs config_item 之间创建。任何在 configfs 文件系统之外的 symlink(2)尝试都将被拒绝。

调用 symlink(2)时, 将使用源 config_item 的->allow_link()方法和自身和目标项作为参数。 如果源项允许链接到目标项, 则返回 0。 如果源项只希望链接到某种类型的对象(例如, 在其自己的子系统中) , 则可以拒绝链接。

在符号链接上调用 unlink(2)时, 将通过->drop_link()方法通知源项。 与->drop_item()方法一样, 这是一个无返回值的函数, 无法返回失败。 子系统负责响应该更改。

在任何项链接到其他项时, 无法删除 config_item, 也无法在有项链接到它时删除 config_item。在 configfs中, 不允许存在悬空的符号链接。

自动创建的子组

新的 config_group 可能希望具有两种类型的子 config_item。 虽然可以通过->make_item()中的魔术名称来编码这一点, 但更明确的做法是让用户空间看到这种分歧的方法。

configfs 提供了一种方法, 通过该方法可以在父级创建时自动在其中创建一个或多个子组。 因此, mkdir(“parent”)将导致创建”parent”、 “parent/subgroup1”, 一直到”parent/subgroupN”。 类型为 1 的项现在可以在”parent/subgroup1”中创建, 类型为 N 的项可以在”parent/subgroupN”中创建。

这些自动创建的子组, 或默认组, 不排除父组的其他子项。 如果存在 ct_group_ops->make_group(), 可以直接在父组上创建其他子组。

通过向父 config_group 结构添加它们, configfs 子系统可以指定默认组。 这是一个关于 configfs 的 C 代码示例, 它展示了 configfs 的一些基本概念和用法。

首先, 在代码中定义了一个名为”simple_children”的子系统/组和一个名为”simple_child”的项。 这个简单的对象展示了如何显示和存储属性, 并且使用一个简单的组来创建和销毁这些子项。

configfs 中的子系统和组是以层次结构排列的, 可以通过 config_group->cg_children 和config_item->ci_parent 来遍历子系统创建的树。 为了保护修改操作, configfs 使用了子系统互斥锁。

在代码中还使用了 symlink(2)函数来创建项之间的链接。 config_item 可以提供 allow_link()和 drop_link()方法来控制链接的创建和删除。 使用 symlink(2)函数创建的链接只允许在 configfs 中的 config_item 之间创建, 对于 configfs 文件系统之外的 symlink(2)尝试会被拒绝。

此外, 代码中还介绍了自动创建的子组的概念。 通过在父级创建时自动创建一个或多个子组, 可以更方便地组织 config_item。 这些自动创建的子组不会排除父组的其他子项。

这只是一个简单的示例, 用于介绍 configfs 的基本概念和用法。 实际使用 configfs 时, 可能会有更复杂的场景和用法。

参考资料

configfs-用户空间控制的内核对象配置 - abin在路上 - 博客园

14_configfs的使用与内部机制 — Linux设备驱动开发教程中心

Welcome to 100ask’s documentation — Linux设备驱动开发教程中心