linux设备驱动(十八)–设备驱动模型(一)kobject和kset

(1)《 Linux 那些事儿 之 我是Sysfs 》来源于复旦和交大三个牛人的Linux技术博客:http://blog.csdn.net/fudan_abc (复旦_abc)他们还分析了很多Linux的驱动,值得珍藏!

(2)《linux设备模型详解》也是一个牛人的博客文章,博客网址:http://hi.baidu.com/csdeny/blog

(3)《s3c2410设备的注册》是一篇关于2410中linux内核实现设备模型的不可多得的好资料。网址:http://blog.chinaunix.net/u1/41638/showart_438078.html

(4)luofuchong的博客 ,此人分析了一些2410中的Linux子系统(如SPI,input等),实力不凡,值得关注。网址:http://www.cnitblog.com/luofuchong/

在这部分的学习中,将会先研究linux设备模型的每个元素,最后将其一步一步整合,至底向上地分析。一开始会比较摸不着头脑,到了整合阶段就柳暗花明了。我之所以没有先介绍整体,再分析每个部分是因为如果不对每个元素做认真分析,看了整体也会云里雾里(我试过了,恕小生愚钝)。所以一开始要耐着性子看,到整合阶段就会豁然开朗。

上边这部分转载下。

linux设备模型的目的是:为内核建立统一的模型,从而对系统有一般性的描述。

现在linux设备模型支持多种任务:

1。电源管理和系统关机:完成这些任务需要对系统结构有一定的了解。设备模型是的操作系统能够以正确的顺序便利系统硬件。

2。与用户空间进行通讯:sysfs虚拟文件系统的实现与设备模型密切相关,并且向外界展示了他所表述的结构,向用户空间提供系统所发出的信息(热插拔事件),而这种机制也是通过设备模型来管理的。

3。设备模型:系统中的许多部分对设备如何连接的信息并不感兴趣,但是它们需要知道哪种类型的设备是可用的。设备模型包括了将设备分类的机制,它会在更高的功能层上来描述这些设备,并使得这些设备对用户层可见。

4。对象生命周期:上述许多功能,包括热插拔和sysfs,使得内核中创建和管理对象的工作更为复杂。设备模型的实现需要创建一系列机制以处理对象的生命周期、对象之间的关系以及这些对象在用户空间的表示。

Linux 设备模型是一个复杂的数据结构。但对模型的大部分来说, Linux 设备模型代码会处理好这些关系, 而不是把他们强加于驱动作者。模型隐藏于交互的背后,与设备模型的直接交互通常由总线级的逻辑和其他的内核子系统处理。所以许多驱动作者可完全忽略设备模型, 并相信设备模型能处理好他所负责的事。

设备模型十分重要,设备模型有时会从其它层的背后暴露出来,比如,一般性的DMA代码使用了device结构。用户可能要使用设备模型提供的引用计数及其相关功能。通过sysfs来与用户空间进行通信等等。

Kobject,kset和子系统

Kobject是组成设备模型的基本结构,它实际上是一个基类,其他的设备模型包含这个基类 (嵌入式kobject)具有该结构成员的一切特性。现在该结构所能处理的任务包括:

对象的引用计数:一个内核对象被创建时,不可能知道该对象的存活时间。跟踪此对象生命周期的一个方法是使用引用计数。当内核中没有代码持有该对象的引用时,该对象将结束自己的有效生命周期,并且可以被删除。

sys系统表述:在sysfs中显示一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。sysfs是一个特殊文件系统,并没有一个实际存放文件的介质。断电后就玩完了。简而言之,sysfs的信息来源是kobject层次结构,读一个sysfs文件,就是动态的从kobject结构提取信息,生成文件。

数据结构关联:从整体上看,设备模型是一个友好而复杂的数据结构,通过其间大量的链接而构成一个多层次的体系结构、

设备热插拔

kobject基础

在<linux/kobject.h>中,包括了与kobject相关结构的声明,以及用于操作该结构的函数清单。

kobject对自身并不感兴趣,它的存在在于把高级对象链接到设备模型上。

