Linux 设备模型之 kobject 内嵌结构

问题描述:前文我们知道了/sys是包含内核和驱动的实施信息的,用户可以通过 /sys 这个接口,用户通过这个接口可以一览内核设备的全貌。本文将从Linux内核的角度来看一看这个设备模型是如何构建的。

1、kobject 结构

在Linux内核里,kobject是组成Linux设备模型的基础,一个kobject对应sysfs里的一个目录。从面向对象的角度来说,kobject可以看作是所有设备对象的基类,因为C语言并没有面向对象的语法,所以一般是把kobject内嵌到其他结构体里来实现类似的作用,这里的其他结构体可以看作是kobject的派生类。Kobject为Linux设备模型提供了很多有用的功能,比如引用计数,接口抽象,父子关系等等。引用计数本质上就是利用kref实现的。

另外,Linux设备模型还有一个重要的数据结构kset。Kset本身也是一个kobject,所以它在sysfs里同样表现为一个目录,但它和kobject的不同之处在于kset可以看作是一个容器,如果你把它类比为C++里的容器类如list也无不可。Kset之所以能作为容器来使用,其内部正是内嵌了一个双向链表结构struct list_head。

kobject 在内核中的描述

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

内核里的设备之间是以树状形式组织的,在这种组织架构里比较靠上层的节点可以看作是下层节点的父节点,反映到sysfs里就是上级目录和下级目录之间的关系,在内核里,正是kobject帮助我们实现这种父子关系。在kobject的定义里,name表示的是kobject在sysfs中的名字;指针parent用来指向kobject的父对象;Kref大家应该比较熟悉了,kobject通过它来实现引用计数;Kset指针用来指向这个kobject所属的kset,下文会再详细描述kset的用法;对于ktype,如果只是望文生义的话,应该是用来描述kobject的类型信息。Ktype的定义如下:

struct kobj_type {
    void (*release)(struct kobject *kobj);
    const struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
};
函数指针release是给kref使用的,当引用计数为0这个指针指向的函数会被调用来释放内存。sysfs_ops和attribute是做什么用的呢?前文里提到,一个kobject对应sysfs里的一个目录,而目录下的文件就是由sysfs_ops和attribute来实现的,其中,attribute定义了kobject的属性,在sysfs里对应一个文件,sysfs_ops用来定义读写这个文件的方法。Ktype里的attribute是默认的属性,另外也可以使用更加灵活的手段,本文的重点还是放在default attribute。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kobject.h>

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

struct my_kobj *obj1, *obj2;
struct kobj_type my_type;

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

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

struct attribute *my_attrs[] = {
	&name_attr, 
	&val_attr,
	NULL,
};

/*
结构体struct attribute里的name变量用来指定文件名,mode变量用来指定文件的访问权限。
这里需要着重指出的是,数组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, "val") == 0) {
		count = sprintf(buffer, "%d\n", obj->val);
	}

	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, "val") == 0) {
		sscanf(buffer, "%d", &obj->val);
	}

	return size;
}

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

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

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

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

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

	my_type.release = obj_release;
	my_type.default_attrs = my_attrs;
	my_type.sysfs_ops = &my_sysfsops;

	kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1"); /*函数来初始化kobject并把它加入到设备模型的体系架构*/
	kobject_init_and_add(&obj2->kobj, &my_type, &obj1->kobj, "mykobj2");
/*
	kobject_init用来初始化kobject结构,kobject_add用来把kobj加入到设备模型之中。
	在实作中,我们先对obj1进行初始化和添加的动作,调用参数里,parent被赋为NULL,表示obj1没有父对象,反映到sysfs里,
	my_kobj1的目录会出现在/sys下,obj2的父对象设定为obj1,那么my_kobj2的目录会出现在/sys/my_kobj1下面。
	前面提到,kobject也提供了引用计数的功能,虽然本质上是利用kref,但也提供了另外的接口供用户使用。
	kobject_init_and_add和kobject_init这两个函数被调用后,kobj的引用计数会初始化为1,
	所以在module_exit时要记得用kobject_put来释放引用计数。
*/	
	return 0;
}

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

	kobject_del(&obj2->kobj); /*先子对象,后父对象*/
	kobject_put(&obj2->kobj);
	
	kobject_del(&obj1->kobj);
	kobject_put(&obj1->kobj);

	return;
}
/*
kobject_del的作用是把kobject从设备模型的那棵树里摘掉,同时sysfs里相应的目录也会删除。
这里需要指出的是,释放的顺序应该是先子对象,后父对象。
因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数,
所以kobject_del需要调用kobject_put来减少父对象的引用计数。在本例中,如果先通过kobject_put来释放obj1,
那kobject_del(&obj2->kobj)就会出现内存错误。
*/
module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

