文章目录
道阻且长,行则将至,行而不辍,未来可期 |
概要
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;
}
总结创建目录的规律
- 无父目录、无kset,则将在sysfs的根目录下,及/sys/下创建目录
- 无父目录、有kset,则将在kset下创建目录,并将kobj加入kset.list
- 有父目录、无kset,则将在parent下创建目录
- 有父目录、有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作为我们设备驱动模型的基石,在我们创建总线设备驱动模型中起着重要的作用,理解了这些,我们在后面中会更加快速的高效的学习。