struct kobject {const char* k_name;/*kobject 的名字数组(sysfs 入口使用的名字)指针;如果名字数组大小小于KOBJ_NAME_LEN,它指向本数组的name,否则指向另外分配的一个名字数组空间 */charname[KOBJ_NAME_LEN];/*kobject 的名字数组,若名字数组大小不小于KOBJ_NAME_LEN,只储存前KOBJ_NAME_LEN个字符*/struct krefkref;/*kobject 的引用计数*/struct list_headentry;/*kobject 之间的双向链表,与所属的kset形成环形链表*/struct kobject* parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobjectkobj*/struct kset* kset;/*指向所属的kset*/struct kobj_type* ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/struct dentry* dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/wait_queue_head_tpoll;/*等待队列头*/};

kobject 初始化

kobject的初始化较为复杂,但是必须的步骤如下:

(1)将整个kobject清零,通常使用memset函数。

(2)调用kobject_init()函数,设置结构内部一些成员。所做的一件事情是设置kobject的引用计数为1。具体的源码如下:

void kobject_init(struct kobject * kobj)/*in kobject.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(structcdev *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 的返回值,如果调用失败,则释放它的对模块的引用计数。

release 函数和 kobject 类型

引用计数不由创建 kobject 的代码直接控制,当 kobject 的最后引用计数消失时,必须异步通知,而后kobject中ktype所指向的kobj_type结构体包含的release函数会被调用。通常原型如下:

void my_object_release(struct kobject *kobj){struct my_object *mine = container_of(kobj, struct my_object, kobj); /* Perform any additional cleanup on this object, then... */kfree(mine);}

每个 kobject 必须有一个release函数, 并且这个 kobject 必须在release函数被调用前保持不变( 稳定状态 ) 。这样,每一个 kobject 需要有一个关联的 kobj_type 结构,指向这个结构的指针能在 2 个不同的地方找到:

(1)kobject 结构自身包含一个成员(ktype)指向kobj_type ;

(2)如果这个 kobject 是一个 kset 的成员, kset 会提供kobj_type 指针。

struct kset {struct kobj_type* ktype; /*指向该kset对象类型的指针*/struct list_headlist;/*用于连接该kset中所有kobject以形成环形链表的链表头*/spinlock_tlist_lock;/*用于避免竞态的自旋锁*/struct kobjectkobj; /*嵌入的kobject*/struct kset_uevent_ops* uevent_ops;

/*原有的struct kset_hotplug_ops * hotplug_ops;已经不存在,被kset_uevent_ops结构体替换,在热插拔操作中会介绍*/};

以下宏用以查找指定kobject的kobj_type 指针:

struct kobj_type *get_ktype(struct kobject *kobj);

这个函数其实就是从以上提到的这两个地方返回kobj_type指针,源码如下:

static inline struct kobj_type * get_ktype(struct kobject * k){if (k->kset && k->kset->ktype)return k->kset->ktype;else return k->ktype;}

关于新版本内核的kobj_type:在新版本的内核中已经在structkset中去除了structkobj_type*ktype;

也就是说只有structkobject中存在kobj_type ,所以get_ktype也相应变为了:

staticinlinestructkobj_type*get_ktype(structkobject*kobj)

{ returnkobj->ktype;

}


kobject 层次结构、kset 和子系统

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

parent 是指向另外一个kobject 结构(分层结构中上一层的节点)的指针,主要用途是在 sysfs 层次中定位对象.

kset

kset 象 kobj_type 结构的扩展; 一个 kset 是嵌入相同类型结构的 kobject 的集合。但 struct kobj_type 关注的是对象的类型,而struct kset 关心的是对象的聚合和集合,其主要功能是包容,可认为是kobjects 的顶层容器类。每个 kset 在内部包含自己的 kobject, 并可以用多种处理kobject 的方法处理kset。 ksets 总是在 sysfs 中出现; 一旦设置了 kset 并把它添加到系统中, 将在 sysfs 中创建一个目录;kobjects 不必在 sysfs 中表示, 但kset中的每一个 kobject 成员都要在sysfs中表述。

增加 kobject 到 kset 中去,通常是在kobject 创建时完成,其过程分为2步:

(1)完成kobject的初始化,特别注意mane和parent和初始化。

(2)把kobject 的 kset 成员指向目标kset。

(3)将kobject 传递给下面的函数:

int kobject_add(struct kobject *kobj); /*函数可能失败(返回一个负错误码),程序应作出相应地反应*/

内核提供了一个组合函数:

extern int kobject_register(struct kobject *kobj); /*仅仅是一个 kobject_init 和 kobject_add 的结合,其他成员的初始化必须在之前手动完成*/

当把一个kobject传递给kobjetc_add时,将会增加它的引用计数。在kset中包含的最重要的内容是对象的引用。在某些时候,可能不得不把kobject从kset中删除,以清除引用:

void kobject_del(struct kobject *kobj); 用来接触对kobject的引用

还有一个kobject_unregister函数,它是kobject_del和kobject_put的组合。

网页上报错,前边自己写的都没了 郁闷。转了下前辈的呵呵

kset在一个标准的内核链表中保存了它的子节点。在大多数情况下,所包含的kobject会在它们的parent成员中保存kset(严格的说是内嵌的kobject)的指针。因此典型的关系图 如下:

1.在图中所有被包含的kobject,实际上是被嵌入到其他类型中,甚至可能是其他的kset

2.一个kobject的父节点不一定是包含它的kset(这种结构比较少见)。

kset上的操作

kset拥有与kobject相似的初始化和设置接口:

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_set的kset,可使用下面的函数设置它的名字。

kobject_set_name(&my_set->kobj,”the name”);

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

sysfs系统

子系统是对整个内核中一些高级部分的表述。子系统通常(但不一定)显示在sys分层结构中的顶层。内核中的子系统包括block_subsys(对块设备来说是sys/block)、 devices_subsys(/sys/devices,设备分层结构的核心)以及内核所知晓的用于各种总线的子系统。驱动程序作者最终要做的是添加一个新类。

下面的结构表示一个子系统:

struct subsystem {

struct kset kset;

struct rw_semphore rwsem;

};

一个子系统就是对kset和一个信号量的封装。

每个kset都必须属于一个子系统。子系统的成员将帮助内核在分层结构中定位kset,但更重要的是,子系统的rwsem信号量被用于串行访问kset内部的链表。在kset结构中,这种成员关系被表示为subsys指针,因此通过kset结构,可以找到包含kset的每一个子系统,但是我们无法直接从subsystem结构中找到子系统所包含的多个kset。

使用下面的宏来声明subsystem

decl_subsys(name,struct kobj_type *type,struct kset_hotplug *hotplug_ops);

这里注意/*原有的struct kset_hotplug_ops * hotplug_ops;已经不存在,被kset_uevent_ops结构体替换,在热插拔操作中会介绍*/

struct kset_uevent_ops* uevent_ops;

子系统操作函数:

void subsystem_init(struct subsystem *subsys);

int subsystem_register(struct subsystem *subsys);

void subsystem_unregister(struct subsystem *subsys);

struct subsytem *subsys_get(struct subsystem *subsys);

void subsys_put(struct subsystem *subsys);

这些函数中的大多数都用于对子系统中的kset进行操作。

低层sysfs操作

kobject是隐藏在sysfs虚拟文件系统后的机制,对于sysfs中的每个目录,内核都会存在一个对应的kobject。每一个kobject都输出一个或多个属性,他们在kobject的sysfs目录中表现为文件,其中的内容有内核生成。

只要调用kobject_add,就能在sysfs中显示kobject。在把kobject添加到kset的时候,已经讨论过这个函数了;在sysfs中创建入口项也是该函数的功能之一。需要了解许多知识,才能理解如何创建sysyfs入口的。

1.kobject在sysfs中的入口项始终是一个目录,因此,对kobject_add调用将在sysfs中创建一个目录。通常这个目录包含一个或多个属性。

2.分配给kobject(使用kobject_set_name函数)的名字是sysfs中的目录名。这样,处于sysfs分层结构相同部分中的kobject必须有唯一的名字。分配给kobject的名字必须是合法的文件名,不能有反斜杠和空格。

3. sysfs入口在目录中的位置对应于kobject的parent指针。如果调用kobject_add的时候,parent为NULL,它将被设置为嵌入到新kobject的kset中的kobject,这样sysfs分层结构通常与kset创建的内部结构相匹配。如果parent和kset都是NULL,则会在最高层创建sysfs目录。

默认属性

当创建kobject的时候,都会给每个kobject一系列默认属性。这些属性保存在kobj_type结构中。

struct kobj_type{

void (*release)(struct kobject *);

struct sysfs_ops *sysfs_ops;

struct attribute **default_attys;

};

default_attrs成员保存了属性列表,用于创建该类型的每一个kobject,sysfs_ops提供了实现这些属性的方法。

default_attrs指向一个包含attributs结构数组的指针:

struct attribute {

char *name; 为属性的名字,在kobject的sysfs目录中显示

struct module *owner; 模块指针,该模块负责实现这些属性

mode_t mode; 属性保护位

};

default_attrs链表中的最后一个元素必须用零填充。

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

struct sysfs_ops {

ssize_t (*show)(struct kobject *kobj,struct attribute **attr,char *buffer);

sszie_t (*store)(struct kobject *kobj,struct attribute **attr,const char *buffer,ssize_t size);

};

当用户空间读取一个属性时,内核会使用指向 kobject 的指针(kobj)和正确的属性结构(*attr)来调用show 方法,该方法将给定属性值编码进缓冲(buffer)(注意不要越界( PAGE_SIZE 字节)), 并返回实际数据长度。sysfs 的约定要求每个属性应当包含一个单个人眼可读值; 若返回大量信息,需将它分为多个属性.

也可对所有 kobject 关联的属性使用同一个 show 方法,用传递到函数的 attr 指针来判断所请求的属性。有的 show 方法包含对属性名字的检查。有的show 方法会将属性结构嵌入另一个结构, 这个结构包含需要返回属性值的信息,这时可用container_of 获得上层结构的指针以返回属性值的信息。

store 方法将存在缓冲(buffer)的数据( size 为数据的长度,不能超过 PAGE_SIZE )解码并保存新值到属性(*attr), 返回实际解码的字节数。store 方法只在拥有属性的写权限时才能被调用。此时注意:接收来自用户空间的数据一定要验证其合法性。如果到数据不匹配, 返回一个负的错误值。

非默认属性

虽然 kobject 类型的 default_attrs 成员描述了所有的 kobject 会拥有的属性,倘若想添加新属性到 kobject 的 sysfs 目录属性只需简单地填充一个attribute结构并传递到以下函数:

int sysfs_create_file(struct kobject *kobj, struct attribute *attr);/*若成功,文件以attribute结构中的名字创建并返回 0; 否则, 返回负错误码*//*注意:内核会调用相同的 show() 和 store() 函数来实现对新属性的操作,所以在添加一个新非默认属性前,应采取必要的步骤确保这些函数知道如何实现这个属性*/

若要删除属性,调用:

int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);/*调用后, 这个属性不再出现在 kobject 的 sysfs 入口。若一个用户空间进程可能有一个打开的那个属性的文件描述符,在这个属性已经被删除后,show 和 store 仍然可能被调用*/

二进制属性

sysfs 通常要求所有属性都只包含一个可读文本格式的值,很少需要创建能够处理大量二进制数据的属性。但当在用户空间和设备间传递不可改变的数据时(如设备上上载固件,如果我们在系统中遇到这样的设备,就可以运行用户空间程序(通过热插拔机制),这些程序使用二进制的sysfs属性将固件代码传递给内核)就需要这个特性。二进制属性使用一个 bin_attribute 结构来描述:

struct bin_attribute {struct attributeattr;/*属性结构体*/size_tsize;/*这个二进制属性的最大大小(若无最大值则为0)*/void*private;ssize_t (*read)(struct kobject *, char *, loff_t, size_t);ssize_t (*write)(struct kobject *, char *, loff_t, size_t);/*read 和 write 方法类似字符驱动的读写方法;,在一次加载中可被多次调用,每次调用最大操作一页数据,且必须能以其他方式判断操作数据的末尾*/int (*mmap)(struct kobject *, struct bin_attribute *attr, struct vm_area_struct *vma);};/*二进制属性必须显式创建,不能以默认属性被创建,创建一个二进制属性调用:*/int sysfs_create_bin_file(struct kobject *kobj, struct bin_attribute *attr);/*删除二进制属性调用:*/int sysfs_remove_bin_file(struct kobject *kobj, struct bin_attribute *attr);

