这几天一直在学习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结构中的、名为kp的kobject结构指针进行转换的代码如下:
struct cdev *device=container_of(kp,struct cdev,kobj);
2、kobject的初始化
① 首先是将整个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函数。
4、release函数和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)保存了该指针。
* 如果kobject是kset的一个成员的话,kset会提供kobj_type指针。
二、kobject层次结构、kset和子系统
通常,内核用kobject结构将各个对象连接起来组成一个分层的结构体系,从而与模型化的子系统相匹配,有两种独立的机制用于连接:parent指针和kset。
在kobject结构的parent成员中,保存了另外一个kobject结构的指针,这个结构表示了分层结构中上一层的节点。比如一个kobject结构表示了一个USB设备,它的parent指针可能指向了表示USB集线器的对象,而USB设备是插在USB集线器上的。
1、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像是kobj_type结构的扩充,一个kset是嵌入相同类型结构的kobject集合。但是kobj_type结构关心的是对象的类型,而kset结构关心的是对象的聚集与集合。
kset是kobject顶层容器类,在每个kset内部,包含了自己的kobject。
kset一直在sysfs中出现,一旦一个kset已被建立并且加入到系统,会有一个sysfs目录给它。kobject没有必要在sysfs中出现,但是每个是kset成员的kobject都出现在那里。
通俗的将,kobject建立一级的子目录,里面只能包含文件;kset可以为kobject建立多级的层次性的父目录。
下图为一个简单的kset分层结构:
创建一个对象时,通常要把一个kobject添加到kset中去。这个过程有两个步骤:
1) 先把kobject的kset成员指向目的的kset
2) 然后将kobject传递给下面的函数
int kobject_add(struct kobject * kobj);
当把一个kobject传递给kobject_add时,将会增加它的引用计数。
内核提供了一个方便使用的函数:
extern int kobject_register(struct kobject * kobj);
该函数只是kobject_init和kobject_add的简单组合。
在某些时候,可能不得不把kobject从kset中删除,以清除引用,使用下面的函数达到这个目的:
void kobject_del(struct kobject * kobj);
还有一个kobject_unregister函数,它是kobject_del和kobject_put的组合。
2、kset上的操作
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成员是实际上被使用的成员。
三、低层sysfs操作
* kobject在sysfs中的入口始终是一个目录,因此,对kobject_add的调用将在sysfs中创建一个目录。
* 分配给kobject(使用kobject_set_name函数)的名字是sysfs中的目录名。这样,处于sysfs分层结构相同部分中的kobject必须有唯一的名字。
* sysfs入口在目录中的位置对应于kobject的parent指针。如果调用kobject_add的时候,parent是NULL,它将被设置为嵌入到新kobject的kset中的kobject,这样,sysfs分层结构通常与kset创建的内部结构相匹配。如果parent和kset都是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、非默认属性
如果希望在kobject的sysfs目录中添加新的属性,即非默认属性,只需要填写一个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:通过环境变量传递任何热插拔脚本需要的信息,他会在(udev或mdev)调用之前,提供添加环境变量的机会。