Linux设备模型:2、基本对象 Kobject、Kset、Ktype
原文:http://www.wowotech.net/device_model/kobject.html
作者:wowo
发布于:2014-3-7 0:25
分类:统一设备模型
前言
Kobject是Linux设备模型的基础,也是设备模型中最难理解的一部分(可参考Documentation/kobject.txt
的表述)。因此有必要先把它分析清楚。
设计背景
由“Linux设备模型(1)_基本概念”可知,Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。
而硬件设备的数量、种类是非常多的,这就决定了Kernel中将会有大量的有关设备模型的数据结构。这些数据结构一定有一些共同的功能,需要抽象出来统一实现,否则就会不可避免的产生冗余代码。这就是Kobject诞生的背景。
目前为止,Kobject主要提供如下功能:
- 通过parent指针,可以将所有Kobject以层次结构的形式组合起来。
- 使用一个引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放(这是Kobject诞生时的唯一功能)。
- 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间(有关sysfs,会在其它文章中专门描述,本文不会涉及太多内容)。
在Linux中,Kobject几乎不会单独存在。它的主要功能,就是内嵌在一个大型的数据结构中,为这个数据结构提供一些底层的功能实现。
Linux driver开发者,很少会直接使用Kobject以及它提供的接口,而是使用构建在Kobject之上的设备模型接口。
代码解析
在Kernel源代码中,Kobject由如下两个文件实现:
include/linux/kobject.h
:包含Kobject所有的数据结构定义和接口声明lib/kobject.c
:核心功能的实现
主要的数据结构
在描述数据结构之前,有必要说明一下Kobject, Kset和Ktype这三个概念。
Kobject是基本数据类型,每个Kobject都会在"/sys/“文件系统中以目录的形式出现。
Ktype代表Kobject(严格地讲,是包含了Kobject的数据结构)的属性操作集合(由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了)。
在设备模型中,ktype的命名和解释,都非常抽象,理解起来非常困难,后面会详细说明。
Kset是一个特殊的Kobject(因此它也会在"/sys/“文件系统中以目录的形式出现),它用来集合相似的Kobject(这些Kobject可以是相同属性的,也可以不同属性的)。
Kobject
/* Kobject: include/linux/kobject.h line 60 */
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct sysfs_dirent *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
name:该Kobject的名称,同时也是sysfs中的目录名称。
由于Kobject添加到Kernel时,需要根据名字注册到sysfs中,之后就不能再直接修改该字段。
如果需要修改Kobject的名字,需要调用kobject_rename接口,该接口会主动处理sysfs的相关事宜。
entry:用于将Kobject加入到Kset中的list_head。
parent:指向parent kobject,以此形成层次结构(在sysfs就表现为目录结构)。
kset:该kobject属于的Kset。可以为NULL。如果存在,且没有指定parent,则会把Kset作为parent(别忘了Kset是一个特殊的Kobject)。
ktype:该Kobject属于的kobj_type。每个Kobject必须有一个ktype,否则Kernel会提示错误。
sd:该Kobject在sysfs中的表示。
kref:"struct kref”类型(在include/linux/kref.h中定义)的变量,为一个可用于原子操作的引用计数。
state_initialized:指示该Kobject是否已经初始化,以便在Kobject的Init,Put,Add等操作时进行异常校验。
state_in_sysfs:指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。
state_add_uevent_sent/state_remove_uevent_sent:记录是否已经向用户空间发送ADD uevent,如果有,且没有发送remove uevent,则在自动注销时,补发REMOVE uevent,以便让用户空间正确处理。
uevent_suppress:如果该字段为1,则表示忽略所有上报的uevent事件。
注:Uevent提供了“用户空间通知”的功能实现,通过该功能,当内核中有Kobject的增加、删除、修改等动作时,会通知用户空间。
Kset
/* include/linux/kobject.h, line 159 */
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
list/list_lock:用于保存该kset下所有的kobject的链表。
kobj:该kset自己的kobject(kset是一个特殊的kobject,也会在sysfs中以目录的形式体现)。
uevent_ops:该kset的uevent操作函数集。当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。
Ktype
/* include/linux/kobject.h, line 108 */
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
release:通过该回调函数,可以将包含该种类型kobject的数据结构的内存空间释放掉。
sysfs_ops:该种类型的Kobject的sysfs文件系统接口。
default_attrs:该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的一个文件)。将会在Kobject添加到内核时,一并注册到sysfs中。
child_ns_type/namespace,和文件系统(sysfs)的命名空间有关,这里不再详细说明。
机制
Kobject的核心功能是:保持一个引用计数,当该计数减为0时,自动释放Kobject所占用的meomry空间(如果Kobject是动态分配的时候)。
在字符设备cdev机构体中,cdev的分配分为两种:静态和动态如果静态分配,cdev.kobj也是静态分配。
而Kobject大多数的使用场景,是内嵌在大型的数据结构中(如Kset、device_driver等),因此这些大型的数据结构,也必须是动态分配、动态释放的。那么释放的时机是什么呢?是内嵌的Kobject释放时。但是Kobject的释放是由Kobject模块自动完成的(在引用计数为0时),那么怎么一并释放包含自己的大型数据结构呢?
这时Ktype就派上用场了。我们知道,Ktype中的release回调函数负责释放Kobject(甚至是包含Kobject的数据结构)的内存空间,那么Ktype及其内部函数,是由谁实现呢?是由上层数据结构所在的模块!因为只有它,才清楚Kobject嵌在哪个数据结构中,并通过Kobject指针以及自身的数据结构类型,找到需要释放的上层数据结构的指针,然后释放它。
讲到这里,就清晰多了。所以,每一个内嵌Kobject的数据结构,例如kset、device、device_driver等等,都要实现一个Ktype,并定义其中的回调函数。同理,sysfs相关的操作也一样,必须经过ktype的中转,因为sysfs看到的是Kobject,而真正的文件操作的主体,是内嵌Kobject的上层数据结构!
顺便提一下,Kobject是面向对象的思想在Linux kernel中的极致体现,但C语言的优势却不在这里,所以Linux kernel需要用比较巧妙(也很啰嗦)的手段去实现。
从面向对象的角度来看:
- struct kobj(及其相关结构如kset, ktype等)属于最抽象的基类,代码最简洁,最不具体;
- struct device(及其相关结构如device_driver,device_type等)是对kobj的封装,是第一层派生类;
- 再上层的结构(如platform_device等),是在struct device的基础上再封装一次,是第二层派生类。
因此,例如我们创建了一个struct platform_device的实例,使用完毕后要释放它。那么这个过程按道理应该是:
- 系统内部先调用platform_device的remove函数,它只处理自己层特有的变量;
- 完毕后,系统调用第一层派生类struct device的release函数,处理了自己这一层的特有变量;
- 最后,调用基类kobject的release函数,将整个空间释放掉。
整个过程应该会跟C++析构过程比较类似,上述的“系统内部”也应该类似于C++编译器自动生成的代码,因为C++中析构函数的逆向调用是自动进行的,并没有在派生类的析构函数中显示调用。类似地,在此处上层的release中也不会显式调用下层的release,都是由系统内部完成的。
功能分析
Kobject使用流程
Kobject大多数情况下会嵌在其它数据结构中使用,其使用流程如下:
- 定义一个struct kset类型的指针,并在初始化时为它分配空间,添加到内核中
- 根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有Kobject
- 定义一个适合自己的ktype,并实现其中回调函数
- 在需要使用到包含Kobject的数据结构时,动态分配该数据结构,并分配Kobject空间,添加到内核中
- 每一次引用数据结构时,调用kobject_get接口增加引用计数;引用结束时,调用kobject_put接口,减少引用计数
- 当引用计数减少为0时,Kobject模块调用ktype所提供的release接口,释放上层数据结构以及Kobject的内存空间
有一种例外,Kobject不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs中创建一个目录,而不需要其它的kset、ktype的操作。这时可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到kernel中。
Kobject的分配与释放
前面讲过,Kobject必须动态分配,而不能静态定义或者位于堆栈之上,它的分配方法有两种。
通过kmalloc
通过kmalloc自行分配(一般是跟随上层数据结构分配),并在初始化后添加到kernel。这种方法涉及如下接口:
/* include/linux/kobject.h, line 85 */
extern void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
extern __printf(3, 4) __must_check
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...);
extern __printf(4, 5) __must_check
int kobject_init_and_add(struct kobject *kobj,
struct kobj_type *ktype, struct kobject *parent,
const char *fmt, ...);
kobject_init:初始化通过kmalloc等内存分配函数获得的struct kobject指针。主要执行逻辑为:
- 确认kobj和ktype不为空
- 如果该指针已经初始化过(判断kobj->state_initialized),打印错误提示及堆栈信息(但不是致命错误,所以还可以继续)
- 初始化kobj内部的参数,包括引用计数、list、各种标志等
- 根据输入参数,将ktype指针赋予kobj->ktype
kobject_add:将初始化完成的kobject添加到kernel中,参数包括需要添加的kobject、该kobject的parent(用于形成层次结构,可以为空)、用于提供kobject name的格式化字符串。主要执行逻辑为:
- 确认kobj不为空,确认kobj已经初始化,否则错误退出
- 调用内部接口kobject_add_varg,完成添加操作
kobject_init_and_add,是上面两个接口的组合,不再说明。
通过方式1分配的kobject,会在引用计数变为0时,由kobject_put调用其ktype的release接口,释放内存空间,具体可参考后面有关kobject_put的讲解。
内部接口 :
1、kobject_add_varg,解析格式化字符串,将结果赋予kobj->name,之后调用kobject_add_internal接口,完成真正的添加操作。
kobject_add_internal,将kobject添加到kernel。主要执行逻辑为:
- 校验kobj以及kobj->name的合法性,若不合法打印错误信息并退出
- 调用kobject_get增加该kobject的parent的引用计数(如果存在parent的话)
- 如果存在kset(即kobj->kset不为空),则调用kobj_kset_join接口加入kset。同时,如果该kobject没有parent,却存在kset,则将它的parent设为kset(kset是一个特殊的kobject),并增加kset的引用计数
- 通过create_dir接口,调用sysfs的相关接口,在sysfs下创建该kobject对应的目录
- 如果创建失败,执行后续的回滚操作,否则将kobj->state_in_sysfs置为1
2、kobj_kset_join,负责将kobj加入到对应kset的链表中。
使用kobject_create
Kobject模块可以使用kobject_create自行分配空间,并内置了一个ktype(dynamic_kobj_ktype),用于在计数为0是释放空间。
/* include/linux/kobject.h, line 96 */
extern struct kobject * __must_check kobject_create(void);
extern struct kobject * __must_check kobject_create_and_add(const char *name,
struct kobject *parent);
/* lib/kobject.c, line 605 */
static void dynamic_kobj_release(struct kobject *kobj)
{
pr_debug("kobject: (%p): %s\n", kobj, __func__);
kfree(kobj);
}
static struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};
kobject_create:该接口为kobj分配内存空间,并以dynamic_kobj_ktype为参数,调用kobject_init接口,完成后续的初始化操作。
kobject_create_and_add:是kobject_create和kobject_add的组合,不再说明。
dynamic_kobj_release:直接调用kfree释放kobj的空间。
Kobject引用计数的修改
通过kobject_get和kobject_put可以修改kobject的引用计数,并在计数为0时,调用ktype的release接口,释放占用空间。
/* include/linux/kobject.h, line 103 */
extern struct kobject *kobject_get(struct kobject *kobj);
extern void kobject_put(struct kobject *kobj);
kobject_get:调用kref_get,增加引用计数。
kobject_put:以内部接口kobject_release为参数,调用kref_put。kref模块会在引用计数为零时,调用kobject_release。
内部接口:
kobject_release() 通过kref结构,获取kobject指针,并调用kobject_cleanup接口继续。
kobject_cleanup,负责释放kobject占用的空间,主要执行逻辑如下:
- 检查该kobject是否有ktype,如果没有,打印警告信息
- 如果该kobject向用户空间发送了ADD uevent但没有发送REMOVE uevent,补发REMOVE uevent
- 如果该kobject有在sysfs文件系统注册,调用kobject_del接口,删除它在sysfs中的注册
- 调用该kobject的ktype的release接口,释放内存空间
- 释放该kobject的name所占用的内存空间
Kset的初始化、注册
Kset是一个特殊的kobject,因此其初始化、注册等操作也会调用kobject的相关接口,除此之外,会有它特有的部分。另外,和Kobject一样,kset的内存分配,可以由上层软件通过kmalloc自行分配,也可以由Kobject模块负责分配,具体如下。
/* include/linux/kobject.h, line 166 */
extern void kset_init(struct kset *kset);
extern int __must_check kset_register(struct kset *kset);
extern void kset_unregister(struct kset *kset);
extern struct kset * __must_check kset_create_and_add(const char *name,
const struct kset_uevent_ops *u,
struct kobject *parent_kobj);
kset_init:该接口用于初始化已分配的kset,主要包括调用kobject_init_internal初始化其kobject,然后初始化kset的链表。需要注意的时,如果使用此接口,上层软件必须提供该kset中的kobject的ktype。
kset_register:先调用kset_init,然后调用kobject_add_internal将其kobject添加到kernel。
kset_unregister:直接调用kobject_put释放其kobject。当其kobject的引用计数为0时,即调用ktype的release接口释放kset占用的空间。
kset_create_and_add:会调用内部接口kset_create动态创建一个kset,并调用kset_register将其注册到kernel。
- kset_create,该接口使用kzalloc分配一个kset空间,并定义一个kset_ktype类型的ktype,用于释放所有由它分配的kset空间。
Linux设备模型:2、基本对象 Kobject、Kset、Ktype的更多相关文章
- Linux 设备模型之 (kobject、kset 和 Subsystem)(二)
问题描写叙述:前文我们知道了/sys是包括内核和驱动的实施信息的,用户能够通过 /sys 这个接口.用户通过这个接口能够一览内核设备的全貌.本文将从Linux内核的角度来看一看这个设备模型是怎样构建的 ...
- Linux设备模型之kobject
阿辉原创,转载请注明出处 参考文档:LDD3-ch14.内核文档Documentation/kobject.txt,本文中使用到的代码均摘自Linux-3.4.75 ----------------- ...
- 【原创】linux设备模型之kset/kobj/ktype分析
背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...
- linux设备模型_转
建议原博文查看,效果更佳. 转自:http://www.cnblogs.com/wwang/category/269350.html Linux设备模型 (1) 随着计算机的周边外设越来越丰富,设备管 ...
- Linux设备模型(总线、设备、驱动程序和类)
Linux设备驱动程序学习(13) -Linux设备模型(总线.设备.驱动程序和类)[转] 文章的例子和实验使用<LDD3>所配的lddbus模块(稍作修改). 提示:在学习这部分内容是一 ...
- Linux设备模型 学习总结
看LDD3中设备模型一章,觉得思维有些混乱.这里从整体的角度来理理思路.本文从四个方面来总结一些内容: 1.底层数据结构:kobject,kset.2.linux设备模型层次关系:bus_type,d ...
- Linux设备模型——设备驱动模型和sysfs文件系统解读
本文将对Linux系统中的sysfs进行简单的分析,要分析sysfs就必须分析内核的driver-model(驱动模型),两者是紧密联系的.在分析过程中,本文将以platform总线和spi主控制器的 ...
- linux设备模型:扩展篇
Linux设备模型组件:总线 一.定义:总线是不同IC器件之间相互通讯的通道;在计算机中,一个总线就是处理器与一个或多个不同外设之间的通讯通道;为了设备模型的目的,所有的设备都通过总线相互连接,甚至 ...
- Linux设备模型(总结)
转:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml 看了一段时间的驱动编程,从LDD3的hello wrod到后来的字 ...
- Linux设备模型 (2)
上一篇文章<Linux设备模型 (1)>主要介绍了Linux设备模型在用户空间的接口sysfs,用户通过这个接口可以一览内核设备的全貌.本文将从Linux内核的角度来看一看这个设备模型是如 ...
随机推荐
- NetCore开发第一步 Log4Net日志引入
1.新建一个带mvc模板的项目: 2.引入Microsoft.Extensions.Logging.Log4Net.AspNetCore包,不要引入错了. 引入后后包的结果如下: 3.Startup类 ...
- 大模型高效微调详解-从Adpter、PrefixTuning到LoRA
一.背景 目前NLP主流范式是在大量通用数据上进行预训练语言模型训练,然后再针对特定下游任务进行微调,达到领域适应(迁移学习)的目的. 指令微调是预训练语言模型微调的主流范式 其目的是尽量让下游任务的 ...
- fastposter v2.7.1 紧急发布 电商海报编辑器
fastposter v2.7.1 紧急发布 电商海报编辑器 fastposter海报生成器,电商海报编辑器,电商海报设计器,fast快速生成海报 海报制作 海报开发.二维码海报,图片海报,分享海报, ...
- 如何使用Mac远程控制Windows电脑?
如何使用Mac远程控制Windows电脑?在你开始之前,设置您要远程处理的 Windows 计算机. 先安装 Microsoft Remote Desktop. 打开 Microsoft Remote ...
- 自研WPF插件系统(沙箱运行及热插拔)
前言 插件化的需求主要源于对软件架构灵活性的追求,特别是在开发大型.复杂或需要不断更新的软件系统时,插件化可以提高软件系统的可扩展性.可定制性.隔离性.安全性.可维护性.模块化.易于升级和更新以及支持 ...
- android studio 安装与配置
android studio 下载地址:http://www.android-studio.org/ 找一个存储空间,我在D盘上,建好如下目录 : 找到刚才在载的文件 android-stu ...
- Python爬图片(面向对象版)
import requests from lxml import etree from threading import Thread class Spider(object): def __init ...
- 为什么SwiftUI使用struct, 限制使用class
前言 在学习SwiftUI所有的地方,视图元素都定义一个struct并实现View协议,该协议定义body变量返回View类型. 但是为什么,这里一直是指定的struct, 而不是class呢? 尝试 ...
- iOS手工Crash解析
一.测试导出来一份ips crash文件,现在需要进行手工解析 现在需要下载对应的dsym文件,为了确定下载好的dsym文件和crash log是不是一致的,可以先看下dsym文件中的uuid p.p ...
- react表单处理 非受控组件
没有和state数据源进行关联的表单项,而是借助ref,使用元素DOM方式获取表单元素值 使用步骤 调用 React.createRef() 方法创建ref对象 将创建好的 ref 对象添加到文本框中 ...