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)查询items和/或属性的列表。可以使用符号链接(2)将项目分组在一起。与sysfs不同,configfs的生命周期完全由用户空间驱动。支持这些项的内核模块必须对此做出响应。

sysfs和configfs可以也应该同时存在于同一个系统上。它们中的一个不是另一个的替代品。

使用configfs

Configfs可以被编译为一个模块或编译到内核中。你可以通过以下方式访问它:



mount -t configfs none /config


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

通过mkdir(2)创建item。此时,item的属性也将显示出来。readdir(3)可以确定属性是什么,read(2)可以查询它们的默认值,而write(2)可以存储新值。不要在一个属性文件中混合多个属性。

configfs属性有两种类型:

  • 普通属性类似于sysfs属性,是小型ASCII文本文件,最大大小为一页(PAGE_SIZE, i386上为4096)。最好每个文件只使用一个值,同样的来自sysfs的警告也适用。Configfs期望write(2)一次存储整个缓冲区。当写入到普通的configfs属性时,用户空间进程应该首先读取整个文件,修改它们希望更改的部分,然后将整个缓冲区写回。
  • 二进制属性,有点类似于sysfs的二进制属性,但在语义上有一些细微的变化。PAGE_SIZE限制不适用,但整个二进制item必须适合单个内核vmalloc缓冲区。用户空间的write(2)调用被缓冲,属性的write_bin_attribute方法将在最后的close调用时被调用,因此用户空间必须检查close(2)的返回代码,以验证操作成功完成。为了避免恶意用户抢占内核,有一个每个二进制属性的最大缓冲值。

当需要销毁一个item时,使用rmdir(2)删除它。如果任何其他item与该item有链接(通过symlink(2)),则该物品不能被销毁。链接可以通过unlink(2)删除。

配置FakeNBD:示例

假设有一个网络块设备(Network Block Device,简称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属性(attribute)包含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命令

Coding With configfs

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

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

子系统是client模块的顶层。在初始化期间,client模块用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中出现。子系统通常希望item显示和/或存储属性。为此,它需要一个类型。

struct 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上执行哪些操作。所有已动态分配的items都需要提供ct_item_ops->release()方法。当config_item的引用计数为零时,将调用此方法。

struct 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想要一个属性以文件的形式出现在item的configfs目录中时,它必须定义一个configfs_attribute来描述它。然后将该属性添加到以null结尾的数组config_item_type->ct_attrs。当item出现在configfs中时,属性文件将以configfs_attribute->ca_name文件名出现。configfs_attribute->ca_mode指定文件权限。

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

struct configfs_bin_attribute



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


当需要使用二进制blob作为item的configfs目录中的文件内容时,使用binary属性。为此,将二进制属性添加到以null结束的数组config_item_type->ct_bin_attrs中,并且该item出现在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_group上的mkdir(2)。这将触发子item的创建:



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。正确配置该item意味着group可以按照item本身的权限行事。但是,它还可以做更多的事情:它可以创建子item或group。这是通过在group的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);
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()方法创建子item。如果提供了该方法,则从组目录中的mkdir(2)调用该方法。子系统分配一个新的config_item(更有可能是它的容器结构),初始化它,并将它返回给configfs。然后Configfs将填充文件系统树以反映新item。

如果子系统希望子节点本身是一个组,则该子系统提供ct_group_ops->make_group()。在组上使用group _init()函数,其他所有操作都是相同的。

最后,当userspace对item或组调用rmdir(2)时,调用ct_group_ops->drop_item()。因为config_group也是一个config_item,所以没有必要单独使用drop_group()方法。子系统必须对item分配时初始化的引用进行config_item_put()。如果一个子系统没有工作要做,它可以省略ct_group_ops->drop_item()方法,configfs将代表子系统调用config_item_put()。

注意:

  drop_item()是void类型函数,因此不会失败。当调用rmdir(2)时,configfs将从文件系统树中删除该项(假设它没有孩子让它忙碌)。子系统负责对此作出响应。如果子系统在其他线程中有对该item的引用,则内存是安全的。item可能需要一段时间才能真正从子系统的使用中消失。但它从configfs中消失了。

当drop_item()被调用时,item的链接已经被拆除。它在父节点上不再有引用,在item层次结构中也没有位置。如果client需要在拆解之前进行一些清理,子系统可以实现ct_group_ops->disconnect_notify()方法。该方法在configfs从文件系统视图中删除item之后被调用但在item从其父组中删除之前。和drop_item()一样,disconnect_notify()是void类型,不会失败。client子系统不应该在这里删除任何引用,因为它们仍然必须在drop_item()中删除。

