Chinaunix首页 | 论坛 | 博客
  • 博客访问: 420391
  • 博文数量: 247
  • 博客积分: 185
  • 博客等级: 入伍新兵
  • 技术积分: 1005
  • 用 户 组: 普通用户
  • 注册时间: 2012-09-10 10:39
文章分类

全部博文(247)

文章存档

2015年(3)

2014年(21)

2013年(53)

2012年(170)

分类: LINUX

2013-03-04 09:49:24

原文地址:Linux设备模型(一) 作者:星闪夜空

        这几天一直在学习Linux设备模型的知识,以《Linux设备驱动程序》第三版为学习的教材,并且参考了一些网络上的文章,结合自己的理解整理出了如下的文章。希望能够帮助正在学习这方面的人,如果有什么问题,还请大家多加指教。

 

Linux设备模型()

一、kobject基础知识

kobject组成设备模型的基本结构,最初它只是被理解为一个简单的引用计数,但是随着时间的推移,它的任务越来越多。它存在的意义在于把高级对象连接到设备模型上。

struct kobject {

   //指向设备名称的指针

const char * k_name; 

   //设备名称           

char name[KOBJ_NAME_LEN]; 

   //kobject的引用计数

struct kref kref;

   //kobject之间的双向链表,与所属的kset形成环形链表                

struct list_head entry;

   //sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj

   struct kobject * parent; 

   //指向所属的kset

struct kset * kset; 

   //保存kobject的属性    

struct kobj_type * ktype;

   //sysfs文件系统中与该对象对应的文件节点路径指针    

struct dentry * dentry;  

   //等待队列头 

wait_queue_head_t poll;    

};

  1、嵌入的kobject

由于内核代码很少去创建一个单独的kobject对象,而是用于控制对相关对象的访问,所以kobject对象常常被嵌入到其他结构中。如cdev结构:

struct cdev {

struct kobject kobj;

struct module *owner;

const struct file_operations *ops;

struct list_head list;

dev_t dev;

unsigned int count;

};

如果要使用cdev结构,只需要访问kobject成员。如对包含在cdev结构中的、名为kpkobject结构指针进行转换的代码如下:

struct cdev *device=container_of(kp,struct cdev,kobj);

  2kobject的初始化

① 首先是将整个kobject设置为0,这通常使用memset函数。

② 之后调用kobject_init()函数,以便设置结构内部的一些成员。kobject_init()所做的一件事情是设置kobject的引用计数为1

③ kobject的使用者必须至少设置kobject的名字:

int kobject_set_name(struct kobject *kobj,const char *format,...);

该函数使用了类似printk的变量参数列表,它可能会导致该操作的失败(因为要分配内存),因此,严格的代码应该检查返回值。

  3、对引用计数的操作

struct kobject * kobject_get(struct kobject * kobj);

kobject_get的成功调用将增加kobject的引用计数,并返回指向kobject的指针。如果kobject已经处于被销毁的过程中,则该调用失败,kobject返回NULL。必须检查返回值,否则可能会产生竞态。

void kobject_put(struct kobject * kobj);

当引用被释放时,调用kobject_put减少引用计数,并在可能的情况下释放该对象。

注意:kobject_init()设置引用计数为1,所以当创建kobject时,如果不再需要初始的引用,就要调用相应的kobject_put函数。

  4release函数和kobject类型

一个被kobject所保护的结构,不能在驱动程序生命周期的任何可预知的、单独的时间点上被释放掉,因此当kobject的最后一个引用计数不再存在时,必须异步地通知。

通知是使用kobject中的kobj_type结构体中的release方法实现的,该方法通常的原型如下:

void my_object_release(struct kobject *kobj)

{

   struct my_object *mine=container_of(kobj,struct my_object,kobj);

   /*对该对象执行其他的清除工作,然后...*/

   kfree(mine);

}

注意:每个kobject都必须有一个release方法,在最后一个引用返回后释放对象。

每个kobject都需要有一个相应的kobj_type结构。可以在两个不同的地方找到这个结构的指针:

kobject结构中包含的一个成员(称之为ktype)保存了该指针。

如果kobjectkset的一个成员的话,kset会提供kobj_type指针。

二、kobject层次结构、kset和子系统

通常,内核用kobject结构将各个对象连接起来组成一个分层的结构体系,从而与模型化的子系统相匹配,有两种独立的机制用于连接:parent指针和kset

kobject结构的parent成员中,保存了另外一个kobject结构的指针,这个结构表示了分层结构中上一层的节点。比如一个kobject结构表示了一个USB设备,它的parent指针可能指向了表示USB集线器的对象,而USB设备是插在USB集线器上的。

  1kset

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像是kobj_type结构的扩充,一个kset是嵌入相同类型结构的kobject集合。但是kobj_type结构关心的是对象的类型,而kset结构关心的是对象的聚集与集合。

ksetkobject顶层容器类,在每个kset内部,包含了自己的kobject

kset一直在sysfs中出现,一旦一个kset已被建立并且加入到系统,会有一个sysfs目录给它。kobject没有必要在sysfs中出现,但是每个是kset成员的kobject都出现在那里。

通俗的将,kobject建立一级的子目录,里面只能包含文件;kset可以为kobject建立多级的层次性的父目录。

下图为一个简单的kset分层结构:

         

创建一个对象时,通常要把一个kobject添加到kset中去。这个过程有两个步骤:

1) 先把kobjectkset成员指向目的的kset

2) 然后将kobject传递给下面的函数

int kobject_add(struct kobject * kobj);

当把一个kobject传递给kobject_add时,将会增加它的引用计数。

内核提供了一个方便使用的函数:

extern int kobject_register(struct kobject * kobj);

该函数只是kobject_initkobject_add的简单组合。

在某些时候,可能不得不把kobjectkset中删除,以清除引用,使用下面的函数达到这个目的:

void kobject_del(struct kobject * kobj);

还有一个kobject_unregister函数,它是kobject_delkobject_put的组合。

  2kset上的操作

    void kset_init(struct kset *kset);  

    int kset_add(struct kset *kset);  

    int kset_register(struct kset *kset);  

void kset_unregister(struct kset *kset); 

在大多数情况下,这些函数只是对kset中的kobject结构调用类似前面的kobject_的函数。

为了管理kset的引用计数,其情况也是一样的:

struct kset *kset_get(struct kset *kset);

void kset_put(struct kset *kset);

一个也kset也拥有名字,它保存在内嵌的kobject中,因此,如果我们有一个名为my_setkset,可使用下面的函数设置它的名字:

kobject_set_name(&my_set-> kobj, "The name");

kset中也有一个指针(ktype成员中)指向kobj_type结构,用来描述它所包含的kobject。该类型的使用优先于kobjectktype。因此在典型应用中,kobjectktype成员被设置为NULL,因为ksetktype成员是实际上被使用的成员。

三、低层sysfs操作

kobjectsysfs中的入口始终是一个目录,因此,对kobject_add的调用将在sysfs中创建一个目录。

分配给kobject(使用kobject_set_name函数)的名字是sysfs中的目录名。这样,处于sysfs分层结构相同部分中的kobject必须有唯一的名字。

sysfs入口在目录中的位置对应于kobjectparent指针。如果调用kobject_add的时候,parentNULL,它将被设置为嵌入到新kobjectkset中的kobject,这样,sysfs分层结构通常与kset创建的内部结构相匹配。如果parentkset都是NULL,则会在最高层创建sysfs目录,而这通常不是我们所期望的。

  1、默认属性    

当创建kobject的时候,都会给每个kobject一系列默认属性,这些属性保存在kobj_type结构中,下面是该结构的成员:

struct kobj_type {

void (*release)(struct kobject *);

struct sysfs_ops * sysfs_ops;

struct attribute ** default_attrs;

};

default_attrs成员保存了属性列表,用于创建该类型的每一个kobject

sysfs_ops提供实现这些属性的方法。

先看default_attrs,它指向了一个包含attribute结构数组的指针:

struct attribute {

const char * name;       //属性的名字

struct module  * owner;   //指向模块的指针

mode_t mode;         //应用于属性的保护位

};

default_attrs数组说明了都有些什么属性,但是没有告诉sysfs如何真正实现这些属性,这个任务交给了kobj_type->sysfs_ops成员,它所指向的结构定义如下:

struct sysfs_ops {

ssize_t (*show)(struct kobject *, struct attribute *,char *);

ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);

};

show:当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态,show相当于read

store:当用户写属性文件时,该函数被调用,用于存储用户存入的属性值。store相当于write

  2、非默认属性

如果希望在kobjectsysfs目录中添加新的属性,即非默认属性,只需要填写一个attribute结构,并把它传递给下面的函数:

int sysfs_create_file(struct kobject * kobj, const struct attribute * attr);

调用下面的函数删除属性:

int sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);

四、热插拔事件的产生

一个热插拔时间是从内核空间发送到用户空间的通知,它表明系统配置出现了变化,无论kobject被创建还是被删除,都会产生这种事件(当把kobject传递给kobject_add或者kobject_del时,才会真正产生这些事件)

对热插拔事件的实际控制是由struct kset_uevent_ops结构中的函数完成。

struct kset_uevent_ops {

int (*filter)(struct kset *kset, struct kobject *kobj); 

const char *(*name)(struct kset *kset, struct kobject *kobj);

int (*uevent)(struct kset *kset, struct kobject *kobj, char **envp,

int num_envp, char *buffer, int buffer_size);

};

filter:决定是否产生事件,如果返回0,将不产生事件。

name:向用户空间传递一个合适的字符串。

uevent:通过环境变量传递任何热插拔脚本需要的信息,他会在(udevmdev)调用之前,提供添加环境变量的机会。

阅读(477) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~