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 | ls /config |
可以使用 mkdir(2)创建 fakenbd 连接。 名称是任意的, 但工具可能会对名称进行一些处理。 也许它是一个 UUID 或磁盘名称:
1 | mkdir /config/fakenbd/disk1 |
target 属性包含 FakeNBD 将连接到的服务器的 IP 地址。 device 属性是服务器上的设备。 可预测的是, rw 属性确定连接是只读还是读写。
1 | echo 10.0.0.1 > /config/fakenbd/disk1/target |
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 | 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 | struct configfs_item_operations { |
config_item_type 的最基本功能是定义可以在 config_item 上执行的操作。 所有动态分配的项目都需要提供 ct_item_ops->release()方法。 当 config_item 的引用计数达到零时, 将调用此方法。
4.3 struct configfs_attribute
1 | struct configfs_attribute { |
当 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 | struct configfs_attribute { |
当需要使用二进制数据块作为文件内容显示在项目的 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 | struct config_group { |
config_group 结构包含一个 config_item。 适当配置该项意味着组可以作为一个独立的项进行操作。 然而, 它还可以做更多的事情: 它可以创建子项或子组。 这是通过在组的 config_item_type 上指定的组操作来实现的。
1 | struct configfs_group_operations { |
组通过提供 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 | struct configfs_subsystem { |
一个子系统由一个顶级的 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。
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在路上 - 博客园