当config_group仍然有子item时,它不能被删除。这在configfs rmdir(2)代码中实现。->drop_item()将不会被调用,因为item还没有被删除。rmdir(2)将失败,因为目录不是空的。

struct 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(struct configfs_subsystem *subsys);


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

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

例子

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

层次导航和子系统互斥锁

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

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

当新分配的项目还没有链接到这个层次结构时,子系统将无法获取互斥锁。类似地,当正在删除的item尚未解除链接时,它将无法获取互斥锁。这意味着当item在configfs中时,item的ci_parent指针永远不会是NULL,并且item只会在其父item的cg_children列表中持续相同的时间。这允许子系统在ci_parent和cg_children持有互斥对象时信任它们。

通过符号链接进行item聚合

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_items之间。configfs文件系统之外的任何symlink(2)尝试都将被拒绝。

当调用symlink(2)时,源config_item的->allow_link()方法将与它本身和目标项一起调用。如果源item允许链接到目标item,则返回0。如果源item只希望链接到某种类型的对象(例如,在它自己的子系统中),那么它可能希望拒绝链接。

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

当一个config_item链接到任何其他item时,它不能被删除;当一个item链接到它时,它也不能被删除。在configfs中不允许悬挂符号链接。

自动创建Subgroups

新的config_group可能希望有两种类型的子config_items。虽然这可以通过->make_item()中的magic name进行编码,但希望有一个方法让用户空间更明确地看到这种差异。

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

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

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

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

Dependent Subsystems

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

configfs提供了两个额外的API调用:configfs_depend_item()和configfs_undepend_item()。client驱动程序可以在现有的项上调用configfs_depend_item()来告诉configfs它是被依赖的。configfs将从rmdir(2)返回该item的-EBUSY。当item不再被依赖时,client驱动程序调用configfs_undepend_item()。

这些API不能在任何configfs回调下调用,因为它们会发生冲突。他们可以阻止和分配。client驱动程序可能不应该凭自己的魄力调用它们。相反,它应该提供外部子系统调用的API。

这是怎么做到的呢?想象一下ocfs2挂载进程。当它挂载时,它请求一个心跳区域item。这是通过调用心跳代码来完成的。在heartbeat代码中,查找区域item。这里,心跳代码调用configfs_depend_item()。如果成功,heartbeat就知道该区域可以安全的交给ocfs2。如果它失败了,无论如何它都会被拆除,heartbeat可以优雅地上报一个错误。

Committable Items

Committable items目前尚未实现。

一些config_items不能具有有效的初始状态。也就是说,不能为item的属性指定默认值以使item能够执行其工作。用户空间必须配置一个或多个属性,在这些属性之后,子系统可以启动这个item所代表的任何实体。

考虑上面的FakeNBD设备。如果没有目标地址(target address)和目标设备(target device),子系统就不知道要导入哪个块设备。这个简单的示例假设子系统只是等待,直到配置了所有适当的属性,然后再连接。这确实有效,但现在每个属性存储都必须检查属性是否已初始化。如果条件满足,每个存储的属性存储都必须触发连接。

最好是一个显式的操作,通知子系统config_item已经准备好了。更重要的是,显式操作允许子系统提供关于属性是否以有意义的方式初始化的反馈。configfs将其作为committable items提供。

configfs仍然只使用正常的文件系统操作。通过rename(2)提交item。item从可以修改的目录移动到不能修改的目录。

任何提供ct_group_ops->commit_item()方法的组都有committable items。当这个组出现在configfs中时,mkdir(2)将不会直接在这个组中工作。相反,这个组将有两个子目录:" live "和" pending "。“live”目录也不支持mkdir(2)或rmdir(2)。它只允许rename(2)。“pending”目录允许mkdir(2)和rmdir(2)。在“pending”目录中创建了一个item。它的属性可以随意修改。Userspace通过将item重命名到“live”目录来提交item。此时,子系统接收到->commit_item()回调。如果所有必需的属性都满足要求,则该方法返回0,并将item移动到“live”目录。

由于rmdir(2)不能在" live "目录下工作,因此必须关闭或" uncommitted "item。同样,这是通过rename(2)完成的,这次是从“live”目录返回到“pending”目录。子系统由ct_group_ops->uncommit_object()方法通知。