本文目录
- 一、知识点
- 1. 设备模型
- 2. sysfs 文件系统
- 3. kobject、kset设备模型框架
- 二、kobject实验
- 1. 创建kobject
- 2. 释放kobject
- ★示例
- 三、kset实验
- 1. 创建kset
- 2. 注销kset
- ★示例
- 四、引用计数器
- 1. 概念
- 2. 为什么要引入引用计数器?
- 3. 常用函数
- (1)初始化计数器值为1
- (2)计数器值+1
- (3)计数器值-1
- 4. 实验
一、知识点
1. 设备模型
Linux 支持世界上几乎所有的,不同功能的硬件。所以Linux驱动一定要跨平台。而且现在支持的硬件数量在一直增加,代码的复杂程度也在上升。为了做好设备驱动的管理,并降低驱动开发难度。兼容设备的热拔插和电源管理。Linux对硬件设备进行了分类和归纳,并抽象出来了一套标准的数据结构和接口。这个就是设备模型。
Linux引入了设备驱动模型分层的概念, 将我们编写的驱动代码分成了两块:设备与驱动。设备负责提供硬件资源而驱动代码负责去使用这些设备提供的硬件资源。 并由总线将它们联系起来。
设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念:
①设备(device) :挂载在某个总线的物理设备;
②驱动(driver) :与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;
③总线(bus) :负责管理挂载对应总线的设备以及驱动;
④类(class) :对于具有相同功能的设备,归结到一种类别,进行分类管理;
2. sysfs 文件系统
sysfs文件系统是 Linux2.6版本引入的虚拟文件系统。sysfs把连接在系统上的设备模型组织成为一个分级的层次视图。并且可以向用户空间导出内核据结构以及属性。
和设备模型有关的文件夹有class
、devices
、bus
这三个文件夹。
(1)bus文件夹:这个文件夹下的所有目录是 Linux系统支持并且已经注册的总线。从总线这个角度展示现在有哪些总线以及总线下连接了什么设备和驱动。
(2)devices文件夹:该文件夹下的所有目录是连接到总线的全部设备,从设备级联角度进行展示。
(3)class文件夹:对设备进行归类。对于具有相同功能的设备,归结到一种类别。类下的所有设备都是/sys/devices下的设备的软连接。
3. kobject、kset设备模型框架
kobject是一个面向对象的管理机制,是构成设备上述设备模型的核心结构,在内核中注册一个kobject,对应就是在sysfs文件系统中创建一个目录和目录里的一个文件夹。即一个kobject对应/sys下一个目录。
在实际使用 kobject 的时候,一般不会单独使用。通常要嵌入到一个数据结构中。这样就可以把高级对象接入到设备模型里面。举例如下:
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj; //将kobj 内嵌到device设备下。
const char *init_name; /* initial name of the device */
const struct device_type *type;
...
}
//平台总线
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev; //包含device结构体。
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
二、kobject实验
kobject 结构体如下,头路径:#include <linux/kobject.h>
struct kobject {
char * k_name;
char name[KOBJ_NAME_LEN];
struct kref kref;
struct list_head entry;
struct kobject * parent;
struct kset * kset;
struct kobj_type * ktype;
struct dentry * dentry;
};
1. 创建kobject
方式一:
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
/*
返回值:struct kobject * ,接收kobject。
参数:
const char *name:创建的kobject的文件夹的名称。
struct kobject *parent:创建的kobject 文件夹的父目录。
*/
方式二:
// 1. 先使用kzalloc给kobject申请一个地址空间。
struct kobject *mykoject;
struct kobj_type *myktype;
mykoject =kzalloc(sizeof(struct kobject), GFP_KERNEL);
// 2. 使用kobject_init_and_add初始化并添加一个kobject。
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...)
/*
struct kobject *kobj: kobject结构体。
struct kobj_type *ktyp:类型。
struct kobject *parent:创建的kobject 文件夹的父目录。
const char *fmt:名称。
使用示例:ret=kobject_init_and_add(mykoject, &myktype ,NULL, "mykojecte");
*/
2. 释放kobject
注意:在释放kobj时,要先释放子目录的kobj,在释放其父目录的kobj!
void kobject_put(struct kobject *kobj);
★示例
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h> // For kzalloc
struct kobject *mykobject01;
struct kobject *mykobject02;
struct kobject *mykobject03;
struct kobj_type mytype;
static int __init mykobj_init(void)
{
int ret;
//使用方式一创建kobject。
mykobject01 = kobject_create_and_add("mykobject01", NULL);
if (!mykobject01)
return -ENOMEM;
mykobject02 = kobject_create_and_add("mykobject02", mykobject01);
if (!mykobject02) {
kobject_put(mykobject01);
return -ENOMEM;
}
//使用方式二创建kobject。
mykobject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
if (!mykobject03) {
kobject_put(mykobject02);
kobject_put(mykobject01);
return -ENOMEM;
}
ret = kobject_init_and_add(mykobject03, &mytype, NULL, "mykobject03");
if (ret) {
kobject_put(mykobject03);
kobject_put(mykobject02);
kobject_put(mykobject01);
return ret;
}
return 0;
}
static void __exit mykobj_exit(void)
{
kobject_put(mykobject03);
kobject_put(mykobject02); //先释放子目录
kobject_put(mykobject01); //再释放父目录
}
module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE("GPL");
三、kset实验
当多个kobject属于同一类的时候,为了方便管理,就引入了Kset。Kset可以认为是一组kobject的集合,是kobject的容器。比如/sys/bus下就属于同一类kobject。kset结构体如下,头文件路径:#include <linux/kobject.h>
struct kset {
struct subsystem * subsys;
struct kobj_type * ktype;
struct list_head list;
struct kobject kobj;
struct kset_hotplug_ops * hotplug_ops;
};
1. 创建kset
struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
/*
const char *name:kest文件夹的名字
const struct kset_uevent_ops *uevent_ops:指向 kset_uevent_ops 结构的指针,该结构包含 kset 的用户空间事件操作。常为NULL。
struct kobject *parent_kobj :父节点目录
*/
2. 注销kset
void kset_unregister(struct kset *k);
★示例
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/slab.h> // For kzalloc
struct kobject *mykobject01;
struct kobject *mykobject02;
struct kset *my_kset;
struct kobj_type mytype;
static int __init mykobj_init(void)
{
int ret;
//1. 创建kest
my_kset=kset_create_and_add("my_kset",NULL,NULL); //在sys下创建my_kset目录.
//2. 创建kobject1
mykobject01= kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject01->kset=my_kset;
ret = kobject_init_and_add(mykobject01, &mytype, NULL, "mykobject01");
//3. 创建kobject2
mykobject02= kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject02->kset=my_kset;
ret = kobject_init_and_add(mykobject02, &mytype, NULL, "mykobject02");
return 0;
}
static void __exit mykobj_exit(void)
{
kobject_put(mykobject01);
kobject_put(mykobject02);
kset_unregister(my_kset);
}
module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE("GPL");
四、引用计数器
1. 概念
引用计数器是一个计数器,它记录了某个资源当前被多少个用户或对象引用。当资源被引用时,引用计数器增加;当引用被释放时,引用计数器减少。当引用计数器减少到零时,表示资源不再被任何对象引用,此时可以安全地释放资源。
2. 为什么要引入引用计数器?
答:如果我们写了一个字符驱动。当硬件设备插上时,系统会生成一个设备节点。用户在应用空间操作这个设备节点就可以操作设备。如果此时将硬件断开。驱动是不是就要立刻释放呢?如果立刻释放,应用程序是不是就崩了呢。所以要等应用程序关闭,在去释放驱动。要如何实现呢?在 Linux 系统中是通过引用技术去来实现的。比如用 kref这个变量记录某个驱动或者某块内存的引用次数。初始值为1,每引用一次+1,每取消引用一次-1,当计数值为0的时候。自动调用自定义的释放函数进行释放驱动或者内存。
3. 常用函数
引用计数器使用结构体struct kref
来描述,结构体如下。头文件路径:#include <linux/kref.h>
struct kref {
atomic_t refcount;
};
在使用引用计数器时,结构体kref一般被嵌入进其他结构中来使用。如结构体 kobject。当然也可以在自定义的结构体中使用该结构体。以下是对原子操作的进一步封装的函数。
(1)初始化计数器值为1
void kref_init(struct kref *kref);
(2)计数器值+1
void kref_get(struct kref *kref);
(3)计数器值-1
当计数器的值为0时,会自动调用自定义的release函数执行释放操作。kobject_put是对该函数的进一步封装。
int kref_put(struct kref *kref, void (*release)(struct kref *kref));
//void (*release)(struct kref *kref):自定义的release函数。
4. 实验
现象:当我们创建一个kobj时,该kobj里的引用计数器就会自动进行+1操作(初值为0)。当我们卸载一个kobj时,该kobj里的引用计数器就会自动进行-1操作,这是因为kobj的创建和卸载函数是对引用计数器操作函数的进一步封装,实质还是对计数器的操作。
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/kernel.h>
#include <linux/slab.h> // For kzalloc
#include <linux/kref.h>
#include <linux/atomic.h>
struct kobject *mykoject01;
struct kobject *mykoject02;
struct kobject *mykoject03;
struct kobj_type mytype;
static int __init mykobj_init(void)
{
int ret;
mykoject01 = kobject_create_and_add("mykoject01", NULL);
printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
mykoject02 = kobject_create_and_add("mykoject02", mykoject01);
printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
mykoject03 = kzalloc(sizeof(struct kobject), GFP_KERNEL);
ret = kobject_init_and_add(mykoject03, &mytype, NULL, "mykoject03");
printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));
return 0;
}
static void __exit mykobj_exit(void)
{
printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));
kobject_put(mykoject01);
printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));
kobject_put(mykoject02);
printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));
kobject_put(mykoject03);
printk("mykoject01 kref is %d\n", atomic_read(&mykoject01->kref.refcount));
printk("mykoject02 kref is %d\n", atomic_read(&mykoject02->kref.refcount));
printk("mykoject03 kref is %d\n", atomic_read(&mykoject03->kref.refcount));
}
module_init(mykobj_init);
module_exit(mykobj_exit);
MODULE_LICENSE("GPL");
现象:当kobj2卸载时,kobj1的计数器也会-1。
理解: