Linux设备模型笔记

先了解一下:

在内核2.5开发周期中需要我完成这样一个目标:为内核建立起一个统一的设备模型。在以前的内核中没有独立的数据结构让内核来获得系统整体配合的信息,但是在许多时候仍然能够正常工作。但随着拓补结构越来越复杂,以及要支持诸如电源管理等新要求,对新版本的内核提出了这样的要求:需要有一个对系统结构抽象性描述。在2.6版的设备模型中提供了这样的抽象。

言归正传:

kobject、kset和子系统

kobject是组成设备模型的基本结构。kobject结构能处理的任务以及它所支持的代码包括:

对象的引用计数:
通常,一个内核对象被创建时,不知道其存活的时间,跟踪此对象生命周期的一个方法是使用引用计数。当内核没有代码持有该对象的引用时,它将结束自己的有效生命周期,且可以被删除(引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。 使用引用计数技术可以实现自动资源管理的目的。 同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法。
sysfs表述:
在sysfs中显示的每一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。
数据结构关联:
从整体上看,设备模型是一个友好而复杂的数据结构,通过在其间的大量连接而构成的多层次的体系结构。kobject实现了该结构并把它们聚合在了一起。
热拔插事件处理:
当系统硬件热拔插时,在kobject子系统控制下,将产生事件以通知用户空间。

kobject基础知识

kobject是一种数据结构,定义在<linux/kobject.h>中,文件里包括了与kobject相关机构的声明,还有用于操作kobject对象的函数清单。一个kobject对自身并不感兴趣,它存在的意义在于把高级对象连接到设备模型上。如在cdev结构中嵌入了kobject结构。

struct cdev
{
struct kobject kobj;
struct module *owner;
struct file_operation *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};

如果使用该结构,只需访问kobject成员就能获得嵌入式的boject对象。但使用kobject的代码经常遇到相反的问题:对于给定的一个kobject指针,如何获得包含它的结构指针呢。此时要使用container_of宏

container_of(pointer, container_type, container_field);  

这个宏需要一个container_of字段的指针,该字段包含在container_type类型的结构中,然后返回该字段的结构指针。在scull_open中,这个宏用来找到适当的设备结构:

struct scull_dev *dev; /*device information*/
dev = container_of(inde->i_cdev, struct scull_dev, dev);
file->private_data = dev; /*for other methods*/

一旦代码找到scull_dev结构之后,scull将一个指针保存到了file结构的private_data字段中,这样可以方便今后对该指针的访问。

利用这个宏,对包含在cdev结构中的、名为kp的kobject结构指针进行转换的的代码如下:

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

为了能通过kobject指针找回(back-casting)包含它的类,程序员要经常定义简单的宏完成这件事。

kobject的初始化:

通常使用memset将整个kobject设置为0,之后调用kobject_init()函数,以便设置结构内部的一些成员:

void kobject_init(struct kobject *kobj); 

kobject_init将kobject的引用计数设为1。我们还需要设置kobject的名字,这是在sysfs入口中使用的名字。仔细分析内核我们也可以直接将字符串拷贝到kobject的name成员带码,但尽量别这么做,而应该用:

int kobject_set_name(struct kobject *kobj, const char *format, ...); //该函数使用了类似printk的变量参数。它可能会导致该操作的失败(因为要分配内存。因此要检查返回值,并做相应的处理
kobject的创建者需要直接或间接设置的成员有:ktype、kset和parent。

对引用计数的操作:

kobject的一个重要函数是为包含它的结构设置引用计数,只要对象的引用计数存在,对象(以及支持它的的代码)就必须存在。底层控制kobject引用计数的函数有:

struct kobject *kobject_get(struct kobject *kobj); //对kobject_get的成功调用将增加kobject的引用计数,并返回指向kobject的指针。如果kobject已经处于被销毁的过程,则调用失败。kobject_get返回NULL。必须检查返回值,否则可能会产生竞态。当引用被释放时,调用该函数减少引用计数,并在可能的情况下释放对象。记住kobject_init设置引用计数为1,所以当创建kobject时,如果不需要初始的引用,就要调用相应的kobject_put函数。
void kobject_put(struct kobject *kobj); //注意,在许多情况下,在kobject中的引用计数不足以防止竞态的产生。例如:kobject(以及包含它的结构)的存在需要创建它的的模块继续存在。当kobject继续被使用时,不能卸载该模块。这就是为什么cdev结构中包含了模块指针的原因。(当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。上例中add()方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。)

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的返回值。如果失败,则释放对模块的引用计数

release函数和kobject类型

当引用计数为0的时候,kobject将采取什么操作。但创建kobject的代码无法知道这种情况。在使用sysfs的时候,即使那些可预知的对象生命周期也会变得复杂,因为用户空间程序可能在任意的时间引用kobject对象(通过保持一个它关联的sysfs文件打开),导致一个被 kobject 保护的结构无法在任何一个单个的, 可预测的驱动生命周期中的点被释放, 但是可以在必须准备在 kobject 的引用计数到 0 的任何时刻运行的代码中. 引用计数不在创建 kobject 的代码的直接控制之下. 因此这个代码必须被异步通知, 无论何时对它的 kobject 的最后引用消失.这个通知由 kobject 的一个释放函数来完成. 通常, 这个方法有一个形式如下: 

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 必须有一个释放函数, 并且这个 kobject 必须持续( 以一致的状态 ) 直到这个方法被调用. 如果这些限制不满足, 代码就有缺陷. 当这个对象还在使用时被释放会有风险, 或者在最后引用被返回后无法释放对象

释放方法没有存储在 kobject 自身里面; 相反, 它被关联到包含 kobject 的结构类型中. 这个类型被跟踪, 用一个 struct kobj_type 结构类型, 常常简单地称为一个 "ktype". 这个结构如下:

struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
};

每一个 kobject 需要有一个关联的 kobj_type 结构. 但指向这个结构的指针能在 2 个不同的地方找到. kobject 结构自身包含一个成员(称为 ktype)包含这个指针.但是, 如果这个 kobject 是一个 kset 的成员, kobj_type 指针由 kset 提供. 这个宏定义为: 

struct kobj_type *get_ktype(struct kobject *kobj);
kobject 层次, kset, 和子系统

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

kset

kset像是kobj_type结构的扩充。它关心的是对象的聚集与集合;而kobj_type结构关心的是对象的类型。

kset的主要功能是包容;可以认为它是kobject的顶层容器类。在每个kset内部,包含了自己的kobject,并且可以用多种处理kobject的方法处理kset。kset总是在sysfs中出现,一旦一个kset 已被建立并且加入到系统, 将在sysfs中创建一个目录。kobject不必在sysfs中表示,但是每一个kobject成员都将在sysfs中得到表述。

创建一个对象时,通常要把一个kobject添加到kset中。分两步: 先把kobject的kset成员指向目的kset,然后将kobject传递给下面的函数:
int kobject_add(struct kobject *kobj);

同样,若该函数失败(如果失败,将返回一错误代码),并对此做出相应的动作,内核提供了一个方便的函数:

extern int kobject_register(struct kobject *kobj);

该函数是kobject_initkobject_add的结合 

当一个 kobject 被传递给 kobject_add, 它的引用计数被递增。在kset中最重要的内容是对象的引用。在某些时候,可能必须从kset中删除kobject,以清除引用,可用下面函数:

void kobject_del(struct kobject *kobj);

还有一个 kobject_unregister函数,是 kobject_del和 kobject_put的结合.

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

* 图表中所有的被包含的 kobject实际上被嵌入在一些其他类型中, 甚至可能是其他的 ksets.

* 一个kobjct的父节点不一定包含它的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中ktype。因此在典型应用中,kobject中的ktype成员被设置为NULL。因为kset中的ktype是实际被使用的成员。最后kset中包含了一个子系统指针(称之为subsys)。

子系统:

子系统是对整个内核中一些高级部分的表述,通常(但不一定)显示在sysfs分层结构中的顶层。






















猜你喜欢

转载自blog.csdn.net/buhuiguowang/article/details/79704147
今日推荐