上一篇<
> 中分析了ksysfs子系统是以kobj结构对象为基础,并在基础上扩展出各种特征,如关联kobj_ktype结构,用于对文件的权限、读写操作等,而文件以kernfs结构为基础生成(以attribute相关的属性结构关联/绑定)等内容,本篇继续分析设备模型kobject的容器kset及设备驱动抽象类(class)。
kset属于kobject属于特定子系统的一组特定类型的kobject,与其说是一组特定类型,倒不如说用kset表示一组特定类型的kobject更方便与管理、遍历等操作,实际场景中如果需要以某个根为目标的多组特定类型的kobject,可以编写复合型kset容器,以根kset容器为基础(实际还是根kobject),在它之后继续关联多个不同的kset容器,当然,这也是linux内核中设备驱动的现状。
kset容器相对于kobject来说,主要多了kobjs的uevent操作,用于记录、过滤用户层通知事件等内容,uevent内部通过netlink与用户通信,它采用net通信体系。
class属于设备驱动抽象框架,它内部由subsys_private(用于将private保存到bustype/class结构的驱动程序核心部分),attribute_group(类的默认属性、设备的默认属性等),dev_uevent(当设备被添加、从此类中移除或其他一些生成uevent以添加环境变量的事情时调用)等核心组件构成。
class_kset用于类(class)的热插拔事件转到类子系统(如/sys/class/目录下,加载(显示)某个class的kobj文件或释放等等)。
从某种意义上来说,class的出现主要用于解决设备、驱动、设备电源等核心组件之间的代码冗余问题,它通过融合方式对设备、驱动、设备电源等核心组件进行关联,提供一个统一的class结构(接口),实际还是回到了以kobject(内核对象)为基础的目的,并且大大增加了不同组件之间交互的灵活性。除此之外,class延续使用kobject的属性、类型(ktype)等概念,也降低了class的分析及使用难度。
创建class_kset容器结构对象,设置根kobj名称,关联kobjs的uevent操作(如果存在)等,然后通过kset_register函数 初始化sysfs对象的引用计数、链表对象及一些变量标志,kobj对象关联父指针(这里它是根节点(NULL),通过发送uevent通知用户空间(如果设置了uevent),kobj向用户空间发送环境缓冲内容
class_kset 用于类的热插拔事件转到类子系统(如/sys/class/目录下,加载(显示)某个class的kobj文件或释放等等)
int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL); // 创建kset容器结构对象,设置根kobj名称,关联kobjs的uevent操作(如果存在)等,然后通过kset_register函数 初始化sysfs对象的引用计数、链表对象及一些变量标志,kobj对象关联父指针(这里它是根节点(NULL),通过发送uevent通知用户空间(如果设置了uevent),kobj向用户空间发送环境缓冲内容
if (!class_kset)
return -ENOMEM;
return 0;
}
class_create 创建class,owner可以设置THIS_MODULE(代表这个模块,自己),也可以设置父class表示它的子级
class实际上是一种抽象框架,它内部由subsys_private(用于将private保存到bustype/class结构的驱动程序核心部分),attribute_group(类的默认属性、设备的默认属性等),dev_uevent(当设备被添加、从此类中移除或其他一些生成uevent以添加环境变量的事情时调用)等核心组件构成
#define class_create(owner, name) \ // owner可以设置THIS_MODULE(代表这个模块,自己),也可以设置父class表示它的子级
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
||
\/
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL); // 为class分配对象 cls
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name; // class 名称
cls->owner = owner; // class根(或父class)
cls->class_release = class_create_release; // 类释放函数
retval = __class_register(cls, key); // 注册class,class实际上是一种抽象框架,它内部由subsys_private(用于将private保存到bustype/class结构的驱动程序核心部分),attribute_group(类的默认属性、设备的默认属性等),dev_uevent(当设备被添加、从此类中移除或其他一些生成uevent以添加环境变量的事情时调用)等核心组件构成
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
kset_ktype 动态注册的sysfs的ktype(用于释放对象,kobj访问sysfs文件等等)
tatic struct kobj_type kset_ktype = {
.sysfs_ops = &kobj_sysfs_ops, // sysfs操作结构对象
// 读取(显示,kobj_attr_show)、写入(存储,kobj_attr_store)默认函数
.release = kset_release, // 释放kobject对象
.get_ownership = kset_get_ownership, // 获取kobj的父级对象的sysfs所有权数据
};
kobject_actions kobj状态
static const char *kobject_actions[] = {
[KOBJ_ADD] = "add",
[KOBJ_REMOVE] = "remove",
[KOBJ_CHANGE] = "change",
[KOBJ_MOVE] = "move",
[KOBJ_ONLINE] = "online",
[KOBJ_OFFLINE] = "offline",
[KOBJ_BIND] = "bind",
[KOBJ_UNBIND] = "unbind",
};
class_ktype 动态注册的sysfs的ktype(用于释放对象,访问sysfs文件等等)
static struct kobj_type class_ktype = {
.sysfs_ops = &class_sysfs_ops, // 读取(显示)、写入(存储)函数
.release = class_release, // 释放class对象
.child_ns_type = class_child_ns_type, // 通过kobj对象指针经过偏移计算,获得指向的class对象指针指向的命令空间类型指针
};
kset_uevent_ops kset与它相关的kobjects的uevent操作
struct kset_uevent_ops {
int (* const filter)(struct kobject *kobj); // 允许kset阻止一个特定kobject的uevent被发送到用户空间
// 如果该函数返回0,该uevent将不会被发送出去
const char *(* const name)(struct kobject *kobj); // 覆盖uevent发送到用户空间的kset的默认名称
// 默认情况下,该名称将与kset本身相同
int (* const uevent)(struct kobject *kobj, struct kobj_uevent_env *env); // 当uevent即将被发送至用户空间时,uevent函数将被调用
};
kset 属于特定子系统的一组特定类型的kobject
/*
* kset定义了一组kobject
* 它们可以是单独不同的“类型”,但总体而言,
* 这些kobject都希望分组在一起,并以相同的方式进行操作
* kset用于定义发生在kobject上的属性回调和其他常见事件
* /
struct kset {
struct list_head list; // 此kset的所有kobject的列表
spinlock_t list_lock; // 用于迭代kobjects的锁
struct kobject kobj; // 这个kset的嵌入式kobject,递归
const struct kset_uevent_ops *uevent_ops; // kset的uevent操作
// 只要kobject发生了什么事情,就会调用这些函数,
// 以便kset可以添加新的环境变量,或者根据需要过滤掉uevent
} __randomize_layout;
uevent_sock 用户空间事件套接字
struct uevent_sock {
struct list_head list; // 链表
struct sock *sk; // 套接字
};
class 设备类
struct class {
const char *name; // 名称
struct module *owner; // 模块所有者
const struct attribute_group **class_groups; // 该类的默认属性
const struct attribute_group **dev_groups; // 该类的设备的默认属性
struct kobject *dev_kobj; // 表示该类并将其链接到层次结构中的kobject
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); // 当设备被添加、从此类中移除或其他一些生成uevent以添加环境变量的事情时调用
char *(*devnode)(struct device *dev, umode_t *mode); // 回调以提供devtmpfs
void (*class_release)(struct class *class); // 调用以释放该类
void (*dev_release)(struct device *dev); // 调用以释放设备
int (*shutdown_pre)(struct device *dev); // 在驱动程序关闭前的关闭时间调用
const struct kobj_ns_type_operations *ns_type; // 回调,以便sysfs可以确定命名空间
const void *(*namespace)(struct device *dev); // 设备的命名空间
void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid); // 允许类为属于该类的设备指定sysfs目录的uid/gid
// 通常绑定到设备的命名空间
const struct dev_pm_ops *pm; // 此类的默认设备电源管理操作
struct subsys_private *p; // 驱动核心的私有数据,除了驱动核心之外,任何人都不能碰这个
};
subsys_private 用于将private保存到bustype/class结构的驱动程序核心部分
struct subsys_private {
struct kset subsys; // 此子系统的结构kset
struct kset *devices_kset; // 子系统的“设备”目录
struct list_head interfaces; // 关联的子系统接口列表
struct mutex mutex; // 保护设备和接口列表
struct kset *drivers_kset; // 关联的驱动程序列表
struct klist klist_devices; // 在devices_kset对象上迭代的klist
struct klist klist_drivers; // 在drivers_kset对象上迭代的klist
struct blocking_notifier_head bus_notifier; // 总线通知程序列表,用于显示任何与此总线上的事情有关的内容
unsigned int drivers_autoprobe:1; // 驱动默认自动探索
struct bus_type *bus; // 此结构关联的结构bus_type的指针
struct kset glue_dirs; // 此结构关联的结构bus_type的指针
struct class *class; // 此结构关联的结构类的指针
};
kset_create_and_add 创建kset容器结构对象,设置根kobj名称,关联kobjs的uevent操作(如果存在)等,然后通过kset_register函数 初始化sysfs对象的引用计数、链表对象及一些变量标志,kobj对象关联父指针(这里它是根节点(NULL),通过发送uevent通知用户空间(如果设置了uevent),kobj向用户空间发送环境缓冲内容
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); // 创建kset容器结构对象,设置根kobj名称,关联kobjs的uevent操作(如果存在)等
error = kset_register(kset); // 初始化sysfs对象的引用计数、链表对象及一些变量标志,kobj对象关联父指针(这里它是根节点(NULL),通过发送uevent通知用户空间(如果设置了uevent),kobj向用户空间发送环境缓冲内容
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);
kset_create 创建kset容器结构对象,设置根kobj名称,关联kobjs的uevent操作(如果存在)等
kset容器也可以按根节点、子节点形式进行多个kset容器关联,每个kset可以表示一组特定类型的kobj,组成一种复合型kset容器,当然它们将以根kobj代表kset进行管理
static struct kset *kset_create(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int retval;
kset = kzalloc(sizeof(*kset), GFP_KERNEL); // kset分配干净的内存
if (!kset)
return NULL;
retval = kobject_set_name(&kset->kobj, "%s", name); 为kset容器中的根kobj设置名称,如"class"
if (retval) {
kfree(kset);
return NULL;
}
kset->uevent_ops = uevent_ops; // kset与它相关的kobjects的uevent操作
kset->kobj.parent = parent_kobj; // 如果存在,为根kobj赋值父kobj,它可能用于多个kset容器关联
kset->kobj.ktype = &kset_ktype;
kset->kobj.kset = NULL;
return kset;
}
kset_get_ownership 获取kobj的父级对象的sysfs所有权数据
static void kset_get_ownership(struct kobject *kobj, kuid_t *uid, kgid_t *gid)
{
if (kobj->parent)
kobject_get_ownership(kobj->parent, uid, gid);
}
||
\/
void kobject_get_ownership(struct kobject *kobj, kuid_t *uid, kgid_t *gid) // 获取kobj对象的sysfs所有权数据
{
*uid = GLOBAL_ROOT_UID; // 0
*gid = GLOBAL_ROOT_GID; // 0
if (kobj->ktype->get_ownership)
kobj->ktype->get_ownership(kobj, uid, gid);
}
kset_register 初始化sysfs对象的引用计数、链表对象及一些变量标志,kobj对象关联父指针(这里它是根节点(NULL),通过发送uevent通知用户空间(如果设置了uevent),kobj向用户空间发送环境缓冲内容
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
kset_init(k); // 初始化sysfs对象的引用计数、链表对象及一些变量标志
err = kobject_add_internal(&k->kobj); // kobj对象关联父指针(这里它是根节点(NULL),如果与其它kset容器关键,应该会有父指针),并加入到kset容器(链表)中
// 然后通create_dir 获取kobj对象的sysfs所有权数据,分配kernfs节点(父级kn,如目录),加入安全策略(父节点存在的情况下),将kn链接到同级rbtree,更新哈希值及时间戳,并激活这个节点......
kobject_uevent(&k->kobj, KOBJ_ADD); // 通过发送uevent通知用户空间(如果设置了uevent),kobj向用户空间发送环境缓冲内容
return 0;
}
EXPORT_SYMBOL(kset_register);
kobject_uevent kobj向用户空间发送环境缓冲内容
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action]; // kobj状态对应的字符串
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
const struct kset_uevent_ops *uevent_ops;
int i = 0;
int retval = 0;
/*
* 不管结果如何,将“移除”事件标记为已完成,
* 因为某些子系统不希望通过自动清理重新触发“移除”
* /
if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
/* 搜索我们所属的kset */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;
/* 如果设置了uevent_suppress,则跳过事件 */
if (kobj->uevent_suppress) {
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
/* 如果过滤器返回零,则跳过该事件 */
if (uevent_ops && uevent_ops->filter)
if (!uevent_ops->filter(kobj)) {
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
/* 始发子系统 */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kobj);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}
/* 环境缓冲区 */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
/* 完整对象路径 */
devpath = kobject_get_path(kobj, GFP_KERNEL);
// 从当前kobj对象记录路径,向父级kobj递归,得到完整路径,如/test -> /class/test -> /sys/class/test
/* default keys */
retval = add_uevent_var(env, "ACTION=%s", action_string); // 将键值字符串添加到环境缓冲区
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
/* keys passed in from the caller */
if (envp_ext) {
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}
/* 让特定于kset的函数添加它的环境缓冲区 */
if (uevent_ops && uevent_ops->uevent) {
retval = uevent_ops->uevent(kobj, env);
if (retval) {
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
"%d\n", kobject_name(kobj), kobj,
__func__, retval);
goto exit;
}
}
switch (action) {
case KOBJ_ADD:
/*
* 标记“添加”事件,这样我们可以确保在自动清理期间将“删除”事件传递给用户空间
* 如果对象确实发送了一个“add”事件,那么如果调用方尚未完成,
* 那么核心将自动生成“remove”
*/
kobj->state_add_uevent_sent = 1;
break;
case KOBJ_UNBIND:
zap_modalias_env(env);
break;
default:
break;
}
mutex_lock(&uevent_sock_mutex);
/* 我们将发送一个事件,因此请求一个新的序列号 */
retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum);
if (retval) {
mutex_unlock(&uevent_sock_mutex);
goto exit;
}
retval = kobject_uevent_net_broadcast(kobj, env, action_string,
devpath); // kobj发送广播内容,环境缓冲内容
mutex_unlock(&uevent_sock_mutex);
...
exit:
kfree(devpath);
kfree(env);
return retval;
}
EXPORT_SYMBOL_GPL(kobject_uevent_env);
kobject_uevent_net_broadcast kobj发送广播内容,环境缓冲内容
static int kobject_uevent_net_broadcast(struct kobject *kobj,
struct kobj_uevent_env *env,
const char *action_string,
const char *devpath)
{
int ret = 0;
#ifdef CONFIG_NET
...
/*
* kobjects目前只携带网络名称空间标签,并且它们是这里唯一相关的标签,
* 因为我们想决定将uevent广播到哪个网络名称空间.
*/
if (ops && ops->netlink_ns && kobj->ktype->namespace)
if (ops->type == KOBJ_NS_TYPE_NET)
net = kobj->ktype->namespace(kobj); // 网络命名空间
if (!net)
ret = uevent_net_broadcast_untagged(env, action_string,
devpath); // 发送未加标签的广播(组ID=1)
// 从nl_table[sk->sk_protocol]数组中获取对应协议类型的监听对象(如果存在),
// 然后向sk_buff对象指针中拷贝环境缓冲内容,
之后赋值到netlink_skb_parms对象指针中,通过广播形式(netlink)发送
else
ret = uevent_net_broadcast_tagged(net->uevent_sock->sk, env,
action_string, devpath); // 发送到指定用户空间的广播
#endif
return ret;
}
__class_register 注册class,class实际上是一种抽象框架,它内部由subsys_private(用于将private保存到bustype/class结构的驱动程序核心部分),attribute_group(类的默认属性、设备的默认属性等),dev_uevent(当设备被添加、从此类中移除或其他一些生成uevent以添加环境变量的事情时调用)等核心组件构成
int __class_register(struct class *cls, struct lock_class_key *key)
{
struct subsys_private *cp;
int error;
pr_debug("device class '%s': registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put); // 初始化klist结构,get/put函数用于初始化获取和释放klist_node结构中记录的设备
INIT_LIST_HEAD(&cp->interfaces); // 初始化关联的子系统接口列表
kset_init(&cp->glue_dirs); // 初始化kset对象,此结构关联的结构bus_type的指针
__mutex_init(&cp->mutex, "subsys mutex", key); // 初始化互斥锁,保护设备和接口列表
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name); // 为kset容器中的根kobj设置名称,如"class"
klist_init
klist_class_dev_get
klist_class_dev_put
/* 为此类设备设置默认的/sys/dev目录 */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj; // 字符设备kobj
#if defined(CONFIG_BLOCK)
/* 让块类目录显示在sysfs的根目录中 */
if (!sysfs_deprecated || cls != &block_class)
cp->subsys.kobj.kset = class_kset; // 关联class_kset,用于更新类的热插拔事件(如加载(显示)某个class的kobj文件或释放等等)
#else
cp->subsys.kobj.kset = class_kset;
#endif
cp->subsys.kobj.ktype = &class_ktype; // 关联class_ktype,动态注册的sysfs的ktype(用于释放对象,访问sysfs文件等等)
cp->class = cls; // 关联class
cls->p = cp; // 关联subsys_private,用于将private保存到bustype/class结构的驱动程序核心部分
error = kset_register(&cp->subsys); // 初始化并添加kset
// kobj对象关联父指针,并加入到kset容器(链表)中,然后通create_dir 获取kobj对象的sysfs所有权数据,分配kernfs节点(父级kn,如目录)...,通过发送uevent通知用户空间(如果设置了uevent),kobj向用户空间发送环境缓冲内容
error = class_add_groups(class_get(cls), cls->class_groups); // 给定一个目录kobj,创建一组属性组
// 获取kobj对象的sysfs所有权数据,创建kernfs_node节点及命名空间 (父级kn,如目录),然后为属性组->属性列表中的属性分配kernfs_node节点及初始化(子级kn,如目录/文件),包括关联kernfs_ops对象,将kernfs_node链接到同级rbtree,更新哈希值及时间戳,并激活这个节点(属性列表中的节点)
class_put(cls); // 减少class对象的引用计数
return error;
}
EXPORT_SYMBOL_GPL(__class_register);
klist_init 初始化klist结构,get/put函数用于初始化获取和释放klist_node结构中记录的设备
/*
* 嵌入对象的get函数(如果没有,则为NULL)
* 嵌入对象的put函数(如果没有,则为NULL)
*
* 初始化klist结构
* 如果klist_node结构将嵌入到refcounted对象中(安全删除所必需的),
* 那么get/put参数将用于初始化获取和释放嵌入对象上的引用的函数
*/
void klist_init(struct klist *k, void (*get)(struct klist_node *),
void (*put)(struct klist_node *))
{
INIT_LIST_HEAD(&k->k_list);
spin_lock_init(&k->k_lock);
k->get = get;
k->put = put;
}
EXPORT_SYMBOL_GPL(klist_init);
klist_class_dev_get klist_node中获取保存的设备,并且递增设备的引用计数
static void klist_class_dev_get(struct klist_node *n)
{
struct device *dev = klist_class_to_dev(n); // klist_node中获取保存的设备(to_device_private_class(n)->device)
get_device(dev); // 递增设备的引用计数
}
klist_class_dev_put klist_node中获取保存的设备,并且递减设备的引用计数
static void klist_class_dev_put(struct klist_node *n)
{
struct device *dev = klist_class_to_dev(n); // klist_node中获取保存的设备(to_device_private_class(n)->device)
put_device(dev); // 递减引用计数
}
class_child_ns_type 通过kobj对象指针经过偏移计算,获得指向的class对象指针指向的命令空间类型指针
static const struct kobj_ns_type_operations *class_child_ns_type(struct kobject *kobj)
{
struct subsys_private *cp = to_subsys_private(kobj); // 通过kobj指针偏移计算出subsys_private结构对象指针
// #define to_subsys_private(obj) container_of(obj, struct subsys_private, subsys.kobj)
// container_of 函数相当于从subsys_private结构的 subsys.kobj(成员) <=> obj对象指针,地址偏移到subsys_private结构对象 cp
// obj 实际的对象指针
// struct subsys_private 逻辑的结构(不存在实体),也是要获取的结构对象指针
// subsys.kobj 逻辑的结构(不存在实体),它是struct subsys_private的成员subsys(struct kset subsys),struct kset的成员kobj(struct kobject kobj) <=> 表示obj的逻辑对象
// 这里有一点需要注意,container_of的第一个参数需要比第三个参数的指针等级高一介
// 如container_of的第三个逻辑参数是一级指针,而第一个实体参数需要二级指针
struct class *class = cp->class;
return class->ns_type; // 从class对象中获取命令空间类型指针
}