Linux设备模型(二) kobject和kset

时间:2022-01-22 11:16:59

kobject的作用有:

1、对象的引用计数:来记录Kobject被引用的次数,并在引用次数变为0时把它释放
2、sysfs表示:在sysfs中的每个对象都有对应的kobject,每个kobject在/sys下也会以目录形式出现。
3、数据结构关联:通过链接将不同的层次数据关联,将所有Kobject以层次结构的形式组合起来。
4、热插拔事件处理:kobject子系统将产生的热插拔事件通知用户空间。

在Linux内核里,kobject是组成Linux设备模型的基本数据类型,每个在内核中注册的 kobject 对象都对应于sysfs 文件系统中的一个目录。
从面向对象的角度来说,kobject可以看作是所有设备对象的基类,因为C语言并没有面向对象的语法,所以一般是把kobject内嵌到其他结构体里来实现类似的作用,这里的其他结构体可以看作是kobject的派生类。

Kobject是基本数据类型,每个Kobject都会在”/sys/”文件系统中以目录的形式出现。

/* include/linux/kobject.h 
lib/kobject.c
*/
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的增加、删除、修改等动作时,会通知用户空间。
    这里kobject其实是被kset包含在其列表内,后边会学习二者的关系

    /* include/linux/kobject.h */
    struct kset {
    struct list_head list;
    spinlock_t list_lock;
    struct kobject kobj;
    const struct kset_uevent_ops *uevent_ops;
    };

kobject 中的 ktype 成员是一个指向 kobj_type 结构的指针

/* include/linux/kobject.h */
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文件系统接口,定义读写struct attribute 产生的文件的方法。
  • default_attrs,该种类型的Kobject的atrribute列表(所谓attribute,就是sysfs文件系统中的一个文件)。将会在Kobject添加到内核时,一并注册到sysfs中。
    attribute定义了kobject的属性,default_attrs为二级结构指针,可以对每个kobject使用多个默认属性,最后一个属性使用NULL填充。
  • child_ns_typenamespace,和文件系统(sysfs)的命名空间有关,这里不再详细说明。

ktype中的 attribute 结构体,sysfs_ops的两个成员函数实现读写操作

/* include/linux/sysfs.h */
struct attribute {
const char *name;//属性名
mode_t mode;//属性权限
};

kobj_type 结构体种的 sysfs_ops 包括 store()show()两个成员函数,用于实现属性的读写操作。

/* include/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);
};

kobject的核心功能是:保持一个引用计数,当该计数减为 0 时,自动释放(由kobject模块负责) kobject所占用的内存空间。
kobject必须是动态分配的(只有这样才能动态释放)

而Kobject大多数的使用场景,是内嵌在大型的数据结构中(如ksetdevice_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 机制的理解:
kobject目前的作用好像主要就是在sysfs中创建一个实体的目录,并给这个目录中添加一些属性,让这个目录具有实际意义,但是
kobject一般不单独使用,而是嵌入到上层结构(比如struct device,struct device_driver)当中使用。
kobject的创建者需要直接或间接设置的成员有:ktype、kset和parent。当parent设置为NULL时,kobject默认创建到/sys顶层目录下,否则创建到对应的kobject目录中。

顺便提一下,Kobject是面向对象的思想在Linux kernel中的极致体现,但C语言的优势却不在这里,所以Linux kernel需要用比较巧妙(也很啰嗦)的手段去实现。

以上是kobject的基本组成,接下来将要了解它是怎么用的

/* include/linux/kobject.h */

void kobject_init(struct kobject *kobj, struct kobj_type *ktype);//初始化kobject结构体

int __must_check kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);//添加结构体kobject到内核

int __must_check kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);//初始化并添加kobject

int kobject_set_name(struct kobject *kobj, const char *name, ...);//设置kobject的name

int kobject_set_name_vargs(struct kobject *kobj, const char *fmt, va_list vargs);//设置kobject的name

static inline const char *kobject_name(const struct kobject *kobj)//获取kobject的name
{
return kobj->name;
}

void kobject_del(struct kobject *kobj);//把kobject从设备层次里删除,同时sysfs里相应的目录也会删除

struct kobject * __must_check kobject_create(void);//
struct kobject * __must_check kobject_create_and_add(const char *name, struct kobject *parent);//

int __must_check kobject_rename(struct kobject *, const char *new_name);//修改kobject的name
int __must_check kobject_move(struct kobject *, struct kobject *);//修改kobject的父对象,即移动kobject的路径

struct kobject *kobject_get(struct kobject *kobj);//递增引用计数
void kobject_put(struct kobject *kobj);//递减引用计数并在为0的情况下释放这个对象

char *kobject_get_path(struct kobject *kobj, gfp_t flag);////获取kobject的路径

各个操作函数的解释如下:

kobject_init  初始化通过kmalloc等内存分配函数获得的struct kobject指针。

kobject_add 将初始化完成的kobject添加到kernel中,参数包括需要添加的kobject、该kobject的parent(用于形成层次结构,为NULL表示在/sys创建)、用于提供kobject name的格式化字符串。
kobject_init_and_add 是上面kobject_init和kobject_add的组合

kobject_add_varg 解析格式化字符串,将结果赋予kobj->name,之后调用kobject_add_internal接口,完成真正的添加操作。

kobject_get 递增引用计数,原子操作,并返回一个指向 kobject 的指针,否则返回 NULL。

kobject_put 递减引用计数并在为0的情况下调用kobject_release释放这个对象

kobject_name 获取kobject的name

kobject_set_name 设置kobject的name

kobject_rename 修改kobject的name

kobject_move 修改kobject的父对象,即移动kobject的路径

kobject_del 把kobject从设备层次里删除,同时sysfs里相应的目录也会删除

kobject_create 内部自己通过kmalloc申请空间创建kobject并初始化,指定内核中 dynamic_kobj_ktype 付给初始化

kobject_create_and_add 内部自己通过kmalloc申请空间创建kobject并初始化完后,并调用kobject_add添加到内核

kobject示例:

在这里做一个示例,将理解起来比较抽象的kobject的用法展示一下,这样理解起来就容易多了,也就知道它有什么用和怎么用
利用内核驱动模块实现在 /sys目录下创建一个如下的目录结构

mykobj1/
|-- mykobj2
| |-- name
| `-- value
|-- name
`-- value

代码如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_kobj { //内嵌kobject的结构
int value;
struct kobject kobj;
};

struct my_kobj *obj1, *obj2;

struct attribute name_attr = {
.name = "name", //文件名
.mode = 0444, //指定文件的访问权限
};

struct attribute value_attr = {
.name = "value", //文件名
.mode = 0666, //指定文件的访问权限
};

struct attribute *my_attrs[] = {
&name_attr,
&value_attr,
NULL, //数组指针my_attrs的最后一项一定要赋为NULL,否则会造成内核oops。
};


ssize_t my_show(struct kobject *kobj, struct attribute *attr, char *buffer)
{
struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);
ssize_t count = 0;

if (strcmp(attr->name, "name") == 0) {
count = sprintf(buffer, "%s\n", kobject_name(kobj));
} else if (strcmp(attr->name, "value") == 0) {
count = sprintf(buffer, "%d\n", obj->value);
}

return count;
}

ssize_t my_store(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size)
{
struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);

if (strcmp(attr->name, "value") == 0) {
sscanf(buffer, "%d", &obj->value);
}

return size;
}

struct sysfs_ops my_sysfsops = {
.show = my_show,
.store = my_store,
};

struct kobj_type my_type = {
.release = mykobj_release,
.default_attrs = my_attrs,
.sysfs_ops = my_sysfsops,
};

void mykobj_release(struct kobject *kobj)
{
struct my_kobj *obj = container_of(kobj, struct my_kobj, kobj);
printk(KERN_INFO "mykobj_release %s\n", kobject_name(&obj->kobj));
kfree(obj);
}

static int __init kobj_test_init(void)
{
printk(KERN_INFO "kobj_test_init\n");

obj1 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL); //分配obj1和obj2并赋值
if (!obj1) {
return -ENOMEM;
}
obj1->value = 1;

obj2 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
if (!obj2) {
kfree(obj1);
return -ENOMEM;
}
obj2->value = 2;

/*函数来初始化kobject并把它加入到设备模型的体系架构*/
kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1"); //parent被赋为NULL,表示obj1没有父对象,则mykobj1目录会出现在/sys下
kobject_init_and_add(&obj2->kobj, &my_type, &obj1->kobj, "mykobj2");//parent被赋为obj1,那么my_kobj2的目录会出现在/sys/my_kobj1
/*
kobject_init_and_add这个函数被调用后,kobj的引用计数会初始化为1,所以在module_exit时要记得用kobject_put来释放引用计数。
*/
return 0;
}

static void __exit kobj_test_exit(void)
{
printk(KERN_INFO "kobj_test_exit\n");

/*先子对象,后父对象*/
kobject_del(&obj2->kobj); //先删除
kobject_put(&obj2->kobj); //再减少引用计数

kobject_del(&obj1->kobj);
kobject_put(&obj1->kobj);

return;
}
/*
这里需要指出的是,释放的顺序应该是先子对象,后父对象。
因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数,
所以kobject_del需要调用kobject_put来减少父对象的引用计数。
在本例中,如果先通过kobject_put来释放obj1,
那kobject_del(&obj2->kobj)就会出现内存错误。
*/

