乐趣区

关于linux-kernel:configfs用户空间控制的内核对象配置

转载:https://www.cnblogs.com/sctb/…
互联网上的好货色越来越少,且看且珍惜,请尊重版权。

1. 什么是 configfs?

configfs 是一个基于内存的文件系统,它提供了与 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 和属性的列表,symlink(2) 能够用来将 items 分组。与 sysfs 不同的是,configfs 示意的生命周期齐全由用户空间管制,反对这些 items 的内核模块必须对用户管制做出响应。

sysfs 和 configfs 应该同时存在于一个零碎中,任何一个都不能取代另一个。

2. 应用 configfs

configfs 能够编译为一个模块,也能够编译到内核中。你能够通过以下命令拜访它:

sudo mount -t configfs none /sys/kernel/config/

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

通过 mkdir(2) 创立一个 item。此时,item 的属性也会呈现,readdir(3) 能够查看有哪些属性,read(2) 能够查问它们的默认值,write(2) 能够存储新的值。

留神:不要在一个属性文件中存入多个属性。

configfs 属性的两种类型:

  1. 个别属性,与 sysfs 属性相似,是小型 ASCII 文本文件,最大尺寸为一页(PAGE_SIZE,在 i386 上为 4096)。每个文件最好只应用一个值,与 sysfs 的注意事项雷同。

    configfs 心愿 write(2) 能一次性存储整个缓冲区。在写入个别的 configfs 属性时,用户空间过程应该先读取整个文件,批改想要批改的局部,而后再把整个缓冲区写回去。

  2. 二进制属性,与 sysfs 二进制属性有些相似,但在语义上做了一些轻微的扭转。不受 PAGE_SIZE 的限度,但整个二进制 item 必须适宜于单个内核 vmalloc 调配的 buffer。

    用户空间的 write(2) 调用是有缓冲的,属性的 write_bin_attribute 办法会在其被敞开时调用,因而,用户空间必须查看 close(2) 的返回码,以便验证操作是否胜利实现。

    为了防止歹意用户对内核进行 OOMing(“out of memory”,溢出攻打),每个二进制属性都有一个最大的缓冲区值。

当一个 item 须要被销毁时,用 rmdir(2) 删除它。如果一个 item 与(通过 symlink(2))其余 item 有链接,则不能销毁该 item。能够通过 unlink(2) 勾销链接。

3. 配置 FakeNBD:一个例子

设想一下,有一个网络块设施(NBD)驱动程序,它容许你拜访近程块设施,咱们称它为 FakeNBD。FakeNBD 应用 configfs 进行配置,显然,须要提供一个很好用的用户态程序,让系统管理员可能不便地配置 FakeNBD,为了使对 FakeNBD 的配置起作用,这个程序必须将配置的信息通知驱动,这就是 configfs 的作用。

当加载 FakeNBD 驱动时,它会在 configfs 中注册本人,用户能应用 readdir(3) 看到它。

ls /sys/kernel/config

fakenbd

用户也能够应用 mkdir(2) 创立 fakenbd 连贯,名字是任意的。不过,示例中的名字可能曾经被其余工具(uuid 或者磁盘名)应用了。

mkdir /sys/kernel/config/fakenbd/disk1
ls /sys/kernel/config/fakenbd/disk1

target device rw

target 属性蕴含 FakeNBD 要连贯的服务器的 IP 地址,device 属性是服务器上的设施,可想而知,rw 属性决定了连贯是只读还是读写。

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

就是这样,通过 shell 就曾经把设施配置好了。

4. 用 configfs 编程

configfs 中的每个对象都是一个 config_item,config_item 就是子系统中的一个对象,它的属性与该对象上的值相匹配。configfs 解决该对象及其属性的文件系统示意,容许子系统疏忽除根本 show/store 之外的其它所有交互。

items 是在 config_group 外面创立和销毁的。一个 group 是共享雷同属性和操作的 items 汇合。items 由 mkdir(2) 创立,rmdir(2) 删除,这些 configfs 都会解决,group 中 有一组操作来执行这些工作。

子系统是客户端模块的顶层。在初始化过程中,客户端模块向 configfs 注册子系统,子系统作为一个目录呈现在 configfs 文件系统的最高层(根)。一个子系统也是一个 config_group,能够做所有 config_group 能做的事件。

4.1 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 *);

一般来说,config_item 构造体被嵌入到一个 container 构造体中,这个 container 构造体实际上代表了子系统正在做的事件,其中的 config_item 局部就是对象与 configfs 的交互方式。

无论是在源文件中动态定义还是由父 config_group 创立,创立 config_item 都必须调用一个 _init() 函数,这将初始化援用计数器并设置相应的字段。

config_item 的所有用户都应该通过 config_item_get() 援用它,并在实现后通过 config_item_put() 函数放弃这个援用。

就其自身而言,config_item 只能在 configfs 中呈现。通常,一个子系统心愿这个 item 可能显示和存储属性,并实现一些其余事件,为此,还须要一个 type 构造体。

换句话说,config_item_type 构造体次要用来实现除了显示和存储属性之外的其余事件。

4.2 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 进行哪些操作。所有被动态分配的 item 都须要提供 ct_item_ops->release() 办法。当 config_item 的援用计数为零时,就会调用这个办法开释它。

4.3 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 中。当 item 呈现在 configfs 中时,属性文件将以 configfs_attribute->ca_name 文件名呈现,configfs_attribute->ca_mode 指定文件权限。

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

4.4 configfs_bin_attribute 构造体

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

当须要应用二进制 blob 来显示 item 对应 configfs 目录中文件的内容时,,会应用二进制属性。

BLOB:binary large object,二进制大对象,是一个能够存储二进制文件的容器。

将二进制属性增加到以 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) 操作会调用 ct_item_ops->write_bin_attribute() 办法。读 / 写会被缓冲,所以只会执行读 / 写的一个,属性自身不须要关怀。

4.5 config_group 构造体

config_item 不能凭空产生,惟一的办法是通过 mkdir(2) 在 config_group 上创立一个,该操作将触发子 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。

此外,group 还能够实现更多工作:创立 item 或 group,这是通过在 group 中 config_item_type 指定的 group 操作来实现的。

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);
};

一个 group 通过提供 ct_group_ops->make_item() 办法来创立子项目。如果提供了这个办法,当在 group 目录中应用 mkdir(2) 时,该办法被调用。当 ct_group_ops->make_item() 办法被调用,子系统将调配一个新的 config_item(or 更可能是它的 container 构造体),初始化并将其返回给 configfs,而后,configfs 将填充文件系统树以反映新的 item。

如果子系统心愿子 item 自身是一个 group,子系统提供 ct_group_ops->make_group(),其余的操作都是一样,应用 group 上的 group _init() 函数初始化。

最初,当用户空间对 item 或 group 调用 rmdir(2) 时,会调用 ct_group_ops->drop_item() 办法。因为 config_group 也是一个 config_item,所以不须要独自的 drop_group() 办法。子系统必须调用 config_item_put() 函数开释 item 调配时初始化的援用,如果除了该操作,子系统不须要要做其它操作,能够省略 ct_group_ops->drop_item() 办法,configfs 将代表子系统对 item 调用 config_item_put() 办法。

重要:drop_item() 的返回值为 void,因而不能失败。当 rmdir(2) 被调用时,configfs 将会从文件系统树中删除该 item(假如没有子 item 正在应用它),子系统负责对此操作做出响应。如果子系统在其余线程中有对该 item 的援用,那么内存是平安的,该 item 从子系统中真正隐没可能还须要一段时间,但它曾经从 configfs 中隐没了。

当 drop_item() 被调用时,item 的链接曾经被拆掉了,它在父 item 上不再有援用,在 item 的层次结构中也没有地位。如果客户端须要在这个拆分产生之前做一些清理工作,子系统能够实现 ct_group_ops->disconnect_notify() 办法。该办法在 configfs 从文件系统构造中删除 item 后,item 从父 group 中删除前被调用,和 drop_item() 一样,disconnect_notify() 的返回值也为 void,不能失败。客户端子系统不应该在这里删除任何援用,因为这必须在 drop_item() 中进行。

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

4.6 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 和一个 mutex 组成,这个 group 是创立子 config_items 的中央。对于一个子系统,这个 group 通常是动态定义的,在调用 configfs_register_subsystem() 之前,子系统必须通过 group _init() 函数来初始化这个 group,并且还必须初始化 mutex。

当调用注册函数返回后,子系统会始终存在,并且能够在 configfs 中看到。这时,用户程序能够调用 mkdir(2),子系统必须为此做好筹备。

5. 一个例子

了解这些基本概念的最好例子是 samples/configfs/configfs_sample.c 中的 simple_children subsystem/group 和 simple_child item,它们展现了一个显示和存储属性的简略对象,以及一个创立和销毁这些子 item 的简略 group。

configfs_sample.c : https://github.com/torvalds/l…

6. 档次导航和子系统互斥

configfs 还提供了一些额定的性能。因为 config_groups 和 config_items 呈现在文件系统中,所以它们被安顿在一个层次结构中。一个子系统是相对不会接触到文件系统局部的,然而子系统可能会对这个层次结构感兴趣。出于这个起因,层次结构是通过 config_group->cg_children 和 config_item->ci_parent 构造体成员示意的。

子系统能够浏览 cg_children 列表和 ci_parent 指针来查看子系统创立的树。这可能会与 configfs 对层次结构的治理发生冲突,所以 configfs 应用子系统的 mutex 来爱护批改。无论何时子系统要浏览层次结构,都必须在子系统 mutex 的爱护下进行。

当一个新调配的 item 还没有被链接到这个层次结构中时,子系统将无奈取得 mutex,同样,当一个正在被删除的 item 还没有解除链接时,子系统也无奈获取 mutex。这意味着,当一个 item 在 configfs 中时,我的项目的 ci_parent 指针永远不会是 NULL,而且,同一时刻,item 只会存在一个父 item 的 cg_children 列表中,这容许子系统在持有 mutex 时信赖 ci_parent 和 cg_children。

7. 通过 symlink(2) 进行 item 汇总

configfs 通过 group->item 为父 / 子关系提供了一个简略的 group,然而,通常状况下,在更大的环境中须要在父 / 子关系之外进行聚合,这是通过 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() 办法会被本人和一个指标 item 调用,如果源 item 容许链接到指标 item,则返回 0,如果源 item 只想链接到某一类型的对象(例如,在它本人子系统中的对象),它能够回绝该链接。

当对符号链接调用 unlink(2) 时,通过 ->drop_link() 办法告诉源 item,和 ->drop_item() 办法一样,这也是一个返回值为 void 的函数,不能失败,子系统负责响应因该函数执行导致的变动。

当一个 config_item 链接到任何其它 item 时,它不能被删除,当一个 item 链接到它时,也不能被删除。在 configfs 中不容许应用软链接。

8. 主动创立分组

一个新的 config_group 可能心愿有两种类型的子 config_items,尽管这能够通过在 ->make_item() 中的 magic names 来编写,但更显式的办法是让用户空间可能看到这种不同。

configfs 提供了一种办法,即在创立父 group 时,在其外部主动创立一个或多个子 group,而不是把行为互不雷同的 item 放在同一个 group 中。因而,mkdir(“parent”) 的后果是 “parent”,”parent/subgroup1″,直到 “parent/subgroupN”。当初,type 1 的 item 能够在目录 “parent/subgroup1” 中创立,type N 的 item 能够在目录 “parent/subgroupN” 中创立。

这些主动创立的子 group,或者说默认 group,并不影响父 group 的其余子 group,如果 ct_group_ops->make_group() 存在,其余子 group 也能够间接在父 group 上创立。

configfs 子系统通过 configfs_add_default_group() 函数将默认 group 增加到父 config_group 构造体中来指定它们,每个增加的 group 与父 group 同时被填充到 configfs 树中。同样地,它们也会与父 group 同时被删除,不会另外告诉,当一个 ->drop_item() 办法调用告诉子系统其父 group 行将隐没时,意味着与该父 group 关联的每个默认子 group 也行将隐没。

因而,不能间接通过 rmdir(2) 来删除默认 group,当父 group 的 rmdir(2) 查看子 group 时,也不会思考它们(默认 group)。

9. 从属子系统

有时,某些驱动程序依赖于特定的 configfs item,例如,挂载 ocfs2 依赖于心跳区域 item,如果应用 rmdir(2) 删除该区域 item,则 ocfs2 挂载会出错 或转为 readonly 模式。

configfs 提供了两个额定的 API 调用:configfs_depend_item() 和 configfs_undepend_item(),客户端驱动程序能够在一个现有的 item 上调用 configfs_depend_item() 来通知 configfs 它是被依赖的。如果其余程序 rmdir(2) 该 item,configfs 将返回 -EBUSY,当这个 item 不再被依赖时,客户端驱动会调用 configfs_undepend_item() 勾销依赖。

这些 API 不能在任何的 configfs 回调下调用,因为它们会抵触,不过,它们能够阻塞和重调配。客户端驱动不能凭本人的直觉调用它们,它应该提供一个内部子系统调用的 API。

这是如何工作的呢?设想一下 ocfs2 的挂载过程。当它挂载时,它会要求一个心跳区域 item,这是通过对心跳代码的调用来实现的。在心跳代码中,区域 item 被查找进去,同时,心跳代码会调用 configfs_depend_item(),如果胜利了,那么心跳代码就晓得这个区域是平安的,能够交给 ocfs2,如果失败了,ocfs2 将被卸载,心跳代码优雅地传递出一个谬误。

10. 可提交 item

注:可提交的 item 目前尚未应用。

有些 config_item 不能有一个无效的初始状态,也就是说,不能为 item 的属性指定默认值(指定了默认值,item 能力起作用),用户空间必须配置一个或多个属性后,子系统才能够启动这个 item 所代表的实体。

考虑一下下面的 FakeNBD 设施,如果没有指标地址和指标设施,子系统就不晓得要导入什么块设施。这个例子假如子系统只是简略地期待,直到所有属性都配置好了,再开始连贯。每次属性存储操作都查看属性是否被初始化的办法的确可行,但这会导致在满足条件(属性都曾经初始化)的状况下,每次属性存储操作必然触发连贯。

更好的做法是用一个显式的操作来告诉子系统 config_item 曾经筹备好了。更重要的是,显式操作容许子系统提供反馈,阐明属性是否以正当的形式被初始化,configfs 以可提交 item(commitable item)的模式提供了这种反馈。

configfs 依然只应用失常的文件系统操作,通过 rename(2) 提交的一个 item,会从一个可批改的目录挪动到一个不能批改的目录。

任何提供 ct_group_ops->commit_item() 办法的 group 都有可提交 item,当这个 group 呈现在 configfs 中时,mkdir(2) 将不会间接在该 group 中工作,相同,该 group 将有两个子目录 “live” 和 “pending”,live” 目录不反对 mkdir(2) 或 rmdir(2),它只容许 rename(2),”pending” 目录容许应用 mkdir(2) 和 rmdir(2)。如果在 “pending” 目录中创立了一个 item,它的属性能够随便批改,用户空间通过将 item 重命名到 “live” 目录中来提交,此时,子系统接管到 ->commit_item() 回调。如果所有所需的属性都被填充,该办法返回 0,item 被移到 “live” 目录下。

因为 rmdir(2) 在 “live” 目录中不起作用,所以必须敞开一个 item,或者说使其 “uncommitted”,同样,这也是通过 rename(2) 来实现的,这次是从 “live” 目录回到 “uncommitted” 目录,并通过 ct_group_ops->uncommit_object() 办法告诉子系统。

参考:https://www.kernel.org/doc/Do…

退出移动版