符号链接

sysfs文件系统具有常用的树形结构,以反映kobject之间的组织层次关系。通常内核中各对象之间的关系远比这复杂。比如一个sysfs的子树(sys/devices)表示所有系统知晓的设备,而其他子树(sys/bus) 下表示了设备的驱动程序。但是这些树并不能表示驱动程序及其所管理的设备之间的关系。为了表示这种关系好需要其他的指针,在 sysfs中,通过符号链接实现这个目的:

int sysfs_create_link(struct kobject *kobj,struct kobject *target,char *name);

该函数创建了一个链接(称为name)指向target的sys入口,并作为kobj的一个属性。这是一个相对链接,因此与sysfs挂载系统中的特定位置无关。

/*删除符号连接调用:*/void sysfs_remove_link(struct kobject *kobj, char *name);

热插拔事件的产生

一个热插拔事件是从内核空间发送到用户空间的通知,它表明系统配置出现了变化。无论kobject被创建还是被删除,都会产生这种事件。热插拔时间会导/sbin/hotplug程序的调用,该程序通过加载驱动程序,创建设备节点,挂载分区,或者其他正确的动作来响应事件。

当我们把kobject传递给kobject_add或则kobject_del时,才会产生这些事件。在事件被传递到用户空间之前,处理kobject(kobject所属的kset)的代码能够为用户空间添加信息,或者完全禁止这类事件的产生。

热插拔操作

对热插拔事件的实际控制,是由保存在kset_uevent_ops结构中的函数完成的:

struct kset_uevent_ops {

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

char *(*name)(struct kset *kset,struct kobject *kobj); 这里函数指针返回一个指针,这个指针指向一个字符串(name)。

int (*uevent)(stuct kset *kset,struct kobject *kobj,char **envp,int num_envp,char *buffer,int buffer_size);

};

我们可以在kset结构的uevent_ops成员中发现指向这个结构的指针。如果在kset中不包含一个指定的kobject,内核将在分层结构中进行收索(通过parent指针),直到找到一个包含kobject的kset,然后使用这个kset进行热插拔操作。

无论什么时候,当内核要为指定的kobject产生事件时,都要调用filter函数,该函数返回零将不产生事件。因此该函数给kset一个机会,用于决定是否向用户空间传递特定事件。

使用该函数的一个例子是块设备驱动。在该子系统中,至少使用了三种类型的kobject,他们是磁盘、分区和请求队列。用户空间将会对磁盘或者分区的添加产生相应,但通常不会响应请求队列的变化。因此,filter函数只允许为kobject产生磁盘和分区事件产生响应。

statci int block_uevent_filter(struct kset *kset,struct *kobject *kobj)

{

struct kobj_type *ktype = get_ktype(kobj);

return ((ktype == &ktype_block)||(ktype == &ktype_part));

}

这里对kobject的快速类型检查足以判断是否产生事件。

在调用用户空间的热插拔程序时,相关系统的名字将作为唯一的参数传递给它。这个功能由kset_uevent_ops结构中的*(*name)函数来完成。

任何热插拔脚本所需要的信息将通过环境变量传递。在kset_uevent_ops中的最后一个函数会在调用脚本前,提供添加环境变量的机会。该方法的原形如下:

int (*uevent)(struct kset *kset,struct kobject *kobj, 描述了产生事件的目的对象

char **envp, 是一个保存其他环境变量定义的数组(NAME = value)

int num_envp, 说明目前有多少个变量入口,变量应当在编码后放入长度为buffer_size的缓冲区中。如果要在envp中添加任何变量,请确保在最后一个新变量后加入NULL入口,这样内核就知道在哪里结束了。

char *buffer,

int buffer_size)

该方法通常返回值是0,返回任何非0值将终止终止热插拔事件的产生。

热插拔事件的创建(如同设备模型中的许多工作一样)通常被总线驱动程序级别上的逻辑所控制。

答:他是憋死的,因为沙漠里没有电线杆撒尿。问:

linux设备驱动(十八)–设备驱动模型(一)kobject和kset

相关文章:

你感兴趣的文章:

标签云: