Skip to content

LV010-数据结构与应用

我们来详细了解一下Configfs。若笔记中有错误或者不合适的地方,欢迎批评指正😃。

这一部分是学习如何创建内核对象, 然后生成对应的文件和目录。

一、核心数据结构

1. 关键数据结构

站在用户的角度来说,一个文件系统里面有目录、文件两种对象。在configfs的内核实现中,对应4个概念。

1.1 configfs_attribute

这里其实还有个configfs_bin_attribute,他们两个都是对应文件。

c
struct configfs_attribute {
	const 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);
};
c
struct configfs_bin_attribute {
	struct configfs_attribute cb_attr;	/* std. attribute */
	void *cb_private;			/* for user       */
	size_t cb_max_size;			/* max core size  */
	ssize_t (*read)(struct config_item *, void *, size_t);
	ssize_t (*write)(struct config_item *, const void *, size_t);
};

(1)读写文件时,会导致上述结构体里的show/store或者read/write函数被调用

(2)文件是位于某个目录的: config_item

1.2 config_item

c
struct config_item {
	char			*ci_name;
	char			ci_namebuf[CONFIGFS_ITEM_NAME_LEN];
	struct kref		ci_kref;
	struct list_head	ci_entry;
	struct config_item	*ci_parent;
	struct config_group	*ci_group;
	const struct config_item_type	*ci_type;
	struct dentry		*ci_dentry;
};

这是 ConfigFS 中最基本的数据结构, 用于表示一个配置项。 每个配置项都是一个内核对象, 可以是设备、 驱动程序、 子系统等。 config_item 结构包含了配置项的类型、名称、 属性、 状态等信息, 以及指向父配置项和子配置项的指针。

Tips:configfs中的每个对象都是config_item,后面的config_group、subsystem本质上都属于特殊的config_item

image-20230328101747270

1.3 config_group

config_group 是一种特殊类型的配置项, 表示一个配置项组,对应一个目录。 它可以包含一组相关的配置项, 形成一个层次结构。config_group 结构包含了父配置项的指针, 以及指向子配置项的链表。

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

1.4 configfs_subsystem

configfs_subsystem 是configfs文件系中的最顶层的数据结构, 用于表示整个 ConfigFS 子系统。 它包含了根配置项组的指针, 以及 ConfigFS 的其他属性和状态信息。

c
struct configfs_subsystem {
	struct config_group	su_group;
	struct mutex		su_mutex;
};
  • 比如:/sys/kernel/config/usb_gadget/sys/kernel/config/iio

  • drivers/usb/gadget/configfs.c中调用configfs_register_subsystem(&gadget_subsys)就会创建subsystem,它对应configfs文件系统中的顶层目录usb_gadget

  • subsystem也属于config_group

image-20230328103329215

1.5 总结

这些数据结构之间的关系可以形成一个树形结构, 其中 configfs_subsystem 是根节点, config_group 表示配置项组, config_item 表示单个配置项。 子配置项通过链表连接在一起, 形成父子关系。

image-20250307100009404

2. 子系统、 容器和 config_item

接下来以设备树插件的驱动为例来详细了解一下这三个概念。

2.1 dtbocfg_root_subsys

c
static struct configfs_subsystem dtbocfg_root_subsys = {
    .su_group = {
        .cg_item = {
            .ci_namebuf = "device-tree",
            .ci_type    = &dtbocfg_root_type,
        },
    },
  .su_mutex = __MUTEX_INITIALIZER(dtbocfg_root_subsys.su_mutex),
};

这段代码定义了一个名为 dtbocfg_root_subsys 的 configfs_subsystem 结构体实例, 表示ConfigFS 中的一个子系统。

.ci_namebuf = "device-tree": 配置项的名称设置为"device-tree", 表示该配置项的名称为"device-tree"。

.ci_type = &dtbocfg_root_type: 配置项的类型设置为 dtbocfg_root_type, 这是一个自定义的配置项类型。

  • .su_mutex 字段是一个互斥锁, 用于保护子系统的操作。 在这里, 使用了__MUTEX_INITIALIZER 宏来初始化互斥锁。

通过这段代码, 创建了一个名为"device-tree"的子系统, 它的根配置项组为空。 可以在该子系统下添加更多的配置项和配置项组, 用于动态配置和管理设备树相关的内核对象。 Linux系统下创建了 device-tree 这个子系统,如下图 :

image-20250307141505657

2.2 注册配置项组

接下来继续分析设备树插件驱动代码中注册配置项组的部分 :

c
static int __init dtbocfg_module_init(void)
{
    int retval = 0;

    pr_info(DRIVER_NAME ": " DRIVER_VERSION "\n");

    config_group_init(&dtbocfg_root_subsys.su_group);
    config_group_init_type_name(&dtbocfg_overlay_group, "overlays", &dtbocfg_overlays_type);

    retval = configfs_register_subsystem(&dtbocfg_root_subsys);
    if (retval != 0) {
        pr_err( "%s: couldn't register subsys\n", __func__);
        goto register_subsystem_failed;
    }

    retval = configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group);
    if (retval != 0) {
        pr_err( "%s: couldn't register group\n", __func__);
        goto register_group_failed;
    }

    pr_info(DRIVER_NAME ": OK\n");
    return 0;

  register_group_failed:
    configfs_unregister_subsystem(&dtbocfg_root_subsys);
  register_subsystem_failed:
    return retval;
}

这段代码是一个初始化函数 dtbocfg_module_init(), 用于初始化和注册 ConfigFS 子系统和配置项组。

  • 第 7 行:通过 config_group_init()函数初始化了 dtbocfg_root_subsys.su_group, 即子系统的根配置项组。

  • 第 8 行:使用 config_group_init_type_name()函数初始化了dtbocfg_overlay_group, 表示名为"overlays"的配置项组, 并指定了配置项组的类型为dtbocfg_overlays_type, 这是一个自定义的配置项类型。

  • 第 10 行:调用 configfs_register_subsystem()函数注册了 dtbocfg_root_subsys 子系统。 如果注册失败, 将打印错误信息, 并跳转到register_subsystem_failed 标签处进行错误处理。

  • 第 16 行:调用 configfs_register_group()函数注册了 dtbocfg_overlay_group 配置项组, 并将其添加到 dtbocfg_root_subsys.su_group 下。 如果注册失败, 同样会打印错误信息, 并跳转到 register_group_failed 标签处进行错误处理。

  • 第 22 - 23 行:如果所有的注册过程都成功, 将打印"OK"消息, 并返回 0, 表示初始化成功。

如果在注册配置项组失败时, 会先调用 configfs_unregister_subsystem() 函数注销之前注册的子系统, 然后返回注册失败的错误码 retval。这段代码的作用是初始化和注册一个名为"device-tree"的 ConfigFS 子系统, 并在其下创建一个名为"overlays"的配置项组。 Linux 系统下, 在 device-tree 子系统下创建了 overlays 容器,如下图 :

image-20250307142437264

3. 属性和方法

我们要在容器下放目录或属性文件, 所以我们看一下 config_item结构体 :

c
struct config_item {
	char			*ci_name;
	char			ci_namebuf[CONFIGFS_ITEM_NAME_LEN]; // 目录的名字
	struct kref		ci_kref;
	struct list_head	ci_entry;
	struct config_item	*ci_parent;
	struct config_group	*ci_group;
	const struct config_item_type	*ci_type; // 目录下属性文件和属性操作
	struct dentry		*ci_dentry;
};

config_item 结构体中包含了 config_item_type 结构体, config_item_type 结构体如下所示:

c
struct config_item_type {
	struct module				*ct_owner;
	struct configfs_item_operations		*ct_item_ops;  // item(目录) 的操作方法
	struct configfs_group_operations	*ct_group_ops; // group(容器) 的操作方法
	struct configfs_attribute		**ct_attrs;        // 属性文件的操作方法
	struct configfs_bin_attribute		**ct_bin_attrs;// bin 属性文件的操作方法
};
c
struct configfs_item_operations {
    // 删除 item 方法, 在 group 下面使用 rmdir 命令会调用这个方法
	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);
};
c
struct configfs_group_operations {
    //创建 item 的方法, 在 group 下面使用 mkdir 命令会调用这个方法
	struct config_item *(*make_item)(struct config_group *group, const char *name);
    //创建 group 的方法
	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);
};
c
struct configfs_attribute {
	const 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);
};

4. 总结

image-20250313090707277image-20250313090813358image-20250313090903830
  • attribute
image-20250313091102610

当我们使用mkdir dtbo_demo这个命令创建dtbo_demo这个item的时候,就会调用configfs_group_operations .make_item方法

二、注册configfs子系统

1. demo源码

demo源码可以看这里:12_configfs_demo/01_configfs_subsystem

2. 开发板测试

  • (1)挂载configfs文件系统
shell
mount -t configfs none /sys/kernel/config
  • (2)加载驱动
shell
insmod sdriver_demo.ko
image-20250313092623759
  • (3)查看注册的子系统
shell
ls /sys/kernel/config/
image-20250313092724621
  • (4)卸载驱动
shell
rmmod sdriver_demo.ko
ls /sys/kernel/config/
image-20250313092830742

三、注册group容器

1. demo源码

源码可以看这里:12_configfs_demo/02_configfs_group

2. 开发板测试

  • (1)挂载configfs文件系统
shell
mount -t configfs none /sys/kernel/config
  • (2)测试效果
shell
insmod sdriver_demo.ko

ls /sys/kernel/config/
ls /sys/kernel/config/sconfigfs/
ls /sys/kernel/config/sconfigfs/sgroup/

rmmod sdriver_demo.ko
ls /sys/kernel/config/
image-20250313094035087

我们进入注册生成的 sconfigfs 子系统, 可以看到注册生成的 sgroup 容器。

四、用户空间创建 item

1. demo实例

1.1 demo源码

源码可以看这里:12_configfs_demo/03_configfs_item

1.2 开发板测试

  • (1)挂载configfs文件系统
shell
mount -t configfs none /sys/kernel/config
  • (2)加载驱动
shell
insmod sdriver_demo.ko
image-20250313105648767
  • (3)创建item
shell
cd /sys/kernel/config/sconfigfs/sgroup/
mkdir item_demo
image-20250313105722490
  • (4)删除item
shell
cd /sys/kernel/config/sconfigfs/sgroup/
rm -rf item_demo
image-20250313105818715
  • (5)卸载驱动
shell
rmmod sdriver_demo.ko

2. drop_item 和 release

当我们在命令行使用 rmdir 命令删除 item 时, 会执行configfs_item_operations.release 函数,

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

.release 成员字段是在 struct config_item_type结构体中成员configfs_item_operations里面定义的一个回调函数指针。 它指向一个函数, 当 configfs 中的配置项被释放或删除时, 内核会调用该函数来执行相应的资源释放操作。 它通常用于释放与配置项相关的资源, 比如释放动态分配的内存、 关闭打开的文件描述符等。

还有一个 drop_item 函数,它是定义在 configfs_group_operations 中:

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

drop_item 一个回调函数指针。 它指向一个函数, 当 configfs 中的配置组(group) 被删除时, 内核会调用该函数来处理与配置组相关的操作。 这个函数通常用于清理配置组的状态、 释放相关的资源以及执行其他必要的清理操作。 .drop_item 函数在删除配置组时被调用, 而不是在删除单个配置项时被调用。

.release 成员字段用于配置项的释放操作, 而 .drop_item 成员字段用于配置组的删除操作。它们分别在不同的上下文中执行不同的任务, 但都与资源释放和清理有关。

3. drop_item实例

2.1 demo源码

源码可以看这里:12_configfs_demo/04_drop_and_release

3.2 开发板测试

  • (1)挂载configfs文件系统
shell
mount -t configfs none /sys/kernel/config
  • (2)加载驱动
shell
insmod sdriver_demo.ko
  • (3)创建item
shell
cd /sys/kernel/config/sconfigfs/sgroup/
mkdir item_demo
  • (4)删除item
shell
cd /sys/kernel/config/sconfigfs/sgroup/
rm -rf item_demo
image-20250313112643471

输入“ rmdir test” 命令删除 item, 执行 rmdir 命令之后, 依次执行了 sdrv_group_delete_item() 函数和sdrv_item_release()函数。

  • (5)卸载驱动
shell
rmmod sdriver_demo.ko

五、注册attribute

1. demo源码

源码可以看这里:12_configfs_demo/05_attribute

2. 开发板测试

  • (1)挂载configfs文件系统
shell
mount -t configfs none /sys/kernel/config
  • (2)加载驱动
shell
insmod sdriver_demo.ko
  • (3)创建item
shell
cd /sys/kernel/config/sconfigfs/sgroup/
mkdir item_demo
image-20250313130003520
  • (4)属性操作
shell
cd item_demo
cat read
echo 1 > write
image-20250313130056103
  • (5)删除item
shell
cd /sys/kernel/config/sconfigfs/sgroup/
rmdir item_demo
# 下面这个命令在有属性文件的时候会无权限
rm -rf item_demo
image-20250313130324320
  • (5)卸载驱动
shell
rmmod sdriver_demo.ko

六、实现多级目录

经过前面的学习, 我们了解到一个配置组也就是(group) 可以包含多个配置项(item)和子配置组。 配置项和配置组都是作为配置组的成员存在的。 配置项和配置组之间通过指针进行关联, 以形成一个层次结构。

配置项(item)是最后一级目录了,它下面不可以再有目录:

image-20250316141505803

我们可以在配置项组中创建目录,那么我们能否实现这样的结构:

shell
/sys/kernel/config/sconfigfs/sgroup/a_dir/item_name_dir

下面就来了解一下多级目录的创建。其实吧,学到这里的时候我也不知道这里有什么用,后面用到了再补充吧。

1. demo源码

源码可以看这里:12_configfs_demo/06_multilevel_dir

2. 开发板测试

  • (1)挂载configfs文件系统
shell
mount -t configfs none /sys/kernel/config
  • (2)加载驱动
shell
insmod sdriver_demo.ko
  • (3)创建目录
shell
cd /sys/kernel/config/sconfigfs/sgroup/
mkdir group_demo

然后输入“mkdir group_demo” 命令创建 config_item ,创建成功之后 会打印这个项的名称:

image-20250313143111469

然后进入group_demo目录下, 此时可以在 test 目录下创建 item —— item_demo:

image-20250313143943356

但由于在驱动中我们并没有实现在 group 下创建 item 功能, 所以会提示创建 item_demo 没有权限。后面没有深入去搞了,这里就了解到这里吧。

  • (5)删除刚才的group_demo
shell
cd /sys/kernel/config/sconfigfs/sgroup/
rmdir group_demo
# 或者
rm -rf group_demo
image-20250313144138207
  • (5)卸载驱动
shell
rmmod sdriver_demo.ko

参考资料

configfs-用户空间控制的内核对象配置 - abin在路上 - 博客园

14_configfs的使用与内部机制 — Linux设备驱动开发教程中心

Welcome to 100ask’s documentation — Linux设备驱动开发教程中心