Linux设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。
现在内核使用设备模型支持多种不同的任务:
设备模型 | 描述 |
---|---|
电源管理和系统关机 | 这些需要对系统结构的理解,设备模型使OS能以正确顺序遍历系统硬件。 |
与用户空间的通讯 | sysfs 虚拟文件系统的实现与设备模型的紧密相关, 并向外界展示它所表述的结构。向用户空间提供系统信息、改变操作参数的接口正越来越多地通过 sysfs , 也就是设备模型来完成。 |
热插拔设备 | 外围设备可根据用户的需要安装与卸载,内核中的热插拔机制可以处理热插拔设备 |
设备类型 | 设备模型包括了将设备分类的机制,在一个更高的功能层上描述这些设备, 并使设备对用户空间可见。 |
对象生命周期 | 设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。 |
Linux 设备模型是一个复杂的数据结构。但对模型的大部分来说, Linux 设备模型代码会处理好这些关系, 而不是把他们强加于驱动作者。模型隐藏于交互的背后,与设备模型的直接交互通常由总线级的逻辑和其他的内核子系统处理。所以许多驱动作者可完全忽略设备模型, 并相信设备模型能处理好他所负责的事。
Kobject、Kset 和 Subsystem
Kobject
kobject是一种数据结构,定义在 <linux/kobject.h> 。
struct kobject {
const char * k_name;/*kobject 的名字数组(sysfs 入口使用的名字)指针;如果名字数组大小小于KOBJ_NAME_LEN,它指向本数组的name,否则指向另外分配的一个名字数组空间 */
char name[KOBJ_NAME_LEN];/*kobject 的名字数组,若名字数组大小不小于KOBJ_NAME_LEN,只储存前KOBJ_NAME_LEN个字符*/
struct kref kref;/*kobject 的引用计数*/
struct list_head entry;/*kobject 之间的双向链表,与所属的kset形成环形链表*/
struct kobject * parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj*/
struct kset * kset;/*指向所属的kset*/
struct kobj_type * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/
struct dentry * dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/
wait_queue_head_t poll;/*等待队列头*/
};
kobject是组成设备模型的基本结构,最初只是被理解为一个简单的引用计数,但是随若时间的推移,它的任务越来越多,因此也有了许多成员。现在 kobject结构所能处理的任务以及它所支持的代码包括:
对象的引用计数
通常一个内核对象被创建时,不可能知道该对象存活的时间。跟踪此对象生命周期的一个方法是使用引用计数。当内核中没有代码持有该对象的引用时,该对象将结束自己的有效生命周期,并且可以被删除。
sysfs表述
在sysfs中显示的每一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。
数据结构关联
从整体上看,设备模型是一个友好而复杂的数据结构,通过在其间的大量连接而构成一个多层次的体系结构。 kobject实现了该结构并把它们聚合在一起
热插拔事件处理
当系统中的硬件被热插拔时,在 kobject子系统控制下,将产生事件以通知用户空间
kobject初始化
kobject的初始化较为复杂,但是必须的步骤如下:
1.将整个kobject清零,通常使用memset函数。
2.调用kobject_init()函数,设置结构内部一些成员。所做的一件事情是设置kobject的引用计数为1。
具体的源码如下:
void kobject_init(struct kobject * kobj)/*inkobject.c*/
{
if (!kobj)
return;
kref_init(&kobj->kref);/*设置引用计数为1*/
INIT_LIST_HEAD(&kobj->entry);/*初始化kobject 之间的双向链表*/
init_waitqueue_head(&kobj->poll);/*初始化等待队列头*/
kobj->kset = kset_get(kobj->kset);/*增加所属kset的引用计数(若没有所属的kset,则返回NULL)*/
}
void kref_init(struct kref *kref)/*in kobject.c*/
{
atomic_set(&kref->refcount,1);
smp_mb();
}
static inline struct kset * to_kset(struct kobject * kobj)/*in kobject.h*/
{
return kobj ? container_of(kobj,struct kset,kobj) : NULL;
}
static inline struct kset * kset_get(struct kset * k)/*in kobject.h*/
{
return k ? to_kset(kobject_get(&k->kobj)) : NULL;/*增加引用计数*/
}
3.设置kobject的名字
int kobject_set_name(struct kobject * kobj, const char * fmt, ...);
4.直接或间接设置其它成员:ktype、kset和parent。(重要)
对引用计数的操作
kobject 的一个重要函数是为包含它的结构设置引用计数。只要对这个对象的引用计数存在, 这个对象( 和支持它的代码) 必须继续存在。底层控制 kobject 的引用计数的函数有:
struct kobject *kobject_get(struct kobject *kobj);/*若成功,递增 kobject 的引用计数并返回一个指向 kobject 的指针,否则返回 NULL。必须始终测试返回值以免产生竞态*/
void kobject_put(struct kobject *kobj);/*递减引用计数并在可能的情况下释放这个对象*/
注意:kobject _init 设置这个引用计数为 1,因此创建一个 kobject时, 当这个初始化引用不再需要,应当确保采取 kobject_put 调用。
struct cdev 的引用计数实现如下:
struct kobject *cdev_get(struct cdev *p)
{
struct module *owner = p->owner;
struct kobject *kobj;
if (owner && !try_module_get(owner))
return NULL;
kobj = kobject_get(&p->kobj);
if (!kobj)
module_put(owner);
return kobj;
}
创建一个对 cdev 结构的引用时,还需要创建包含它的模块的引用。因此, cdev_get 使用 try_module_get 来试图递增这个模块的使引用计数。如果这个操作成功, kobject_get 被同样用来递增 kobject 的引用计数。kobject_get 可能失败, 因此这个代码检查 kobject_get 的返回值,如果调用失败,则释放它的对模块的引用计数。