module_init(kobj_test_init);
module_exit(kobj_test_exit);

MODULE_LICENSE("Dual BSD/GPL");

在追踪内核源码时遇到了这个结构体kobj_attribute,它是用来读写attribute的,让每一个attribute会对应自己的show/store函数,提高代码灵活性

/* lib/kobject.c 
include/kobject.h
*/
struct kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count);
};

但是这个结构体是内核在使用kobject_create() 和kobject_create_and_add()函数时才能用到的,我们可以借鉴一下这种方法

/* default kobject attribute operations */
static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct kobj_attribute *kattr;
ssize_t ret = -EIO;

kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->show)
ret = kattr->show(kobj, kattr, buf);
return ret;
}

static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct kobj_attribute *kattr;
ssize_t ret = -EIO;

kattr = container_of(attr, struct kobj_attribute, attr);
if (kattr->store)
ret = kattr->store(kobj, kattr, buf, count);
return ret;
}

const struct sysfs_ops kobj_sysfs_ops = {
.show = kobj_attr_show,
.store = kobj_attr_store,
};

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

NOTE:
关于示例中使用到的container_of

/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member. 成员变量的指针
* @type: the type of the container struct this is embedded in. 结构体的类型
* @member: the name of the member within the struct. 结构体中成员变量的名字
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

这个强大的宏函数在Linux内核中使用较多
其作用是在已知某一个成员变量的指针、结构体类型、名字的情况下,计算该成员所在结构体的起始地址。
其目的是根据已知的结构体成员变量的地址ptr,结构体类型type 和该成员名 member 获取这个结构体的起始地址。


总结一下:
kobject是组成Linux设备模型的基础,一个kobject对应sysfs里的一个目录。其实它存在的意义在于把高级对象连接到设备模型上,所以kobject 被嵌入到其他结构中。kobject 可被看作一个最顶层的基类,其他类都它的派生产物。kobject一般不单独使用,而是嵌入到上层结构(比如struct device,struct device_driver)当中使用。
kobject 实现了一系列方法,对自身并没有特殊作用,而对其他对象却非常有效。初始它只被作为一个简单的引用计数, 但随时间的推移,其任务越来越多。现在kobject 所处理的任务和支持代码包括:对象的引用计数:跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时, 该对象将结束自己的有效生命期并可被删除。


kset 内核对象集合

先看kset结构体,从结构体就能看出它和kobject的关系,在内核中用 kset 数据结构表示如下:

struct kset
{
struct list_head list; //用于连接该 kset 中所有 kobject 的链表头
spinlock_tlist_lock;
struct kobjectkobj; //嵌入的 kobject
struct kset_uevent_ops * uevent_ops;//事件操作集
};

list和 list_lock 用于保存该kset下所有的kobject的链表
kobj 该kset自己的kobject,kset结构里的kobj表明它也是一个kobject,list列表用来组织它所有的子对象,在之前的kobject 的 kset 成员是一个指向所属的 kset 结构的指针。
uevent_ops 该kset的uevent操作函数集。当任何 Kobject需要上报uevent时,都要调用它所从属的kset的 uevent_ops,添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。
因此,如果一个kobject不属于任何kset时,是不允许发送uevent的(这也许就是设置kset的目的)。

一个 kset 是一个嵌入相同类型结构的 kobject 的集合.
kset 的主要功能是容纳; 它可被当作顶层的给 kobjects 的顶层容器类。
实际上,每个 kset 在内部容纳它自己的 kobject,并且它可以用处理kobject的方法处理kset。
值得注意的是 kset 一直在 sysfs 中出现; 一旦一个 kset 已被建立并且加入到系统,会有一个kobject被添加进来,就会在 /sys 中创建一个目录。

kset表示一组kobject的集合,kobject通过kset组织成层次化的结构。
所有属于该kset的kobjetc结构的parent指针指向该kset包含的kobject对象,构成一个父子层次关系这些kobject可以是不同或相同的类型(kobj_type)。

那这个父子层次关系是如何维持的?
因为源码里,在kobject_add的时候,会判断如果kobj->kset存在,它的kobj->parent = kobj->kset->kobj ,即该kobj的父对象为它所属的kset的内部kobj

kset就如同/sys目录下的某个顶层目录,后边的kobject都是其子层。
Linux设备模型(二) kobject和kset

struct kset_uevent_ops
{
int (*filter)(struct kset *kset, struct kobject *kobj);//事件过滤
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);//环境变量设置
};

struct kobj_uevent_env {
char *envp[UEVENT_NUM_ENVP];
int envp_idx;
char buf[UEVENT_BUFFER_SIZE];
int buflen;
};

filter()函数用于过滤掉不需要导出到用户空间的事件
uevent()函数用于导出一些环境变量给用户的热插拔处理程序

同时Linux 内核提供一系列函数操作 kset
void kset_init(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);
struct kset * kset_create_and_add(const char *name, const struct kset_uevent_ops *u, struct kobject *parent_kobj);

static inline struct kset *to_kset(struct kobject *kobj)
{
return kobj ? container_of(kobj, struct kset, kobj) : NULL;
}

static inline struct kset *kset_get(struct kset *k);
{
return k ? to_kset(kobject_get(&k->kobj)) : NULL;
}
static inline void kset_put(struct kset *k)
{
kobject_put(&k->kobj);
}
static inline struct kobj_type *get_ktype(struct kobject *kobj)
{
return kobj->ktype;
}
struct kobject *kset_find_obj(struct kset *, const char *);
struct kobject *kset_find_obj_hinted(struct kset *, const char *, struct kobject *);

以下是kset的常用操作函数,它其实在源码中都是操作其内嵌的kobject,因为对于kset来说,这个kobject 才是核心。

kset_init() 该接口用于初始化已分配的kset,需要注意的时,如果使用此接口,上层软件必须提供该kset中的kobject的ktype
kset_register() 该接口用于初始化并添加kset,先调用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。

get_ktype() 用于获取kobject的ktype

kset_get()和 kset_put()分别递增和递减 kset-> kobject 对象的引用计数

kset_add()和 kset_del() 函数 分别 实现 将指定 keset 对象加入设备层次和从其中删除
kset_register()函数完成 kset 的注册,kset_unregister()函数则完成 kset 的注销
kobject 被创建或删除时会产生事件(event),kobject 所属的 kset 将有机会过滤事件或为用户空间添加信息
每个 kset 能支持一些特定的事件变量,在热插拔事件发生时,kset 的成员函数可以设置一些事件变量,这些变量将被导出到用户空间。
kset 的uevent_ops 成员是执行该 kset 事件操作集 kset_uevent_ops 的指针

源码示例

my_kset/
|-- mykobj1
| |-- name
| `-- val
`-- mykobj2
|-- name
`-- va

kset的源码和kobject的基本类似

struct my_kobj *obj1, *obj2;  
struct kset *my_kset;

int kset_filter(struct kset *kset, struct kobject *kobj)
{
printk("Filter: kobj %s.\n",kobj->name);
return 1;
}

const char *kset_name(struct kset *kset, struct kobject *kobj)
{
static char buf[20];
printk("Name: kobj %s.\n",kobj->name);
sprintf(buf,"%s","kset_name");
return buf;
}

int kset_uevent(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env)
{
int i = 0;
printk("uevent: kobj %s.\n",kobj->name);

while( i < env->envp_idx){
printk("%s.\n",env->envp[i]);
i++;
}

return 0;
}

struct kset_uevent_ops uevent_ops =
{
.filter = kset_filter,
.name = kset_name,
.uevent = kset_uevent,
};

static int __init kset_test_init(void)
{
printk(KERN_INFO "kobj_test_init\n");

my_kset = kset_create_and_add("my_kset", &uevent_ops, NULL);
if (!my_kset) {
return -ENOMEM;
}

obj1 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL); //分配obj1和obj2并赋值
if (!obj1) {
return -ENOMEM;
}
obj1->value = 1;

obj2 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
if (!obj2) {
kfree(obj1);
return -ENOMEM;
}
obj2->value = 2;

//主要是给两个kobj加上父对象,即kset中的kobj
obj1->kobj.kset = my_kset;
obj2->kobj.kset = my_kset;

/*函数来初始化kobject并把它加入到设备模型的体系架构*/
kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1");//parent被赋为NULL,内部会自动将kset->kobj赋给parent
kobject_init_and_add(&obj2->kobj, &my_type, NULL, "mykobj2");//parent被赋为NULL,内部会自动将kset->kobj赋给parent

return 0;
}
static void __exit kset_test_exit(void)
{
printk(KERN_INFO "kobj_test_exit\n");

/*先子对象,后父对象*/
kobject_del(&obj2->kobj); //先删除
kobject_put(&obj2->kobj); //再减少引用计数

kobject_del(&obj1->kobj);
kobject_put(&obj1->kobj);

//删除kset
kset_unsigned(my_kset);

return;
}

module_init(kset_test_init);
module_exit(kset_test_exit);

MODULE_LICENSE("Dual BSD/GPL");