(todo)Linux 内核:设备驱动模型sysfs与kobject基类
Posted 黄树超
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(todo)Linux 内核:设备驱动模型sysfs与kobject基类相关的知识,希望对你有一定的参考价值。
(todo)Linux 内核:设备驱动模型(0)sysfs与kobject
背景
学习Linux 设备驱动模型时,对 kobject 不太理解。因此,学习了一下。
现在我知道了:kobj/kset
是如何作为统一设备模型的基础,以及到底提供了哪些功能。
以后我们就知道,在具体应用过程中,如device、bus甚至platform_device等是如何使用kobj/kset的。
参考:
- https://blog.csdn.net/yj4231/article/details/7799245
- http://www.wowotech.net/device_model/kobject.html
- http://www.wowotech.net/device_model/421.html
- Documentation\\kobject.txt
内核版本:3.14
kobject, kset和ktype
要分析sysfs,首先就要分析kobject和kset,因为驱动设备的层次结构的构成就是由这两个东东来完成的。
sysfs与kobject密不可分。
kobject
kobject是一个对象的抽象,它用于管理对象。
kobject被用来控制访问一个更大的,具有特定作用域的对象;为了达到这个作用,kobject将会被嵌入到其他结构体中。
如果你用面向对象的角度来看,kobject结构可以被看做顶层的抽象类,其他类都是从这个类派生出来的。
在sysfs中,每个kobject对应着一个目录。
// include/linux/kobject.h
struct kobject {
/* 对应sysfs的目录名 */
const char *name;
/*用于连接到所属kset的链表中,用于将kobj挂在kset->list中*/
struct list_head entry;
/*指向 父对象,形成层次结构,在sysfs中表现为父子目录的关系*/
struct kobject *parent;
/*
属于哪个kset
表征该kobj所属的kset。
kset可以作为parent的“候补”:当注册时,传入的parent为空时,可以让kset来担当。
*/
struct kset *kset;
/*类型属性,每个kobj或其嵌入的结构对象应该都对应一个kobj_type。 */
struct kobj_type *ktype;
/*sysfs中与该对象对应的文件节点对象*/
// 在3.14以后的内核中,sysfs基于kernfs来实现。
struct kernfs_node *sd;
/*对象的应用计数*/
struct kref kref;
/* 记录初始化与否。调用kobject_init()后,会置位。 */
unsigned int state_initialized:1;
/* 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。 */
unsigned int state_in_sysfs:1;
/* 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。*/
unsigned int state_add_uevent_sent:1;
/* 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。*/
unsigned int state_remove_uevent_sent:1;
/* 如果该字段为1,则表示忽略所有上报的uevent事件。 */
unsigned int uevent_suppress:1;
};
//include/linux/kref.h
struct kref {
atomic_t refcount;
};
目前为止,Kobject主要提供如下功能:
- 构成层次结构:通过
parent
指针,可以将所有Kobject
以层次结构的形式组合起来。 - 生命周期管理:使用引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放。
- 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间。
没有一个结构会嵌入多于一个kobject结构,如果这么做了,关于这个对象的引用计数肯定会一团糟,你的code也会充满bug,所以千万不要这么做。
ktype
每个kobject对象都内嵌有一个ktype,该结构定义了kobject在创建和删除时所采取的行为,可以理解为对象的属性。
实际上,Kobject的生命周期管理就是由关联的ktype来实现的。
注意,每个kobject必须关联一个ktype。
由于通用性,多个Kobject可能共用同一个属性操作集,因此把Ktype独立出来了。
// include/linux/kobject.h
struct kobj_type {
/* 处理对象终结的回调函数。该接口应该由具体对象负责填充。 */
void (*release)(struct kobject *kobj);
/* 该类型kobj的sysfs操作接口。 */
const struct sysfs_ops *sysfs_ops;
/* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
struct attribute **default_attrs;
/*child_ns_type/namespace 是 文件系统命名空间相关)略*/
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
// linux/sysfs.h
struct sysfs_ops {
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
struct attribute {
const char *name;
umode_t mode;
};
当kobject的引用计数为0时,通过release
方法来释放相关的资源。
attribute
为属性,每个属性在sysfs中都有对应的属性文件。
sysfs_op
的两个方法用于实现读取和写入属性文件时应该采取的行为。
kset
kset是一些kobject的集合,这些kobject可以有相同的ktype(属性),也可以不同。
同时,kset自己也包含一个kobject;因此在sysfs中,kset也是对应这一个目录,但是目录下面包含着其他的kojbect。
也有一种说法,把Kset看是一个特殊的Kobject。
每个Kobject不一定出现在sys中,但Kset中的每个Kobject会出现在sys中。
// include/linux/kobject.h
/**
* 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 {
/*属于该kset的kobject链表,与kobj->entry对应,用来组织本kset管理的kobj*/
struct list_head list;
spinlock_t list_lock;
/*该kset内嵌的kobj*/
struct kobject kobj;
/*
kset用于发送消息的操作函数集。
需要指出的是,kset能够发送它所包含的各种子kobj、孙kobj的消息;
即kobj或其父辈、爷爷辈,都可以发送消息;优先父辈,然后是爷爷辈,以此类推。
*/
const struct kset_uevent_ops *uevent_ops;
};
kset通过标准的内核链表(struct list_head list
)来管理它的所有子节点,kobject通过kset成员变量指向它的kset容器。
大多数情况下,kobject都是它所属kset(或者严格点,kset内嵌的kobject)的子节点。
kobject与kset的关系
下面这张图非常经典。最下面的kobj
都属于一个kset
,同时这些kobj的父对象就是kset
内嵌的kobj
。
通过链表,kset
可以获取所有属于它的kobj
。
从sysfs
角度而言,kset
代表一个文件夹,而下面的kobj
就是这个文件夹里面的内容,而内容有可能是文件也有可能是文件夹。
kobj
和kset
没有严格的层级关系,也并不是完全的父子关系。如何理解这句话?
-
kobject
之间可以组成一个树形结构:Kobj.parent = kobj\'
-
kset
之间也可以组成一个树形结构,但这是基于kobject
实现的:Kset.kobj.parent = Kset\'.kobj
正因为kobj和kset并不是完全的父子关系,因此在注册kobj时,将同时对parent及其所属的kset增加引用计数。
若parent和kset为同一对象,则会对kset增加两次引用计数。
kset算是kobj的“接盘侠”:
- 当kobj没有所属的parent时,才让kset来接盘当parent;
- 如果连kset也没有,那该kobj属于顶层对象,其sysfs目录将位于/sys/下。
待会我们看
kobject_add_internal
中是如何做的。
kset
内部本身也包含一个kobj
对象,在sysfs
中也表现为目录;所不同的是,kset
要承担kobj
状态变动消息的发送任务。因此,首先kset会将所属的kobj组织在kset.list
下,同时,通过uevent_ops
在合适时候发送消息。
对于kobject_add()
来说,它的输入信息是:kobj-parent
、kobj-name
,kobject_add()
优先使用传入的parent
作为kobj->parent
;其次,使用kset
作为kobj->parent
。
kobj状态变动后,必须依靠所关联的kset来向用户空间发送消息;若无关联kset(该kobj
向上组成的树中,任何成员都无所属的kset),则kobj无法发送用户消息。
kobject 结构可能的层次结构如图:
举例:通过kobject
创建bus
目录
分析如何通过kobject
在/sys
创建bus
目录。
// drivers/base/bus.c
static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
struct kobj_type *ktype = get_ktype(kobj);
if (ktype == &bus_ktype)
return 1;
return 0;
}
static const struct kset_uevent_ops bus_uevent_ops = {
.filter = bus_uevent_filter,
};
int __init buses_init(void)
{
// 直接调用kset_create_and_add,第一个参数为要创建的目录的名字,而第三个参数指定父对象。
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
// ...
return 0;
}
kset_create_and_add
kset_create_and_add
是kobject_create
和kobject_add
的组合
// lib/kobject.c
/**
* kset_create_and_add - create a struct kset dynamically and add it to sysfs
*
* @name: the name for the kset
* @uevent_ops: a struct kset_uevent_ops for the kset
* @parent_kobj: the parent kobject of this kset, if any.
*
* This function creates a kset structure dynamically and registers it
* with sysfs. When you are finished with this structure, call
* kset_unregister() and the structure will be dynamically freed when it
* is no longer being used.
*
* If the kset was not able to be created, NULL will be returned.
*/
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 = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
/*添加kset到sysfs*/
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
EXPORT_SYMBOL_GPL(kset_create_and_add);
/**
* kobject_add - the main kobject add function
* @kobj: the kobject to add
* @parent: pointer to the parent of the kobject.
* @fmt: format to name the kobject with.
*
* The kobject name is set and added to the kobject hierarchy in this
* function.
*
* If @parent is set, then the parent of the @kobj will be set to it.
* If @parent is NULL, then the parent of the @kobj will be set to the
* kobject associated with the kset assigned to this kobject. If no kset
* is assigned to the kobject, then the kobject will be located in the
* root of the sysfs tree.
*
* If this function returns an error, kobject_put() must be called to
* properly clean up the memory associated with the object.
* Under no instance should the kobject that is passed to this function
* be directly freed with a call to kfree(), that can leak memory.
*
* Note, no "add" uevent will be created with this call, the caller should set
* up all of the necessary sysfs files for the object and then call
* kobject_uevent() with the UEVENT_ADD parameter to ensure that
* userspace is properly notified of this kobject\'s creation.
*/
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
{
va_list args;
int retval;
if (!kobj)
return -EINVAL;
if (!kobj->state_initialized) {
printk(KERN_ERR "kobject \'%s\' (%p): tried to add an "
"uninitialized object, something is seriously wrong.\\n",
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
EXPORT_SYMBOL(kobject_add);
这里主要调用了两个函数,接下分别来看下。
kset_create
建立kset,设置名字与父对象。
路径:lib/kobject.c
/**
* kset_create - create a struct kset dynamically
*
* @name: the name for the kset
* @uevent_ops: a struct kset_uevent_ops for the kset
* @parent_kobj: the parent kobject of this kset, if any.
*
* This function creates a kset structure dynamically. This structure can
* then be registered with the system and show up in sysfs with a call to
* kset_register(). When you are finished with this structure, if
* kset_register() has been called, call kset_unregister() and the
* structure will be dynamically freed when it is no longer being used.
*
* If the kset was not able to be created, NULL will be returned.
*/
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 实例*/
kset = kzalloc(sizeof(*kset), GFP_KERNEL);
/*设置kobj->name*/
retval = kobject_set_name(&kset->kobj, "%s", name);
kset->uevent_ops = uevent_ops;
/*设置父对象*/
kset->kobj.parent = parent_kobj;
/*
* The kobject of this kset will have a type of kset_ktype and belong to
* no kset itself. That way we can properly free it when it is
* finished being used.
*/
kset->kobj.ktype = &kset_ktype;
/*本keset不属于任何kset*/
kset->kobj.kset = NULL;
return kset;
}
Issues
读源码时有个疑问,当调用kset_create()时,为何kset->kobj.kset=NULL;
源码中的解释如下:
/*
* The kobject of this kset will have a type of kset_ktype and belong to
* no kset itself. That way we can properly free it when it is
* finished being used.
*/
但是当第三个参数parent_kobj
有值时,既然kset->kobj.parent=parent_kobj;
那么说明kset->kobj是有上层kset的,而这个上层kset应该可以用container_of由parent_kobj得出。
但为何在源码中直接就将kset->kobj.kset置为NULL了呢。是因为kset不会从属于其它kset么。谢谢
wowo
@Issues:
首先,kset是一个特殊的kobject,它是kobject的容器,用于将多个kobject收集在一起。那么kset是否还会从属于另一个kset?即容器是否会嵌套?从代码注释上可以看出,答案是否定的。从实际逻辑上考虑,这也是合理的。这就是kset->kobj.kset为NULL的原因。
其次,虽然kset->kobj.kset为NULL,但为了kset的自动free,它还是需要一个ktype的,这就是为什么kset->kobj.ktype不为NULL。
最后,您提到的kset->kobj.parent=parent_kobj,也就是说该kset可能会有一个上层的parent(也是一个kset),这就是另外一个含义了,kset之间可以组成一个树形结构,这不是kset要描述的事情,而是kobject要描述的。
loren
2017-05-10 20:09
@wowo:“kset->kobj.parent=parent_kobj,能说明该kset可能会有一个上层的parent(也是一个kset)”
为什么这个上层的parent必须是一个kset呢?不能这个parent不属于任何一个kset吗?
wowo
2017-05-11 08:56
@loren:是的,你说的对,kernel没有规定这个parent kobj必须是什么,它只是为了让kset有个树形结构,因而可以是另一个kset,也有可能是一个普通的数据结构。
不过看kernel代码,大部分时候,都是一个kset。因为kset的上级是kset,结构上比较清晰。
这个函数中,动态分配了kset实例,调用kobject_set_name
设置kset->kobj->name为bus
,也就是我们要创建的目录bus
。
同时这里kset->kobj.parent
为NULL
,也就是没有父对象。
因为要创建的bus目录是在sysfs所在的根目录创建的,自然没有父对象。
随后简要看下由kobject_set_name函数调用引发的一系列调用。
// lib/kobject.c
/**
* kobject_set_name_vargs - Set the name of an kobject
* @kobj: struct kobject to set the name of
* @fmt: format string used to build the name
* @vargs: vargs to format the string.
*/
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs)
{
const char *old_name = kobj->name;
char *s;
if (kobj->name && !fmt)
return 0;
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (!kobj->name) {
kobj->name = old_name;
return -ENOMEM;
}
/* ewww... some of these buggers have \'/\' in the name ... */
while ((s = strchr(kobj->name, \'/\')))
s[0] = \'!\';
kfree(old_name);
return 0;
}
// lib/kasprintf.c
/* Simplified asprintf. */
char *kvasprintf(gfp_t gfp, const char *fmt, va_list ap)
{
unsigned int len;
char *p;
va_list aq;
va_copy(aq, ap);
len = vsnprintf(NULL, 0, fmt, aq);
va_end(aq);
p = kmalloc_track_caller(len+1, gfp);
if (!p)
return NULL;
vsnprintf(p, len+1, fmt, ap);
return p;
}
EXPORT_SYMBOL(kvasprintf);
kset_register
// lib/kobject.c
/**
* kset_register - initialize and add a kset.
* @k: kset.
*/
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
/*初始化kset*/
kset_init(k);
/*在sysfs中建立目录*/
err = kobject_add_internal(&k->kobj);
if (err)
return err;
/* 向用户空间发送 KOBJ_ADD事件。 */
kobject_uevent(&k->kobj, KOBJ_ADD);
return 0;
}
这里面调用了3个函数。这里先介绍前两个函数。kobject_uevent
实现的是用户空间事件传递,留到以后再说。
kset_init
该函数用于初始化kset。
/**
* kset_init - initialize a kset for use
* @k: kset
*/
void kset_init(struct kset *k)
{
/*初始化kobject的某些字段*/
kobject_init_internal(&k->kobj);
/*初始化链表头*/
INIT_LIST_HEAD(&k->list);
/*初始化自旋锁*/
spin_lock_init(&k->list_lock);
}
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
/*初始化引用基计数*/
kref_init(&kobj->kref);
/*初始化链表头*/
INIT_LIST_HEAD(&kobj->entry);
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;
}
kobject_add_internal
该函数将在sysfs中建立目录。
kobject_add_internal()
会根据kobj.parent
和kobj.kset
来综合决定父对象(相当于/sysfs
中的父级目录):
- 如果有
parent
则默认parent
为父对象; - 如果没有
parent
作为父对象,但是有当前的kobj
位于kset
中,则使用kset
中的kobj
作为父对象 - 如果没有
parent
也没有kset
作为父对象,那该kobj
属于顶层对象:在sysfs
中,我们可以看到kobj
位于/sys/
下。
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
/*检查name字段是否存在*/
if (!kobj->name || !kobj->name[0]) {
return -EINVAL;
}
/*有父对象则增加父对象引用计数*/
parent = kobject_get(kobj->parent);
/* join kset if set, use it as parent if we do not already have one */
/*
由于在kset_create中有kset->kobj.kset = NULL,
因此if (kobj->kset)条件不满足。
*/
if (kobj->kset) {
if (!parent)
/*kobj属于某个kset,但是该kobj没有父对象,则以kset的kobj作为父对象*/
parent = kobject_get(&kobj->kset->kobj);
/*将kojbect添加到kset结构中的链表当中*/
kobj_kset_join(kobj);
/* 根据kobj.parent和kobj.kset来综合决定父对象*/
kobj->parent = parent;
}
pr_debug("kobject: \'%s\' (%p): %s: parent: \'%s\', set: \'%s\'\\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
/*根据kobj->name在sys中建立目录*/
error = create_dir(kobj);
// 如果出错时,回收资源
if (error) {
/* 把kobj从kobj->kset的链表中去除 */
// 对应于 kobj_kset_join
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
WARN(1, "%s failed for %s with "
"-EEXIST, don\'t try to register things with "
"the same name in the same directory.\\n",
__func__, kobject_name(kobj));
else
WARN(1, "%s failed for %s (error: %d parent: %s)\\n",
__func__, kobject_name(kobj), error,
parent ? kobject_name(parent) : "\'none\'");
} else
kobj->state_in_sysfs = 1;
return error;
}
因此在这个函数中,对name进行了必要的检查之后,调用了create_dir
在sysfs中创建目录。
在create_dir执
行完成以后会在sysfs的根目录(/sys/)建立文件夹bus。该函数的详细分析将在后面给出。
至此,对bus目录的建立有了简单而直观的了解。
我们可以看出kset其实就是表示一个文件夹,而kset本身也含有一个kobject,而该kobject的name字段即为该目录的名字,本例中为bus。
kobj/kset功能特性
我们先大概提一下这些功能特性,在后面的文章中会详细说明:对象生命周期管理
以及用户空间事件投递
。
对象生命周期管理
在创建一个kobj对象时,kobj中的引用计数管理成员kref被初始化为1;从此kobj可以使用下面的API函数来进行生命周期管理:
// lib/kobject.c
/**
* kobject_get - increment refcount for object.
* @kobj: object.
*/
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj)
kref_get(&kobj->kref);
return kobj;
}
/**
* kobject_put - decrement refcount for object.
* @kobj: object.
*
* Decrement the refcount, and if 0, call kobject_cleanup().
*/
void kobject_put(struct kobject *kobj)
{
if (kobj) {
kref_put(&kobj->kref, kobject_release);
}
}
static void kobject_release(struct kref *kref)
{
struct kobject *kobj = container_of(kref, struct kobject, kref);
kobject_cleanup(kobj);
}
对于kobject_get()
,它就是直接使用kref_get()
接口来对引用计数进行加1操作;
而对于kobject_put()
,它不仅要使用kref_put()
接口来对引用计数进行减1操作,还要对生命终结的对象执行release()
操作:当引用计数减为0时,回收该对象的资源。
然而kobject是高度抽象的实体,导致kobject不会单独使用,而是嵌在具体对象中。反过来也可以这样理解:凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject来实现需求。
sysfs文件系统的层次组织
sysfs向用户空间展示了驱动设备的层次结构。这一个功能比较简单,先完全贴出来。
我们都知道设备和对应的驱动都是由内核管理的,这些对于用户空间是不可见的。现在通过sysfs,可以在用户空间直观的了解设备驱动的层次结构。
实际上,sysfs文件系统的组织功能是基于kobject实现的:该功能依靠kobj的parent、kset、sd等成员来完成。sysfs文件系统能够以友好的界面,将kobj所刻画的对象层次、所属关系、属性值,展现在用户空间。
我们来看看sysfs的文件结构:
$ uname -r
4.15.0-142-generic
$ ls /sys
block bus class dev devices firmware fs hypervisor kernel module power
目录 | 意义 |
---|---|
block | 块设备 |
bus | 系统中的总线 |
class | 设备类型,比如输入设备 |
dev | 系统中已注册的设备节点的视图,有两个子目录char和block。 |
devices | 系统中所有设备拓扑结构视图 |
fireware | 固件 |
fs | 文件系统 |
kernel | 内核配置选项和状态信息 |
module | 模块 |
power | 系统的电源管理数据 |
用户空间事件投递
这方面的内容可参考《http://www.wowotech.net/device_model/uevent.html》,该博文已经详细的说明了用户空间事件投递。
具体对象有事件发生时,相应的操作函数中(如device_add()
),会调用事件消息接口kobject_uevent()
,在该接口中,首先会添加一些共性的消息(路径、子系统名等),然后会找寻合适的kset->uevent_ops
,并调用该ops->uevent(kset, kobj, env)
来添加该对象特有的事件消息(如device对象的设备号、设备名、驱动名、DT信息);
一切准备完毕,就会通过两种可能的方法向用户空间发送消息:1.netlink广播;2. uevent_helper程序。
因此,上面具体对象的事件消息填充函数,应该由特定对象来填充;对于device对象来说,在初始化的时候,通过下面这行代码: `devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);`
来完成kset->uevent_ops的指定,今后所有设备注册时,调用device_register()-->device_initialize()后,都将导致:
dev->kobj.kset = devices_kset;
因此通过device_register()注册的设备,在调用kobject_uevent()接口发送事件消息时,就自动会调用devices_kset的device_uevent_ops。该ops的uevent()方法定义如下:
> static const struct kset_uevent_ops device_uevent_ops = {
>
> .filter = dev_uevent_filter,
>
> .name = dev_uevent_name,
>
> .uevent = dev_uevent,
>
> }; ||
>
> \\/
>
> dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env)
>
> add_uevent_var(env, "xxxxxxx", ....)
>
> add_uevent_var(env, "xxxxxxx", ....)
>
> add_uevent_var(env, "xxxxxxx", ....)
>
> ....
>
> if (dev->bus && dev->bus->uevent)
>
> dev->bus->uevent(dev, env) //通过总线的uevent()方法,发送设备状态改变的事件消息
>
> if (dev->class && dev->class->dev_uevent)
>
> dev->class->dev_uevent(dev, env)
>
> if (dev->type && dev->type->uevent)
>
> dev->type->uevent(dev, env)
在该ops的uevent()方法中,会分别调用bus、class、device type的uevent方法来生成事件消息。
总结
kobject
可以看成是一个基类,这个基类通过ktype
属性实现了资源回收的功能,至于kset
,负责记录一组kobject
,我们将其看成是一个集合,负责事件管理。
从此,其他对象就可以通过包含kobject
来实现上层的功能。
kobject_create_and_add解析
// lib/kobject.c
/**
* kobject_create_and_add - 动态创建一个kobject结构并注册到sysfs
*
* @name: kobject的名称
* @parent: kobject的parent kobject of this kobject, 如果有的话
*
* 该方法动态创建一个kobject结构并注册到sysfs。当你完成该结构
* 之后. 调用kobject_put(),这样该结构在不再使用时将会动态的释放。
*
* 如果该kobject无法被创建,将会返回NULL。
*/
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
struct kobject *kobj;
int retval;
kobj = kobject_create();
if (!kobj)
return NULL;
retval = kobject_add(kobj, parent, "%s", name);
if (retval) {
printk(KERN_WARNING "%s: kobject_add error: %d\\n", __func__, retval);
kobject_put(kobj);
kobj = NULL;
}
return kobj;
}
kobject_create
/**
* kobject_create - 动态创建一个kobject结构
*
* 动态创建一个kobject结构,并将其设置为一个带默认释放方法的
* 动态的kobject。
*
* 如果无法创建kobject,将会返回NULL。从这里返回的kobject 结构释放
* 必须调用kobject_put() 方法而不是kfree(),因为kobject_init()已经被调用
* 过了。
*/
struct kobject *kobject_create(void)
{
struct kobject *kobj;
kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj)
return NULL;
kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}
kobject_init
/**
* kobject_init - 初始化一个kobject结构
* @kobj: 指向要初始化的kobject的指针
* @ktype: 指向该kobject 的ktype的指针
*
* 该方法会正确的初始化一个kobject来保证它可以被传递给kobject_add()
* 调用.
*
* 该功能被调用后,kobject必须通过调用kobject_put()来清理,而不是直接
* 调用kfree,来保证所有的内存都可以被正确的清理。
*/
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) {
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {
err_str = "must have a ktype to be initialized properly!\\n";
goto error;
}
if (kobj->state_initialized) {
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized object, something is seriously wrong.\\n", kobj);
dump_stack();
}
kobject_init_internal(kobj);
kobj->ktype = ktype;
return;
error:
printk(KERN_ERR "kobject (%p): %s\\n", kobj, err_str);
dump_stack();
}
kobject_init_internal
/* 设置初始化状态 */
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
// 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。
kobj->state_in_sysfs = 0;
// 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。
kobj->state_add_uevent_sent = 0;
// 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。
kobj->state_remove_uevent_sent = 0;
// 标记已经初始化了。
kobj->state_initialized = 1;
}
/**
* kobject_add - kobject添加主方法
* @kobj: 要添加的kobject
* @parent: 指向父kobject的指针
* @fmt: kobject带的名称格式
*
* 本方法中会设置kobject名称并添加到kobject阶层。
*
* 如果设置了@parent, 那么@kobj的parent将会被设置为它.
* 如果 @parent为空, 那么该@kobj的 parent会被设置为与该kobject
* 相关联的. 如果没有kset分配给这个kobject,那么该kobject会放在
* sysfs的根目录。
*
* 如果该方法返回错误,必须调用 kobject_put()来正确的清理该object
* 相关联的内存。
* 在任何情况下都不要直接通过调用to kfree()来直接释放传递给该方法
* 的kobject,那样会导致内存泄露。
*
* 注意,该调用不会创建"add" uevent, 调用方需要为该object设置好所有
* 必要的sysfs文件,然后再调用带UEVENT_ADD 参数的kobject_uevent()
* 来保证用户控件可以正确的收到该kobject创建的通知。
*/
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
{
va_list args;
int retval;
if (!kobj)
return -EINVAL;
if (!kobj->state_initialized) {
printk(KERN_ERR "kobject \'%s\' (%p): tried to add an "
"uninitialized object, something is seriously wrong.\\n",
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval;
retval = kobject_set_name_vargs(kobj, fmt, vargs);
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\\n");
return retval;
}
kobj->parent = parent;
return kobject_add_internal(kobj);
}
/**
* kobject_set_name_vargs - 设置一个kobject的名称
* @kobj: 要设置名称的kobject
* @fmt: 用来构建名称的字符串格式
* @vargs: 构建字符串的参数
*/
int kobject_set_name_vargs(struct kobject *kobj, const char *fmt,
va_list vargs)
{
const char *old_name = kobj->name;
char *s;
if (kobj->name && !fmt)
return 0;
kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs);
if (!kobj->name) {
kobj->name = old_name;
return -ENOMEM;
}
/* ewww... some of these buggers have \'/\' in the name ... */
while ((s = strchr(kobj->name, \'/\')))
s[0] = \'!\';
kfree(old_name);
return 0;
}
API与使用
由于我们现在关心的是设备驱动模型,因此暂不了解。等到具体的代码中,我们再进行解析。
例子:https://blog.csdn.net/yldfree/article/details/84393321
https://www.cnblogs.com/helloahui/p/3674933.html
## Kobject的使用
### 使用流程
kobject是高度抽象的实体,导致**在大多数情况下**,kobject基本上不会单独使用,而是嵌在具体对象中。
其使用流程如下:
1、定义一个`struct kset`类型的指针,并在初始化时为它分配空间,添加到内核中
2、根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有`Kobject`
3、定义一个适合自己的`ktype`,并实现其中回调函数
4、在需要使用到包含`Kobject`的数据结构时,动态分配该数据结构,并分配`Kobject`空间,添加到内核中
5、每一次引用数据结构时,调用`kobject_get`接口增加引用计数;引用结束时,调用`kobject_put`接口,减少引用计数
6、当引用计数减少为0时,`Kobject`模块调用`ktype`所提供的`release`接口,释放上层数据结构以及`Kobject`的内存空间
上面有提过,有一种例外,Kobject不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs中创建一个目录,而不需要其它的kset、ktype的操作。这时可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到kernel中。
### kobject初始化
创建kobject当然必须初始化kobject对象,kobject的一些内部成员需要(强制)通过kobject_init()初始化:
\\```c
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
\\```
在调用kobject_init将kobject注册到sysfs之后,必须调用kobject_add()添加:
\\```c
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
\\```
它正确设置了kobject的名称和父节点,如果这个kobject和一个特定的kset关联,则kobj->kset必须在调用`kobject_add`之前被指定。
若kobject已经跟一个kset关联,在调用kobject_add时可以将这个kobject的父节点设置为NULL,这样它的父节点就会被设置为这个kset本身。
在将kobject添加到kernel时,它的名称就已经被设定好了,代码中不应该直接操作kobject的名称。
如果你必须为kobject改名,则调用kobject_rename()函数实现:
\\```c
int kobject_rename(struct kobject *kobj, const char *new_name);
\\```
kobject_rename内部并不执行任何锁定操作,也没用何时名称是有效的概念。因此调用者必须自己实现完整性检查和保证序列性。
> 有一个函数叫kobject_set_name(),但是它遗留了一些缺陷,目前正在被移除。如果你的代码需要调用这个函数,它是不正确的,需要被修正。
可以通过kobject_name()函数来正确获取kobject的名称:
\\```c
const char *kobject_name(const struct kobject * kobj);
\\```
辅助函数`kobject_init_and_add`用来在同时初始化和添加kobject,它的参数和前面介绍的单独使用kobject_init()、kobject_add()这两个函数时一样
\\```c
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...);
\\```
**创建简单的kobject**
有些时候,开发者希望的只是在sysfs中创建一个目录,并且不会被kset、show、store函数等细节的复杂概念所迷惑。
这里有一个可以单独创建kobject的例外,为了创建这样一个入口,可以通过如下的函数来实现:
\\```c
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
\\```
这个函数会创建一个kobject,并把它的目录放到指定父节点在sysfs中目录里面。
可以通过如下的函数简单创建kobject相关联的属性:
\\```c
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
\\```
或者
\\```c
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
\\```
这两种方法都包含了属性类型,和通过`kobject_create_and_add()`创建的kobject类型;属性类型可以直接使用kobj_attribute类型,所以不需要特别创建自定义属性。
### 分配和释放
以上是关于(todo)Linux 内核:设备驱动模型sysfs与kobject基类的主要内容,如果未能解决你的问题,请参考以下文章