源代码下载


2、结构改进

在这个实作中,我们建立了两个对象obj1和obj2,obj1是obj2的父对象,如果推广开来,obj1可以有更多的子对象。在Linux内核中,这种架构方式其实并无太大的实际价值,有限的用处之一是在sysfs里创建子目录(Linux内核里有这种用法,这种情况下,直接调用内核提供的kobject_create来实现,不需要自定义数据结构并内嵌kobject),而且,创建子目录也是有其他的办法的。我们知道,Linux设备模型最初的目的是为了方便电源管理,这就需要从上到下的遍历,在这种架构里,通过obj1并无法访问其所有的子对象。这个实作最大的意义在于可以让我们比较清晰的理解kobject如何使用。通常情况下,kobject只需要在叶节点里使用,上层的节点要使用kset。

struct kset {
    struct list_head list;
    spinlock_t list_lock;
    struct kobject kobj;
    const struct kset_uevent_ops *uevent_ops;
};
Kset结构里的kobj表明它也是一个kobject,list变量用来组织它所有的子对象。

<span style="font-family:Microsoft YaHei;font-size:12px;">#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kobject.h>

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

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

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

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

struct attribute *my_attrs[] = {
	&name_attr, 
	&val_attr,
	NULL,
};
/*
结构体struct attribute里的name变量用来指定文件名,mode变量用来指定文件的访问权限。
这里需要着重指出的是,数组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, "val") == 0) {
		count = sprintf(buffer, "%d\n", obj->val);
	}

	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, "val") == 0) {
		sscanf(buffer, "%d", &obj->val);
	}

	return size;
}

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

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

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

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

	obj1 = kzalloc(sizeof(struct my_kobj), GFP_KERNEL);
	if (!obj1) {
		kset_unregister(my_kset);
		return -ENOMEM;
	}
	obj1->val = 1;

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

	obj1->kobj.kset = my_kset;
	obj2->kobj.kset = my_kset;

	my_type.release = obj_release;
	my_type.default_attrs = my_attrs;
	my_type.sysfs_ops = &my_sysfsops;

	kobject_init_and_add(&obj1->kobj, &my_type, NULL, "mykobj1");/*函数来初始化kobject并把它加入到设备模型的体系架构*/
	kobject_init_and_add(&obj2->kobj, &my_type, NULL, "mykobj2");
/*
	kobject_init用来初始化kobject结构,kobject_add用来把kobj加入到设备模型之中。
	在实作中,我们先对obj1进行初始化和添加的动作,调用参数里,parent被赋为NULL,表示obj1没有父对象,反映到sysfs里,
	my_kobj1的目录会出现在/sys下,obj2的父对象设定为obj1,那么my_kobj2的目录会出现在/sys/my_kobj1下面。
	前面提到,kobject也提供了引用计数的功能,虽然本质上是利用kref,但也提供了另外的接口供用户使用。
	kobject_init_and_add和kobject_init这两个函数被调用后,kobj的引用计数会初始化为1,
	所以在module_exit时要记得用kobject_put来释放引用计数。
*/
	return 0;
}

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

	kobject_del(&obj1->kobj);/*先子对象,后父对象*/
	kobject_put(&obj1->kobj);

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

	kset_unregister(my_kset);

	return;
}
/*
kobject_del的作用是把kobject从设备模型的那棵树里摘掉,同时sysfs里相应的目录也会删除。
这里需要指出的是,释放的顺序应该是先子对象,后父对象。
因为kobject_init_and_add和kobject_add这两个函数会调用kobject_get来增加父对象的引用计数,
所以kobject_del需要调用kobject_put来减少父对象的引用计数。在本例中,如果先通过kobject_put来释放obj1,
那kobject_del(&obj2->kobj)就会出现内存错误。
*/

module_init(mykset_init);
module_exit(mykset_exit);

MODULE_LICENSE("GPL");
</span>

在module_init里,我们首先调用kset_create_and_add创建my_kset,接下来把my_kset赋给obj1和obj2,最后调用kobject_init_and_add来添加obj1和obj2。这里需要注意的是,kobject_init_and_add参数里的parent都是NULL,在这种情况下,obj1和obj2的父对象由kobject结构里的kset指针决定,在这个实作里就是my_kset。在module_exit里,我们还需要额外调用kset_unregister来释放之前创建的my_kset.




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。