Linux驱动学习之设备驱动模型基础--kobject/kset/kref/ktype

道阻且长,行则将至,行而不辍,未来可期

概要

kobject和kset构建了/sys/bus/下的目录结构,而我们要学习的总线都在这个目录下,所以我们要先学习设备模型的框架,本章我们将以实际创建kobject和kset来分析,我们所看到总线设备模型是怎么被创建的

设备模型的框架

在我们系统总线目录下,存在着许多的总线,例如i2c,spi,platform等等,他们是由kobject和kset组合而成
在这里插入图片描述

kobject

kobject的全称是“kernel object”,即内核对象,每一个kobject都会对应系统/sys/目录下的一个目录
kobject在内核中是一个结构体,定义在include/linux/kobject.h

struct kobject {
	const char		*name;
	struct list_head	entry;
	struct kobject		*parent;
	struct kset		*kset;
	struct kobj_type	*ktype;
	struct kernfs_node	*sd; /* sysfs directory entry */
	struct kref		kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release;
#endif
	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;
};

又因为一个kobject是/sys/下的一个目录,而目录又是多个层次的,所以对应的kobject的树状图如下:
不同的kobject可以通过*parent指针来表示对应关系
在这里插入图片描述

kset

kset是一组kobject的集合,kset将多个kobject链接起来
kset在内核里面使用struct kset 表示,定义在include/linux/kobject.h
因为kset结构体中包含kobject,所以我们也可以将kset看成/sys/下的一个目录

struct kset {
	struct list_head list;
	spinlock_t list_lock;
	struct kobject kobj;
	const struct kset_uevent_ops *uevent_ops;
};

kset和kobject之间的关系

这里比较抽象,kset的list通过与kobject的entry相关联,可以将多个kobject连接起来,因为kset中也包含kobject,所以kset中也有*parent节点,可以通过kset.kobj->parent来指定
在这里插入图片描述

kref(引用计数器)

概念

在Linux中,如果我们写了一个字符驱动,当硬件设备插上时,系统会生成一个设备节点,用户在应用空间操作这个设备节点就可以操作设备,如果此时硬件断开,驱动是不是就要立即释放呢,释放之后是不是应用程序就崩溃了,所以要等应用程序释放,再去释放驱动
这时候就是通过引用计数器来实现,比如使用kref来记录某个驱动或者内存的引用次数,初始值为1,每引用一次+1,每释放一次-1,当计数值为0时,自动调用自定义的释放函数进行释放驱动或者内存
在使用时一般嵌套在其他结构体中
在这里插入图片描述

代码分析
<include/linux/kref.h>
struct kref {
	atomic_t refcount;
};

static inline void kref_init(struct kref *kref)
{
	//初始化为1
	atomic_set(&kref->refcount, 1);
}
//将计数器的值加一
static inline void kref_get(struct kref *kref)
//将计数器的值减一,当计数值为0时,自动调用release函数
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
	return kref_sub(kref, 1, release);
}
static inline int kref_sub(struct kref *kref, unsigned int count,
	     void (*release)(struct kref *kref))
{
	WARN_ON(release == NULL);

	if (atomic_sub_and_test((int) count, &kref->refcount)) {
		release(kref);//释放
		return 1;
	}
	return 0;
}

ktype

通过ktype定义属性文件的读写,可以实现用户空间直接对内核空间的操控,就比如我们可以直接通过读写设置gpio的值

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

实例解析kobject和kset

我们实际写一个kobject的对象,并以代码及现象来说明他是如何被创建的

创建kobject对象

这个我们主要使用了两种方法分别在/sys/下创建了mykobject01和mykobject03目录,以及在mykobject01下创建了mykobject02目录

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

struct  kobject *mykobject01;
struct  kobject *mykobject02;
struct  kobject *mykobject03;

struct kobj_type mytype;

static int mykobj_init(void)
{
    int ret;
    //创建kobject的第一种方法
    //创建并添加了名为“mykobject01”的kobject对象,父kobject为null,当为null时,说明在/sys/下创建
    mykobject01 = kobject_create_and_add("mykobject01", NULL);
    //创建并添加了名为“mykobject02”的kobject对象,父kobject为mykobject01
    mykobject02 = kobject_create_and_add("mykobject02", mykobject01);

    /*创建kobject的第二种方法*/
    //1.使用kzalloc函数分配一个kobject对象的内存
    mykobject03 = kzalloc(sizeof(struct kobject),GFP_KERNEL);
    //2.初始化并添加到内核中,名为"mykobject03"
    ret = kobject_init_and_add(mykobject03,&mytype,NULL,"%s","mykobject03");


    return 0;
}

static void mykobj_exit(void)
{
    kobject_put(mykobject01);
    kobject_put(mykobject02);
    kobject_put(mykobject03);
}

module_init(mykobj_init);
module_exit(mykobj_exit);

MODULE_LICENSE("GPL");

在这里插入图片描述
这里有个疑问,为什么在mykobject01 = kobject_create_and_add("mykobject01", NULL);中输入为null的话,会在/sys/下创建mykobject01
我们通过追踪代码可以得知

