Skip to main content
 首页 » 编程设计

filesystems\configfs\configfs.txt 翻译Android下的

2022年07月19日32qq号

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

Joel Becker <joel.becker@oracle.com>
Updated: 31 March 2005
Copyright (c) 2005 Oracle Corporation, Joel Becker <joel.becker@oracle.com>

[What is 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和/或attributes。 symlink(2)可用于将items组合在一起。 与sysfs不同,表示的生命周期完全由用户空间驱动。 支持这items的内核模块必须响应这一点。

sysfs和configfs都可以并且应该在同一系统上一起存在。 任何一个都不是另一个的替代品。

[Using configfs]

configfs可以编译为模块或编译进内核。 你可以通过这样做来访问它:

  mount -t configfs none /config

除非还加载了客户端模块,否则configfs树将为空。 这些使用configfs作为subsystems注册其item types的模块称为客户端模块。 一旦加载了client subsystem后,它将显示为/config下的子目录(或多个子目录)与sysfs一样,configfs树始终存在,无论是否挂载在/config上

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

configfs属性有两种类型:

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

(2)二进制属性,有点类似于sysfs的二进制属性,但语义上稍作不同。 没有了PAGE_SIZE的限制,但整个二进制项(item)必须满足在单内核vmalloc的缓冲区中。 来自用户空间的write(2)的内容被缓存,并且在最后close()属性文件时将调用属性的write_bin_attribute()方法,也就是说在close()二进制属性文件时才会被真正的写入。因此用户空间必须检查close(2)的返回值以确认操作成功完成。为了避免恶意用户OOMing内核,每个二进制属性都有一个最大缓冲区值。

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

[Configuring FakeNBD: an Example]

假设有一个网络块设备(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脚本也是可以的。

[Coding With configfs]

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

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

子系统是客户端模块的顶级。 在初始化期间,客户端模块注册子系统到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显示和/或存储属性等。 为此,它需要一种类型(type)。

[struct config_item_type] 
 
struct configfs_item_operations { 
    void (*release)(struct config_item *); 
    int (*allow_link)(struct config_item *src, struct config_item *target); 
    int (*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希望某个属性在项目的configfs目录中显示为文件时,它必须定义一个configfs_attribute来描述它。然后,它将该属性添加到以NULL结尾的数组config_item_type->ct_attrs中。 当项目出现在configfs中时,属性文件显示的文件名将是configfs_attribute->ca_name。 configfs_attribute->ca_mode指定该属性文件的权限。

如果属性是可读的并提供 ->show 方法,则只要用户空间要求对属性执行read(2),就会调用该方法。 如果属性是可写的并提供 ->store 方法,则只要用户空间要求对属性执行write(2),就会调用该方法。

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

当需要使用二进制块作为项目的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()方法[注:4.14内核中没有这个成员的,换成configfs_bin_attribute->read()],只要用户空间要求对属性执行read(2),就会调用该方法。 对于write(2),将发生相反的情况。 读/写是会被缓冲的,因此只会发生一次实际的读/写(一个open/close过程中); 属性的不需要关注它。

config_item无法凭空存在,config_group上的mkdir(2)是创建它的唯一方法。 这将触发子项目的创建。

[struct config_group] 
 
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_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); 
    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()方法创建子项。 如果这个函数提供了,在这个group的目录下执行mkdir(2)就会调用此方法。 子系统分配新的config_item(或更可能是其容器结构),初始化它,并将其返回给configfs。 然后,Configfs将填充文件系统树以反映该新的项目。

如果子系统希望这个child本身成为一个组,则子系统需要提供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将此项目从文件系统视图中移除之后,但在从其父group中删除之前调用。 与drop_item()一样,disconnect_notify()为void类型,也不会返回失败。 客户端子系统不应该在这里删除任何引用,因为它们仍然必须在drop_item()中执行。

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

一个子系统必须自己注册,通常在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()之前,改子系统必须通过通常的组_init()函数已经初始化这个组了,并且它还必须初始化这个互斥锁。
当注册调用返回时,子系统处于活动状态,将通过configfs显示出来。 此时,可以调用mkdir(2)并且子系统必须已经为它做好了准备。

[An Example]

这些基本概念的最佳示例是simple_children subsystem/group和samples/configfs/configfs_sample.c中的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。

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

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

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

config_item在链接到任何其他项目时无法被删除(应该是被连接吧?),也不能在项目链接到它时删除。 configfs中不允许Dangling符号链接。

[Automatically Created Subgroups]

一个新的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 mount必须BUG或变成只读。 不开心。

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

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

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

[Committable Items]

注意:可提交的项目目前尚未实现。

有些config_items不能具有合法的初始状态。 也就是说,没有可以为项目的属性指定的以便其可以执行工作的默认值。 用户空间必须配置一个或多个属性,之后子系统可以启动此项表示的任何实体。

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

更好的是通知子系统config_item准备就绪的显式操作。 更重要的是,显式操作允许子系统提供关于属性是否以有意义的方式初始化的反馈。 configfs将此提供为可提交的项目。

configfs仍然只使用普通的文件系统操作。 一个项目是通过rename(2)提交。 该项目从可以修改的目录移动到不能修改的目录。

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

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


本文参考链接:https://www.cnblogs.com/hellokitty2/p/10325027.html