设备模型(一)
一、概述
从2.6内核引入了sysfs文件系统,与proc, devfs, devpty同类别,属于虚拟的文件系统。目的是展示设备驱动模型中各组件的层次关系,第一层目录:block, device, bus, drivers, class, power, firmware.
block 块设备;devices 系统所有的设备并根据设备挂接的总线类型组织成层次结构;bus 系统所有的总线类型;drivers 内核中所有已经注册的设备驱动程序;class 系统中的设备类型(如网卡 设备、声卡设备、输入设备等)。在/sys/bus下和/sys/bus/下也会有设备文件,但那只是符号链接,它指向/sys/devices/下的真实设备。此即为Linux设备模型。
总线(bus)、设备(device)、驱动(driver)3个数据结构构成了设备的上层建筑,而kobject、kset、kobj_type(ktype)这三个数据结构构成了设备模型的经济基础。内核引入内核这概念最主要的目的无非就是为了省电,便于管理。Linux设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。设备模型提供了这个抽象. 现在它用在内核来支持不同的任务, 包括:
1.电源管理,根据设备的层次关系,当系统进入睡眠的时候,不需要一个一个设备的关,只需要关一个总线设备,接在总线下的设备就都会关掉。
2.sysfs虚拟文件系统的实现与设备模型的紧密相关,并向外界展示它所表述的结构。向用户空间提供系统信息、改变操作参数的接口正越来越多地通过 sysfs,也就是设备模型来完成。
3.关于热插拔,这跟扫描有关系,比如说,你把一个设备直接接在USB上,系统就会去扫描设备,并且在USB总线上寻找匹配的设备驱动,最后初始化设备,等待用户使用。
4.设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。Linux设备模型是一个复杂的数据结构。但对模型的大部分来说,Linux设备模型代码会处理好这些关系,而不是把他们强加于驱动作者。模型隐藏于交互的背后,与设备模型的直接交互通常由总线级的逻辑和其他的内核子系统处理。所以许多驱动作者可完全忽略设备模型, 并相信设备模型能处理好他所负责的事。
在具体实现方面分两个层次:
一是底层数据结构来实现基本对象及其层次关系:kobjects和ksets。
二是基于这两个底层数据结构上实现的设备模型:总线,设备,驱动。
二、底层数据结构:kobject,kset
1)Kobject
1.概念
Kobject实现基本的面向对象管理机制,是构成设备模型的核心结构。它与sysfs文件系统紧密相连,在内核中注册每个kobject对象对应sysfs文件系统中的一个目录。
内核通过kobject 结构将各个对象连接起来组成一个分层的结构体系,可以把kobject理解为面向对象的基类,确切的说kobject也可以理解为所有驱动对象的基类,作为基类的kobject并不关心自己是如何实现的,所以,在内核中,没有用kobject直接定义的变量,kobject只是作为一个抽象的基类而存在,而由于Linux内核是C编写的,通过查看device_driver、device等结构体中内嵌了kobject结构体。
2.结构,定义在
struct kobject {
const char * k_name;/*指向设备名称的指针 */
char name[KOBJ_NAME_LEN];/*kobject 的名字数组,设备名称*/
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;/*等待队列头*/
};
2.1 kobj_type结构
//kobject的ktype对象是一个指向kobject_type结构的指针,该结构记录了kobject对象的一些属性。每个kobject都需要对应一个相应的kobj_type结构。
struct kobj_type{
void (*release)(struct kobject *kobj);//release方法用于释放kobject占用的资源,当kobject引用计数为0时被调用。
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;//对应于kobject的目录下一个文件,name就是文件名。
};
2.1.1 kobje_type的attribute结构
struct attribute{
char*name;//属性文件名
struct module *owner;//所属的模块
mode_t mode;//属性文件的操作模式(可读,可写...)
}
2.1.2 kobje_type的struct sysfs_ops结构
struct sysfs_ops
{
ssize_t (*show)(structkobejct *, struct attribute *, char *name);//当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态;
ssize_t (*store)(structkobejct *, struct attribute *, char *name);//当用户写属性文件时,该函数被调用,用于存储用户存入的属性值。
}
3.这个在层次上处理最顶层的kobject结构提供了所有模型需要的最基本的功能:
(1)引用计数:跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时, 该对象将结束自己的有效生命期并可被删除。
(2)sysfs表示每个sys/下的对象对应着一个kobject。
(3)热拔插事件处理。处理设备的热拔插事件。
在 sysfs 中创建kobject的入口是kobject_add的工作的一部分,只要调用 kobject_add 就会在sysfs 中显示,还有些知识值得记住:
(1)kobjects 的 sysfs 入口始终为目录, kobject_add 的调用将在sysfs 中创建一个目录,这个目录包含一个或多个属性(文件);
(2)分配给 kobject 的名字( 用 kobject_set_name ) 是 sysfs 中的目录名,出现在 sysfs 层次的相同部分的 kobjects 必须有唯一的名字. 分配给 kobjects 的名字也应当是合法的文件名字: 它们不能包含非法字符(如:斜线)且不推荐使用空白。
(3)sysfs 入口位置对应 kobject 的 parent 指针。若 parent 是 NULL ,则它被设置为嵌入到新 kobject 的 kset 中的 kobject;若 parent 和 kset 都是 NULL, 则sysfs 入口目录在顶层目录,通常不推荐。
4.相关函数:
void kobjet_init(struct kobject*kobj)//初始化Kobject
int kobject_add(struct kobject*kobj)//将Kobject对象注册到linux系统,如果失败则返回一个错误码.
struct kobject *kobject_get(struct kobject *kobj);/*若成功,递增 kobject 的引用计数并返回一个指向 kobject 的指针,否则返回 NULL。必须始终测试返回值以免产生竞态*/
void kobject_put(struct kobject *kobj);/*递减引用计数并在可能的情况下释放这个对象*/
int kobject_init_and_add(structkobject *kobj, kobj_type *ktype, struct kobject *parent, const *fmt…)
//初始化并注册kobject,kobject传入要初始化的Kobject对象,ktype将在后面介绍到,parent指向上级的kobject对象,如果指定位NULL,将在/sys的顶层创建一个目录。*fmt为kobject对象的名字。
5.实例
#include
#include
#include
#include
#include
#include
#include
void obj_test_release(struct kobject *kobject);
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);
struct attribute test_attr = {
.name = "kobj_config",
.mode = S_IRWXUGO,
};
static struct attribute *def_attrs[] = {
&test_attr,
NULL,
};
struct sysfs_ops obj_test_sysops =
{
.show = kobj_test_show,
.store = kobj_test_store,
};
struct kobj_type ktype =
{
.release = obj_test_release,
.sysfs_ops=&obj_test_sysops,
.default_attrs=def_attrs,
};
void obj_test_release(struct kobject *kobject)
{
printk("eric_test: release .\n");
}
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)
{
printk("have show.\n");
printk("attrname:%s.\n", attr->name);
sprintf(buf,"%s\n",attr->name);
return strlen(attr->name)+2;
}
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)
{
printk("havestore\n");
printk("write: %s\n",buf);
return count;
}
struct kobject kobj;
static int kobj_test_init()
{
printk("kboject test init.\n");
kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");
return 0;
}
static int kobj_test_exit()
{
printk("kobject test exit.\n");
kobject_del(&kobj);
return 0;
}
module_init(kobj_test_init);
module_exit(kobj_test_exit);
在/sys目录下创建了kobject_test目录,在kobject_test目录下有kobj_config文件。
读kobject_config文件则调用了show函数。并在用户空间显示了show返回的kobject对象名字。写kobject_config文件调用了store函数。
2)kset
1.概念
Kset是具有相同类型的kobject的集合,在sysfs中体现成一个目录,在内核中用kset数据结构表示。
一个kset的主要功能是容纳;它可被当作顶层的给kobjects的容器类.实际上,每个kset在内部容纳它自己的 kobject,并且它可以,在许多情况下,如同一个kobject相同的方式被对待.值得注意的是ksets一直在sysfs中出现,一旦一个 kset已被建立并且加入到系统, 会有一个sysfs目录给它.kobjects没有必要在sysfs中出现, 但是每个是 kset 成员的 kobject 都出现在那里.
通俗的讲,kobject建立一级的子目录里面只能包含文件,kset可以为kobject建立多级的层次性的父目录。
2.结构体
如果这个 kobject 是一个kset的成员, kset会提供kobj_type指针。通常情况下kobject只需要在叶节点里使用,上层的节点要使用kset。
struct kset {
struct kobj_type * ktype; /*指向该kset对象类型的指针*/
struct list_head list;/*用于连接该kset中所有kobject以形成环形链表的链表头*/
spinlock_t list_lock;/*用于避免竞态的自旋锁*/
struct kobject kobj; /*嵌入的kobject*/
struct kset_uevent_ops * uevent_ops; //指向热插拔操作表的指针
};
包含在kset中的所有的kobject被组织成一个上相的循环链表list域是该链表的头指针,ktype域指向一个kobj_type结构,被该kset中的所有kobject共享,表示这些对象的类型,kset数据结构还内嵌了一个kobject对象,所有属于这个kset的kobject对象的parent域指向这个内嵌的对象,此外kset还依赖于kobj维护引用计数:这就明了,kset的引用计数其实就是其内嵌对象kobject对象的的引用计数,具有相同类型的kobject集合在一起组成了kset,许多kset集合在一起组成了子系统subsystem。
kset 在一个标准的内核链表中保存了它的子节点,在大部分情况下, 被包含的 kobjects 在它们的 parent 成员中保存指向 kset内嵌的 kobject的指针,关系如下:
(1)ksets 有类似于kobjects初始化和设置接口。
(2)ksets 还有一个指针指向kobj_type结构来描述它包含的kobject,这个类型优先于kobject自身中的ktype。因此在典型的应用中, 在 struct kobject中的ktype成员被设为 NULL, 而kset中的ktype是实际被使用的。
(3)在新的内核里, kset不再包含一个子系统指针struct subsystem * subsys, 而且subsystem已经被kset取代。
(4)子系统是对整个内核中一些高级部分的表述。子系统通常出现在sysfs分层结构中的顶层,内核子系统包括 block_subsys(/sys/block 块设备)、 devices_subsys(/sys/devices 核心设备层)以及内核已知的用于各种总线的特定子系统。对于新的内核已经不再有subsystem数据结构了,用kset代替了。每个 kset 必须属于一个子系统,子系统成员帮助内核在分层结构中定位kset。
3.Kset操作:
struct kset *kset_create_and_add(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
int kset_register(struct kset*kset)//注册kset
void kset_unregister(struct kset*kset)//注销kset
4.实例
#include
#include
#include
#include
#include
#include
#include
#include
struct kset kset_p;
struct kset kset_c;
int kset_filter(struct kset *kset, struct kobject *kobj)
{
printk("Filter: kobj %s.\n",kobj->name);
return 1;
}
const char *kset_name(struct kset *kset, struct kobject *kobj)
{
static char buf[20];
printk("Name: kobj %s.\n",kobj->name);
sprintf(buf,"%s","kset_name");
return buf;
}
int kset_uevent(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env)
{
int i = 0;
printk("uevent: kobj %s.\n",kobj->name);
while( i < env->envp_idx){
printk("%s.\n",env->envp[i]);
i++;
}
return 0;
}
struct kset_uevent_ops uevent_ops =
{
.filter = kset_filter,
.name = kset_name,
.uevent = kset_uevent,
};
int kset_test_init()
{
printk("kset test init.\n");
kobject_set_name(&kset_p.kobj,"kset_p");
kset_p.uevent_ops = &uevent_ops;
kset_register(&kset_p);
kobject_set_name(&kset_c.kobj,"kset_c");
kset_c.kobj.kset = &kset_p;
kset_register(&kset_c);
return 0;
}
int kset_test_exit()
{
printk("kset test exit.\n");
kset_unregister(&kset_p);
kset_unregister(&kset_c);
return 0;
}
module_init(kset_test_init);
module_exit(kset_test_exit);
可以看出当kset加载时,在/sys下创建了一个kset_p目录,在kset_p下面创建了kset_c目录,当kset模块被加载和卸载时都产生了热插拔事件。
三、代码分析
/*****************************************************************************************************/
1.kobject对象的初始化以及添加到sysfs文件系统中
/*****************************************************************************************************/
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,struct kobject *parent, const char *fmt, ...)
{
va_list args;
int retval;
//初始化kobject
kobject_init(kobj, ktype);
va_start(args, fmt);
//为kobjcet设置名称,在sysfs中建立相关信息
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
//上面的流程主要分为两部份。一部份是kobject的初始化。在这一部份,它将kobject与给定的ktype关联起来。初始化kobject中的各项结构。
//另一部份是kobject的名称设置。空间层次关系的设置,具体表现在sysfs文件系统中.
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) {//指针不能为空
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {//kobj_type指针也不能为空
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) {//标志为1表示kobject已经初始化过
printk(KERN_ERR "kobject (%p): tried to init an initialized ""object, something is seriously wrong.\n", kobj);
dump_stack();
}
kobject_init_internal(kobj);//初始化
kobj->ktype = ktype;//将kobject与给定的ktype关联起来
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);//初始化kobject的计数器,计数器设为1
INIT_LIST_HEAD(&kobj->entry);//初始化链表
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;//表示kobject已经初始化过
}
static inline void kref_init(struct kref *kref)
{
atomic_set(&kref->refcount, 1);
}
//另一部份是kobject的名称设置。空间层次关系的设置,具体表现在sysfs文件系统中.
static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,const char *fmt, va_list vargs)
{
va_list aq;
int retval;
va_copy(aq, vargs);
//设置kobject的名字。即设置kobject的name成员
retval = kobject_set_name_vargs(kobj, fmt, aq);
va_end(aq);
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
//设置kobject的parent。
kobj->parent = parent;
//在sysfs中添加kobjcet信息
return kobject_add_internal(kobj);
}
//设置好kobject->name后,转入kobject_add_internal()。在sysfs中创建空间结构.
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
//如果kobject的名字为空.退出
if (!kobj->name || !kobj->name[0]) {
pr_debug("kobject: (%p): attempted to be registered with empty ""name!\n", kobj);
WARN_ON(1);
return -EINVAL;
}
//取kobject的父结点,并递增父kobject的计数
parent = kobject_get(kobj->parent);
//如果kobject的父结点没有指定,而且kobj->kset已设定,就将kobj->kset->kobject做为它的父结点
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);//递增计数,返回kset->kobject
kobj_kset_join(kobj);//递增kobj对象中的kset计数,并且将kobj->kset连接到kobj->entry队列中
kobj->parent = parent;//设定父kobject
}
//在sysfs中创建kobject的相关信息
error = create_dir(kobj);
if (error) {
//v如果创建失败。减少相关的引用计数
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
printk(KERN_ERR "%s failed for %s with ""-EEXIST, don't try to register things with ""the same name in the same directory.\n",__FUNCTION__, kobject_name(kobj));
else
printk(KERN_ERR "%s failed for %s (%d)\n",__FUNCTION__, kobject_name(kobj), error);
dump_stack();
} else
//如果创建成功。将state_in_sysfs建为1。表示该object已经在sysfs中了
kobj->state_in_sysfs = 1;
return error;
}
static int create_dir(struct kobject *kobj)
{
int error = 0;
if (kobject_name(kobj)) {//kobject名称不为空
error = sysfs_create_dir(kobj);//为kobject创建目录
if (!error) {
error = populate_dir(kobj);//创建目录成功,为kobject->ktype中的属性创建属性文件
if (error)
sysfs_remove_dir(kobj);
}
}
return error;
}
//kobject所表示的目录创建过程,这是在sysfs_create_dir()中完成的。
int sysfs_create_dir(struct kobject * kobj)
{
struct sysfs_dirent *parent_sd, *sd;
int error = 0;
BUG_ON(!kobj);
/*如果kobject的parnet存在。就在目录点的目录下创建这个目录。如果没有父结点不存在,就在/sys下面创建结点。在上面的流程中,我们可能并没有为其指定父结点,也没有为其指定kset。*/
if (kobj->parent)
parent_sd = kobj->parent->sd;
else
parent_sd = &sysfs_root;
//在sysfs中创建目录,参见sysfs文件系统解析
error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);
if (!error)
kobj->sd = sd;
return error;
}
//为kobject->ktype中的属性创建文件,在populate_dir()中完成的。
static int populate_dir(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj);//得到kobject的kobj_type结构
struct attribute *attr;
int error = 0;
int i;
if (t && t->default_attrs) {
for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {//遍历ktype中的属性
error = sysfs_create_file(kobj, attr);//在sysfs文件系统kobject目录下创建属性文件,参见sysfs文件系统解析
if (error)
break;
}
}
return error;
}
/****************************************************************************************************/
2.kset对象的初始化以及添加到sysfs文件系统中
/****************************************************************************************************/
struct kset *kset_create_and_add(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
{
struct kset *kset;
int error;
//创建一个kset容器
kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
//注册创建的kset容器
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
static struct kset *kset_create(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
{
struct kset *kset;
int retval;
kset = kzalloc(sizeof(*kset), GFP_KERNEL);//为kset分配内存空间
if (!kset)
return NULL;
//设置kset中kobject的名字
retval = kobject_set_name(&kset->kobj, name);
if (retval) {
kfree(kset);
return NULL;
}
kset->uevent_ops = uevent_ops;//设置uevent操作集
kset->kobj.parent = parent_kobj;//设置父对象
kset->kobj.ktype = &kset_ktype;//设置容器操作集
kset->kobj.kset = NULL;//设置父容器为空
return kset;
}
//创建好了kset之后,会调用kset_register().这个函数就是kset操作的核心代码了.
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
//继续初始化
kset_init(k);
//向sysfs文件系统添加该容器,即为k内嵌的kobject结构建立空间层次结构,代码见上
err = kobject_add_internal(&k->kobj);
if (err)
return err;
//因为添加了kset,会产生一个事件,这个事件是通过用户空间的hotplug程序处理的,这就是kset明显不同于kobject的地方.
kobject_uevent(&k->kobj, KOBJ_ADD);//参见udev原理
return 0;
}
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj);//初始化kset中的kobject结构
INIT_LIST_HEAD(&k->list);//初始化链表
spin_lock_init(&k->list_lock);
}