LV055-kset简介
一、kset简介
1. kset 是什么?
kset(内核对象集合)是一种用于组织和管理一组相关 kobject 的容器(或者说叫集合)。 kset 是 kobject 的一种扩展, 它提供了一种层次化的组织结构, 可以将一组相关的 kobject 组织在一起。
kset 是一个特殊的 kobject(它也会在 "/sys/“文件系统中以目录的形式出现),它用来集合相似的 kobject(这些 kobject 可以是相同属性的,也可以不同属性的)。
kset 定义在 kobject.h - include/linux/kobject.h - struct kset:
**
* struct kset - a set of kobjects of a specific type, belonging to a specific subsystem.
*
* A kset defines a group of kobjects. They can be individually
* different "types" but overall these kobjects all want to be grouped
* together and operated on in the same manner. ksets are used to
* define the attribute callbacks and other common events that happen to
* a kobject.
*
* @list: the list of all kobjects for this kset
* @list_lock: a lock for iterating over the kobjects
* @kobj: the embedded kobject for this kset (recursion, isn't it fun...)
* @uevent_ops: the set of uevent operations for this kset. These are
* called whenever a kobject has something happen to it so that the kset
* can add new environment variables, or filter out the uevents if so
* desired.
*/
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
} __randomize_layout;- list:指向该 kset 下所有的 kobject 的链表。用于将 kset 链接到全局 kset 链表中, 以便对 kset 进行遍历和管理。
- list_lock:避免操作链表时产生竞态的自旋锁,确保线程安全性。
- kobj:该 kset 自己的 kobject(kset 是一个特殊的 kobject,也会在 sysfs 中以目录的形式体现)。 用于在/sys 目录下创建对应的目录, 并与 kset 关联。
- uevent_ops:struct kset_uevent_ops 类型,该 kset 的 uevent 操作函数集,当 kset 中的某些 kobject 对象状态发生变化需要通知用户空间时,调用其中对应的函数来完成。当任何 kobject 需要上报 uevent 时,都要调用它所从属的 kset 的 uevent_ops,添加环境变量,或者过滤 event(kset 可以决定哪些 event 可以上报)。因此,如果一个 kobject 不属于任何 kset 时,是不允许发送 uevent 的。
kset 通过包含一个 kobject 作为其成员, 将 kset 本身表示为一个 kobject, 并使用 kobj 来管理和操作 kset。 通过 list 字段, kset 可以 链接到全局 kset 链表中, 以便进行全局的遍历和管理。 同时, list_lock 字段用于保护对 kset 链表的并发访问。 kset 还可以定义自己的 uevent 操作, 用于处理与 kset 相关的 uevent 事件, 例如在添加或删除 kobject 时发送相应的 uevent 通知。
这些字段共同构成了 kset 的基本属性和关系, 用于在内核中组织和管理一组相关的 kobject。 kset 提供了一种层次化的组织结构, 并与 sysfs 目录相对应, 方便对 kobject 进行管理和操作。
2. kset 和 kobject 的关系
在 Linux 内核中, kset 和 kobject 是相关联的两个概念, 它们之间存在一种层次化的关系,

(1)kset 是 kobject 的一种扩展: kset 可以被看作是 kobject 的一种特殊形式, 它扩展了 kobject 并提供了一些额外的功能。 kset 可以包含多个 kobject, 形成一个层次化的组织结构。
(2)kobject 属于一个 kset: 每个 kobject 都属于一个 kset。kobject 结构体中的 struct kset *kset 字段指向所属的 kset。 这个关联关系表示了 kobject 所在的集合或组织。 多个 kset 的时候如下图:

通过 kset 和 kobject 之间的关系, 可以实现对内核对象的层次化管理和操作。 kset 提供了对 kobject 的集合管理接口, 可以通过 kset 来迭代、 查找、 添加或删除 kobject。 同时, kset 也提供了特定于集合的功能, 例如在集合级别处理 uevent 事件。
总结起来, kset 和 kobject 之间的关系是: 一个 kset 可以包含多个 kobject, 而一个 kobject 只能属于一个 kset。 kset 提供了对 kobject 的集合管理和操作接口, 用于组织和管理具有相似特性或关系的 kobject。 这种关系使得内核能够以一种统一的方式管理和操作不同类型的内核对象。
二、相关 API
1. kset 的初始化、注册
Kset 是一个特殊的 kobject,因此其初始化、注册等操作也会调用 kobject 的相关接口,除此之外,会有它特有的部分。另外,和 kobject 一样,kset 的内存分配,可以由上层软件通过 kmalloc 自行分配,也可以由 kobject 模块负责分配。
1.1 kset_init()
kset_init() 函数定义如下:
/**
* kset_init - initialize a kset for use
* @k: kset
*/
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj);
INIT_LIST_HEAD(&k->list);
spin_lock_init(&k->list_lock);
}该接口用于初始化已分配的 kset,主要包括调用 kobject_init_internal() 初始化其 kobject,然后初始化 kset 的链表。需要注意的时,如果使用此接口,上层软件必须提供该 kset 中的 kobject 的 ktype。
1.2 kset_register()
kset_register() 定义如下:
/**
* kset_register - initialize and add a kset.
* @k: kset.
*/
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k);
err = kobject_add_internal(&k->kobj);
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
EXPORT_SYMBOL(kset_register);先调用 kset_init(),然后调用 kobject_add_internal() 将其 kobject 添加到 kernel。
1.3 kset_unregister()
kset_unregister() 定义如下:
/**
* kset_unregister - remove a kset.
* @k: kset.
*/
void kset_unregister(struct kset *k)
{
if (!k)
return;
kobject_del(&k->kobj);
kobject_put(&k->kobj);
}
EXPORT_SYMBOL(kset_unregister);直接调用 kobject_put() 释放其 kobject。当其 kobject 的引用计数为 0 时,即调用 ktype 的 release 接口释放 kset 占用的空间。
1.4 kset_create_and_add()
kset_create_and_add() 定义如下:
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int error;
kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);会调用内部接口 kset_create() 动态创建一个 kset,并调用 kset_register() 将其注册到 kernel。内部接口 kset_create() 使用 kzalloc 分配一个 kset 空间,并定义一个 kset_ktype 类型的 ktype,用于释放所有由它分配的 kset 空间。
三、创建 kset demo
1. demo 源码
可以直接看这里:05_device_model/03_kset_create
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */
#include <linux/kobject.h>
#include <linux/slab.h>
#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"
// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif
struct kobject *skobject1 = NULL; // 定义 kobject 结构体指针,用于表示第一个自定义内核对象
struct kobject *skobject2 = NULL; // 定义 kobject 结构体指针,用于表示第二个自定义内核对象
struct kobj_type stype = {0}; // 定义一个 kobj_type 结构体变量 stype,用于描述 kobject 的类型。
struct kset *skset = NULL; // 定义 kset 结构体指针,用于表示自定义内核对象的集合
/**
* @brief sdriver_demo_init()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __init int sdriver_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdriver_demo module init!\n");
// 创建并添加 kset,名称为 "skset",父 kobject 为 NULL,属性为 NULL
skset = kset_create_and_add("skset", NULL, NULL);
// 为 skobject1 分配内存空间,大小为 struct kobject 的大小,标志为 GFP_KERNEL
skobject1 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将 skset 设置为 skobject1 的 kset 属性
skobject1->kset = skset;
// 初始化并添加 skobject1,类型为 mytype,父 kobject 为 NULL,格式化字符串为 "skobject1"
ret = kobject_init_and_add(skobject1, &stype, NULL, "%s", "skobject1");
if(ret < 0)
{
PRTE("kobject_init_and_add fail!ret=%d\n", ret);
goto err_kobject_init_and_add_1;
}
// 为 skobject2 分配内存空间,大小为 struct kobject 的大小,标志为 GFP_KERNEL
skobject2 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 将 skset 设置为 skobject2 的 kset 属性
skobject2->kset = skset;
// 初始化并添加 skobject2,类型为 stype,父 kobject 为 NULL,格式化字符串为 "skobject2"
ret = kobject_init_and_add(skobject2, &stype, NULL, "%s", "skobject2");
if(ret < 0)
{
PRTE("kobject_init_and_add fail!ret=%d\n", ret);
goto err_kobject_init_and_add_2;
}
return 0;
err_kobject_init_and_add_1:
err_kobject_init_and_add_2:
return ret;
}
/**
* @brief sdriver_demo_exit()
* @note
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdriver_demo_exit(void)
{
// 释放 skobject1 的引用计数
kobject_put(skobject1);
// 释放 skobject2 的引用计数
kobject_put(skobject2);
// remove a kset
kset_unregister(skset);
PRT("sdriver_demo module exit!\n");
}
module_init(sdriver_demo_init); // 将__init 定义的函数指定为驱动的入口函数
module_exit(sdriver_demo_exit); // 将__exit 定义的函数指定为驱动的出口函数
/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */然后执行 make,编译出 sdriver_demo.ko。
2. 开发板验证
我们拷贝到开发板,然后加载驱动:

驱动加载之后,我们进入/sys/目录下,可以看到创建生成的 kset,如下图所示,我们进到 skset 目录下,可以看到创建的 2 个 kobject。
参考资料
【1】linux 驱动开发—— 6、linux 设备驱动模型_什么是 kobject-CSDN 博客
【2】Linux 设备模型剖析系列一(基本概念、kobject、kset、kobj_type)_device 下的 kobj-CSDN 博客
【4】一张图掌握 Linux platform 平台设备驱动框架!【建议收藏】-CSDN 博客
【5】关于 kobjects、ksets 和 ktypes 的一切你没想过需要了解的东西 — The Linux Kernel documentation