Configfs - 用户空间驱动的内核对象配置

embedded/2024/12/30 19:26:36/

什么是configfs

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

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

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

sysfs 和 configfs 可以且应该共存于同一系统中。两者不可互相替代。

使用configfs

configfs 可以编译为模块或编译到内核中。你可以这样访问它:

mount -t configfs none /config

除非还加载了客户端模块,否则 configfs 树将为空。这些模块将其项类型作为子系统注册到 configfs。加载客户端子系统后,它将作为子目录(或多个子目录)出现在 /config 下。与 sysfs 一样,无论是否安装在 /config 上,configfs 树始终存在。

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

configfs 属性有两种类型:

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

  • 二进制属性,与 sysfs 二进制属性有些相似,但在语义上略有不同。PAGE_SIZE 限制不适用,但整个二进制项必须适合单个内核 vmalloc 缓冲区。来自用户空间的 write(2) 调用被缓冲,属性的 write_bin_attribute 方法将在最终关闭时调用,因此用户空间必须检查 close(2) 的返回代码以验证操作是否成功完成。为了避免恶意用户 OOMing 内核,每个二进制属性都有一个最大缓冲区值。

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

配置FakeNBD:一个示例

假设有一个网络块设备 (NBD) 驱动程序,它允许您访问远程块设备。称之为 FakeNBD。FakeNBD 使用 configfs 进行配置。显然,系统管理员会使用一个不错的程序来配置 FakeNBD,但该程序必须以某种方式将其告知驱动程序。这就是 configfs 的作用所在。

当 FakeNBD 驱动程序被加载时,它会向 configfs 注册自身。readdir(3) 可以正常看到这种情况:

# ls /config
fakenbd

可以使用 mkdir(2) 创建 fakenbd 连接。名称是任意的,但该工具可能会使用该名称。可能是 uuid 或磁盘名称:

# mkdir /config/fakenbd/disk1
# ls /config/fakenbd/disk1
target device rw

target 属性包含 FakeNBD 将要连接的服务器的 IP 地址。device 属性是服务器上的设备。可以预见的是,rw 属性决定了连接是只读的还是读写的:

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

就是这样。就这些了。现在设备已配置完毕,通过 shell 即可。

使用configfs进行编码

configfs 中的每个对象都是一个 config_item。config_item 反映子系统中的对象。它具有与该对象上的值匹配的属性。configfs 处理该对象及其属性的文件系统表示,允许子系统忽略除基本显示/存储交互之外的所有内容。

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

子系统是客户端模块的顶层。在初始化期间,客户端模块将子系统注册到 configfs,子系统作为目录出现在 configfs 文件系统的顶层。子系统也是一个 config_group,可以执行 config_group 可以执行的所有操作。

struct config_item(配置项)

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 中。通常,子系统希望该项显示和/或存储属性等。为此,它需要一个类型。

config_item_type

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 的引用计数达到零时,将调用此方法。

configfs_attribute

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) 操作时,都会调用该方法。

configfs_bin_attribute

struct configfs_bin_attribute {struct configfs_attribute       cb_attr;void                            *cb_private;size_t                          cb_max_size;
};

当需要使用二进制 blob 作为项目 configfs 目录中文件的内容出现时,将使用二进制属性。为此,将二进制属性添加到以 NULL 结尾的数组 config_item_type->ct_bin_attrs,并且项目出现在 configfs 中,属性文件将与 configfs_bin_attribute->cb_attr.ca_name 文件名一起出现。configfs_bin_attribute->cb_attr.ca_mode 指定文件权限。cb_private 成员供驱动程序使用,而 cb_max_size 成员指定要使用的最大 vmalloc 缓冲区量。

如果二进制属性可读,并且 config_item 提供了 ct_item_ops->read_bin_attribute() 方法,则每当用户空间请求对属性执行 read(2) 操作时,都会调用该方法。对于 write(2) 操作,情况正好相反。读取/写入操作被缓冲,因此只会发生一次读取/写入操作;属性本身无需关心它。

struct config_group(配置组)

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

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,