kobject_create_and_add
	->kobject_add
		-> kobject_add_varg
			->kobject_add_internal
				->create_dir
					->sysfs_create_dir_ns{
						//这里表示,当kobj没有父节点的时候,父节点就为/sys/
						if (kobj->parent)
							parent = kobj->parent->sd;
						else
							/*struct kernfs_node *sysfs_root_kn;
							sysfs_root_kn 是一个 struct kernfs_node 类型的指针
							代表了 sysfs 文件系统(/sys/)的根节点
							在Linux/fs/sysfs/mount.c中被创建*/
							parent = sysfs_root_kn;
						}

创建kset对象

kset是kobject的集合,所以我们在创建kset之后,将之设置为创建的kobject的kset属性就可以将他们链接起来了

#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/device.h>
// 定义kobject结构体指针
struct  kobject *mykobject01;
struct  kobject *mykobject02;
//定义kset结构体指针
struct kset *mykset;
struct kobj_type mytype;
static int mykset_init(void)
{
    int ret ;
    //创建并添加kset,名称为“mykset”,父kobject为NULL,属性为NULL
    mykset = kset_create_and_add("mykset",NULL,NULL);

    //为mykobject01分配一个空间,大小为struct kobject的大小,标志为GFP_KERNEL
    mykobject01 = kzalloc(sizeof(struct kobject),GFP_KERNEL);
    //将mykset设置为mykobject01的kset属性
    mykobject01->kset = mykset;
    //初始化并添加mykobject01,类型为mytype,父kobject为NULL,格式化字符串为“mykobject01”
    ret = kobject_init_and_add(mykobject01,&mytype,NULL,"%s","mykobject01");

    //为mykobject02做同样的事
    mykobject02 = kzalloc(sizeof(struct kobject),GFP_KERNEL);
    mykobject02->kset = mykset;
    ret = kobject_init_and_add(mykobject02,&mytype,NULL,"%s","mykobject02");
    return 0;
}

static void mykset_exit(void)
{
    kobject_put(mykobject01);
    kobject_put(mykobject02);
}

module_init(mykset_init);
module_exit(mykset_exit);

MODULE_LICENSE("GPL");

如下图所示,我们创建的mykset就会出现在/sys/下,因为myset中包含konbject属性,所以我们也可以认为kset也是一个目录
在这里插入图片描述
但是我们发现,按照我们创建kobject案例,当kobject_init_and_add(mykobject02,&mytype,NULL,"%s","mykobject02");中的父节点为null是,我们创建的kobject应该出现在/sys/目录下,为啥会出现在mykset目录下
分析源码我们可以得出原因,所以我们会在/sys/mykset下看到

kobject_init_and_add
	->kobject_add_varg
		->kobject_add_internal{
			parent = kobject_get(kobj->parent);
			/* join kset if set, use it as parent if we do not already have one */
			//如果创建了kset属性且parent为null的话,父目录设置为kset属性
			if (kobj->kset) {
				if (!parent)
					parent = kobject_get(&kobj->kset->kobj);
			kobj_kset_join(kobj);
			kobj->parent = parent;
		}

总结创建目录的规律

  1. 无父目录、无kset,则将在sysfs的根目录下,及/sys/下创建目录
  2. 无父目录、有kset,则将在kset下创建目录,并将kobj加入kset.list
  3. 有父目录、无kset,则将在parent下创建目录
  4. 有父目录、有kset,则将在parent下创建目录,并将kobj加入kset.list

kref在kobject中的使用

当在父目录下创建子目录时,父目录的计数值也要加1,释放也要减1

//创建时
kobject_create_and_add
	->	kobject_add
		->kobject_add_varg
			->kobject_add_internal
				->kobject_get
					->kref_get //+1
//释放时
kobject_put
	->	kref_put(&kobj->kref, kobject_release);//当值为0时加1

ktype在kobject中的使用

当我们创建的kobj对象之后,特别是使用方法2我们还申请了内存,所以在释放的时候我们应该怎么去释放函数,我们追踪代码来分析,在上面提到,当计数值为1的时候,我们调用relase函数,最后发现调用的时ktype中的release函数,通过创建kobj的第一种方法中,系统自动帮我们申请了内存,自动帮我们写好了ktype的releale函数,所以我们在使用方法2的时候要自己完善ktype函数

kobject_release
	->kobject_cleanup{
		struct kobj_type *t = get_ktype(kobj);
		//当ktype函数中没有ktype
		if (t && !t->release)
			pr_debug("kobject: '%s' (%p): does not have a release() "
			 	"function, it is broken and must be fixed.\n",
			 kobject_name(kobj), kobj);
		//当ktype中有ktype时调用release
		if (t && t->release) {
			pr_debug("kobject: '%s' (%p): calling ktype release\n",
			 kobject_name(kobj), kobj);
			t->release(kobj);
	}

//完善的ktype函数
static struct kobj_type dynamic_kobj_ktype = {
	.release	= dynamic_kobj_release,
};
static void dynamic_kobj_release(struct kobject *kobj)
{
	pr_debug("kobject: (%p): %s\n", kobj, __func__);
	//在这里释放我们的函数
	kfree(kobj);
}

小结

kobject/kset/kref/ktype作为我们设备驱动模型的基石,在我们创建总线设备驱动模型中起着重要的作用,理解了这些,我们在后面中会更加快速的高效的学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值