config_group 结构包含一个 config_item。正确配置该项目意味着组可以作为其自身的项目。但是,它可以做更多:它可以创建子项目或组。这是通过组的 config_item_type 上指定的组操作来实现的:

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);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()。其他所有操作均相同,使用组上的 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() 为空,因此不会失败。调用 rmdir(2) 时,configfs 将从文件系统树中删除该项目(假设它没有子项来保持忙碌)。子系统负责对此做出响应。如果子系统在其他线程中引用了该项目,则内存是安全的。该项目实际上从子系统的使用中消失可能需要一些时间。但它已从 configfs 中消失。

调用 drop_item() 时,项目的链接已被拆除。它不再引用其父级,也不在项目层次结构中。如果客户端需要在拆除之前进行一些清理,子系统可以实现 ct_group_ops->disconnect_notify() 方法。该方法在 configfs 从文件系统视图中删除项目之后但在从其父组中删除项目之前调用。与 drop_item() 一样,disconnect_notify() 为空且不能失败。客户端子系统不应在此处删除任何引用,因为它们仍必须在 drop_item() 中执行此操作。

当 config_group 仍有子项时,无法将其删除。这是在 configfs rmdir(2) 代码中实现的。不会调用 ->drop_item(),因为项尚未删除。rmdir(2) 将失败,因为目录不为空。

configfs_subsystem

子系统必须注册自身,通常在 module_init 时。这会告诉 configfs 让子系统出现在文件树中:

struct configfs_subsystem {struct config_group     su_group;struct mutex            su_mutex;
};int configfs_register_subsystem(struct configfs_subsystem *subsys);
void configfs_unregister_subsystem

子系统由顶层 config_group 和互斥锁组成。子 config_items 就是在组内创建的。对于子系统,该组通常是静态定义的。在调用 configfs_register_subsystem() 之前,子系统必须通过常用的 group _init() 函数初始化组,并且还必须初始化互斥锁。

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

一个例子

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

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

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

子系统可以浏览 cg_children 列表和 ci_parent 指针来查看子系统创建的树。这可能会与 configfs 对层次结构的管理发生争用,因此 configfs 使用子系统互斥锁来保护修改。每当子系统想要浏览层次结构时,它都必须在子系统互斥锁的保护下进行。

当新分配的项目尚未链接到此层次结构时,子系统将无法获取互斥锁。同样,当删除的项目尚未取消链接时,它将无法获取互斥锁。这意味着当项目位于 configfs 中时,项目的 ci_parent 指针永远不会为 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() 方法存在,则可以调用 symlink(2),并以 config_item 作为链接源。这些链接仅允许在 configfs config_items 之间进行。任何在 configfs 文件系统之外的 symlink(2) 尝试都将被拒绝。

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

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

当 config_item 链接到其他项时,无法将其删除;当有项链接到 config_item 时,也无法将其删除。configfs 中不允许有悬空符号链接。

自动创建子组

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

configfs 提供了一种方法,在创建父组时,会自动在父组中创建一个或多个子组,而不是让其中某些项目的行为与其他项目不同。因此,mkdir(“parent”) 会生成“parent”、“parent/subgroup1”直至“parent/subgroupN”。现在可以在“parent/subgroup1”中创建类型 1 的项目,在“parent/subgroupN”中创建类型 N 的项目。

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

configfs 子系统通过使用 configfs_add_default_group() 函数将默认组添加到父 config_group 结构来指定默认组。每个添加的组都会与父组同时填充到 configfs 树中。同样,它们也会与父组同时被删除。不提供额外通知。当 ->drop_item() 方法调用通知子系统父组即将消失时,这也意味着与该父组关联的每个默认组子组都会消失。

因此,无法通过 rmdir(2) 直接删除默认组。当父组上的 rmdir(2) 检查子组时,也不会考虑默认组。

依赖子系统

有时其他驱动程序依赖于特定的 configfs 项。例如,ocfs2 挂载依赖于心跳区域项。如果使用 rmdir(2) 删除该区域项,则 ocfs2 挂载必须出现 BUG 或变为只读。不开心。

configfs 提供了两个额外的 API 调用:configfs_depend_item() 和 configfs_undepend_item()。客户端驱动程序可以对现有项调用 configfs_depend_item() 来告知 configfs 它被依赖。然后 configfs 将从 rmdir(2) 返回该项的 -EBUSY。当该项不再被依赖时,客户端驱动程序将对其调用 configfs_undepend_item()。

这些 API 不能在任何 configfs 回调下调用,因为它们会发生冲突。它们可以阻塞和分配。客户端驱动程序可能不应该出于自己的意愿调用它们。相反,它应该提供外部子系统调用的 API。

这是如何工作的?想象一下 ocfs2 挂载过程。挂载时,它会请求一个心跳区域项。这是通过调用心跳代码来完成的。在心跳代码中,会查找区域项。在这里,心跳代码会调用 configfs_depend_item()。如果成功,那么心跳就知道该区域可以安全地提供给 ocfs2。如果失败,它无论如何都会被拆除,心跳可以优雅地传递错误。


http://www.ppmy.cn/embedded/149736.html

相关文章

使用 VSCode 学习与实践 LaTeX:从插件安装到排版技巧

文章目录 背景介绍编辑器编译文件指定输出文件夹 usepackagelatex 语法列表插入图片添加参考文献 背景介绍 最近在写文章,更喜欢latex的论文引用。然后开始学习 latex。 编辑器 本文选择vscode作为编辑器,当然大家也可以尝试overleaf。 overleaf 有网…

SPI实验 LED数码管

LED数码管(LED Segment Displays)是由8个发光二极管构成,并按照一定的图形及排列封装在一起的显示器件。其中7个LED构成7笔字形,1个LED构成小数点(固有时成为八段数码管)。 LED数码管有两大类,…

【国产NI替代】基于全国产FPGA的16振动+2转速+8路IO口输入输出(24bits)256k采样率,高精度终端采集板卡

16振动2转速8路IO口输入输出(24bits) 高精度终端采集板卡 采用AG16KF256国产FPGA的硬件架构,外扩 16MB的SDRAM做为千兆网的发送缓存,比起同 款带ARM的硬件架构,纯FPGA的架构虽然边缘 计算能力不如ARM,但…

rust教程 第一章 —— 初识rust

文章目录 一、前言二、Rust简介三、安装Rust编译器四、第一个Rust程序五、 IDE环境六、初识包管理七、总结 本系列文章已升级、转移至我的自建站点中,本章原文为:初识rust 一、前言 近些年来不断有新的语言崛起,比如当下非常火的go语言&am…

1.Occ-基础部分

Occ-几何建模,几何算法,图形渲染,应用框架集大成者 1.基础类型 The primitive types are predefined in the language and they are manipulated by value. Standard_Boolean is used to represent logical data. It may have only two va…

面试场景题系列:设计支付系统

从网约车、旅游、外卖到电商或者医疗保健,幕后运行的支付系统让所有的经济活动变成可能。近年来,设计一个可靠、可扩展、灵活的支付系统成为流行的系统设计面试题。 什么是支付系统?维基百科上是这么说的,“支付系统是通过转移货…

ORB-SLAM2源码学习:System.cc: System::TrackStereo、TrackRGBD、TrackMonocular追踪器接口

前言 在之前的单目实例中,我们在初始化一个SLAM的系统后,系统处理了接受的文件数据,初始化并运行了各个线程,之后就要给追踪器传入图像进行整个SLAM的流程了。 1.函数声明 追踪器的函数定义在System源文件下,针对不…

python进阶-06-Selenium一个真实项目实战,还有FastAPI背景介绍

python进阶-06-Selenium一个真实项目实战和FastAPI背景介绍 一.说明 截止到目前我们已经掌握了python爬虫这一基础的基本知识,并从另外一个方向来利用爬虫帮助我们实现办公自动化,实现自动帮我们处理日常工作,这次我们利用Selenium+FastAPI搭建一个真实项目,并部署到服务…