自己写驱动之Linux设备驱动开发详解———设备驱动概述
驱动模型和建立在kobject之上的抽象之所以难以理解,部分原因在于没有一个明显的入口点。处理kobjects需要理解一些不同的相互之间互相引用的类型。为了使事情变得简单,我们将采用“多遍”的方法,从模糊的概念开始并且逐步添加细节。为了这个目的,在这里给出一些我们将要使用到的一些概念。
l 一个kboject是类型为struct kobject的一个对象
Kobject有一个名字(name)和一个引用计数(reference count)。一个kobject还包含一个父指针(该指针可以使得对象之间可以分层次排列)、一个特殊的类型(即ktype)、一个在sysfs虚拟文件系统里的表示(representation)。
Kobjects基本上并不关注本身,它们通常被嵌入到其他数据结构中,这些数据结构中包含真正受关注的成员。
永远不要让一个数据结构中包含超过1个kobject,如果真的这么做了,那么对该对象的引用计数必定一团糟并且不正确,你的代码也将充满bug,所以千万别这么做。
l 一个ktype是 包含kobject的对象 的类型
每一个包含kobject的结构需要一个对应的ktype。当创建和销毁kobject的时候,ktype控制将会发生什么。
l 一个kset是一组kobjects
这组kobjects可以属于同一ktype,也可以属于不同的ktypes。Kset是收集kobjects的基本的容器类型。Ksets也包含它们自己的kobjects,不过你可以很安全地忽视那些实现细节,因为kset的核心代码会自动地处理它们自己的kobject。
我们将会学习如何创建和操作所有这些类型。我们将采用自底向上的方法,先回到kobjects的学习。
一、嵌入kobjects
内核代码基本上不会创建单独的kobject,但是也有例外(后面解释)。Kobjects被用来控制访问一个更大的、针对特定域的对象。所以,你会发现kobjects常嵌入至其他数据结构中。如果你习惯用面向对象的方式考虑问题,可以认为kobjects是被继承的顶层的抽象基类。Kobject实现了一组操作,这组操作对自身并没有多大的用处,但是对其他对象(包含kobject的对象)来说很有用。C语言并不支持继承关系的直接支持,所以必须使用其他技术—比如嵌入数据结构。举个例子,UIO的代码里有一个数据结构定义了关联到一个uio设备的内存边界:
struct uio_mem
{
struct kobject kobj;
unsigned long addr;
unsigned long size;
int memtype;
void __iomem *internal_addr;
};
如果你有一个uio_mem结构体,使用其kobj成员就可以找出嵌入的koject。使用kobjects的代码经常会遇到一个问题:给定一个kobject指针,怎样找出包含该kobject的结构的指针?你必须避免一些“诡计”,比如,假设kobject是某个结构的第一个成员(结构的第一个成员的指针就是该结构的指针),相反,你应该使用container_of宏():
container_of(pointer, type, member)
pointer就是嵌入的kobject的指针,type是包含kobject的结构的类型,member是pointer指向的结构里的域的名字。container_of的返回值就是给定type的指针。举个例子,kp指向uio_mem结构里的kobject,那么可以通过如下方式获取指向uio_mem的指针:
struct uio_mem *u_mem = container_of(kp, struct uio_mem, kobj);
程序员通常定义一个简单宏来将kobject指针“后向转换”为其“容器”的类型(即包含kobject的结构的指针)。
二、初始化kobjects
创建kobject的代码当然必须得初始化那个对象。一些内部的域强制使用kobject_init()来初始化:
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
因为每个kobject必须和一个kobj_type 相关联,所以要想正确地创建kobject就需要一个ktype。调用kobject_init()后,为了在sysfs中注册kobject,函数kobject_add()必须被调用:
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
该函数可以正确地设置kobject的名称和它的父节点。如果kobject被关联到一个特殊的kset,在调用kobject_add()之前kobj->kset必须被赋值。如果一个kset被关联到一个kobject,那么在kobject_add()调用中该kobject的父节点可被设置为NULL(就是kobject_add()的第二个参数设置为NULL),并且,该kobject的父节点就是那个kset本身。
因为kobject的名称在kobject加入内核的时候就被设定了,所以永远不要直接操作一个kobject的名字。如果你必须改变kobject的名字,那么请调用kobject_rename():
int kobject_rename(struct kobject *kobj, const char *new_name);
该函数不会进行任何locking,也不会去检查名字的合法性,所以调用者必须提供locking机制和检查名字的合法性。
还有一个叫做kobject_set_name()的函数,该函数将被删除,所以不要调用这个函数。
应该使用函数kobject_name()来获取kobject的名字:
const char *kobject_name(const struct kobject * kobj);
还有一个函数用于同时初始化和将kobject加入内核,即kobject_init_and_add():
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...);
该函数的参数和单独的函数kobject_init()和kobject_add()所描述的参数一样。
三、热插拔
当一个kobject被注册到kobject核心后,需要对外声明该kobject已经被创建。可以通过调用kobject_uevent()来实现:
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
当kobject第一次被加入内核时,使用KOBJ_ADD事件。使用该事件时,所有的kobject的属性或孩子都必须已被正确地初始化,因为当KOBJ_ADD发生时用户空间会立刻开始检查它们。
当kobject被从内核移除时,kobject核心会自动创建KOBJ_REMOVE事件,调用者无需手动创建。
四、引用计数
Kobject的一个关键的功能就是作为包含它的对象的引用计数。只要对这个对象的引用还存在,该对象(和支持该对象的代码)就必须存在。底层操作kobject引用计数的函数是:
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
正确调用kobject_get()将会增加kobject的引用计数并且返回指向kobject的指针。
当释放一个引用时,调用kobject_put()会减少引用计数,并且有可能会释放对象(当引用计数为0时)。注意,kobject_init()设置引用计数为1,所以设置kobject的代码最终需要调用kobject_put()来释放那个引用。
因为kobjects是动态的,所以它们不能被声明为静态的或者存放在堆栈上,而总是要动态地分配。未来版本的内核会包含对kobject的运行时检查,如果发现kobject是静态创建的,将会警告开发者。
如果你仅仅想用kobject作为你的结构体的引用计数器,那么请使用结构kref;使用kobject太浪费了。想了解kref的信息请参考Documentation/kref.txt。
五、创建“简单”的kobjects
有时开发者仅仅希望有一种途径去在sysfs层次中创建一个简单的目录,而不是必须要和复杂的ksets、show和store方法,还有别的细节搞混。这就是一个需要单独创建一个kobject的例外(前面说过一般不单独创建一个kobject的)。为了创建这样一个入口,可以使用函数:
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
该函数会在sysfs中指定父kobject的下面创建和放置一个kobject。创建简单的和该kobject相关联的属性时,可以使用:
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
或者int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
这里使用在 通过kobject_create_and_add()创建的kobject 上的两种类型的属性,可以是kobj_attribute类型的,因此不需要创建自定义的属性。
六、ktypes和release方法
一个重要的事情还没有被讨论,那就是当一个kobject的引用计数为0时,将会发生什么?创建kobject的代码一般不知道这种情况什么时候会发生(引用计数变为0);引入sysfs后,即使是可以预期的对象生命周期也会变得复杂,因为内核的其他部分可以引用任何注册于系统内的kobject。
最终的结果就是,一个被kobject保护的结构(结构中包含一个kobject)在其引用计数变为0之前不能被释放。创建kobject的代码并不直接控制引用计数。因此,当对kobjects的最后一个引用消失时,代码必须异步地通知。
一旦你通过kobject_add()注册了你的kobject,永远不要直接使用kfree()去释放它。仅有的安全的方法是使用kobject_put()。
这个通知是通过kobject的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()方法,内核会给出警告。不要尝试通过提供一个“空的”release方法来规避这个警告;如果你尝试这么做,那么你会被kobject的维护者无情地嘲笑。
注意,在release方法中kobject的名字是可以获取的,但是在回调中必须不能被改变。否则,kobject核心将会出现内存泄漏,令人不快。
有趣的是,release方法并不存在于kobject内部,而是和ktype关联。所以让我们来介绍kobj_type结构:
struct kobj_type { void (*release)(struct kobject *);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs; };
这个结构体被用来描述一个特殊类型的kobject(或者,更准确点说,描述kobject的“容器”对象)。每个kobject都需要一个相关联的kobj_type结构;当你调用kobject_init()或者kobject_init_and_add()时指向kobj_type结构的指针必须被赋值。
结构体kobj_type的release成员是一个指向对应于该类kobject的release方法。其他两个成员(sysfs_ops和default_attrs)控制如何在sysfs中表示这个类型的对象;这超出了本文档的范围。
default_attrs指针是默认属性的列表,当创建注册于这个ktype的kobject的时候,这些默认属性会被自动的添加。
七、ksets
一个kset仅仅是那些希望互相关联的kobjects的集合。没有特别的限制非要这些kobjects属于同一ktype,但是如果不是,那么要非常小心。
一个kset提供以下功能:
l 它为一组kobjects提供一个容器
一个kset可以被内核用来跟踪所有的块设备或者所有的PCI设备驱动。
l 一个kset也是sysfs的一个子目录,在那里与之关联的kobjects会被显示
每个kset都包含一个kobject,该kobject可以被设置为其他kobjects的父节点;sysfs的顶层目录就是通过这种方式构建的
l ksets支持kobjects的热插拔,并且影响着如何向用户空间报告uevent事件
在面向对象领域,kset是顶层的容器类;ksets包含它们自己的kobject,但是那个kobject被kset代码所管理,并且不应该被其他用户操作。
一个kset使用标准的内核链表管理它的孩子节点。Kobjects通过它们的kset成员指向包含它们的kset。基本上所有的情况下,属于某个kset的kobject都会把包含它的kset当作自己的父节点(或者,严格地说,把内嵌于kset的kobject当作父节点)。
由于一个kset内含一个kobject,因此kset总是应该动态地创建而不是静态地声明或者运行在堆栈上。
使用如下代码创建一个kset:
struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *u,
struct kobject *parent);
当结束对kset的使用时,调用void kset_unregister(struct kset *kset);来销毁它。如果一个kset希望控制与之关联的kobjects的uevent操作,可以使用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, struct kobj_uevent_env *env); };
对于特定的kobject,filter函数允许kset阻止uevent事件被传送到用户空间。如果filter函数返回0,uevent事件将不会被发送。
name函数的调用可以重写发送uevent到用户空间的kset的缺省名字。缺省情况下,该名字就是kset自己的名字,但是如果存在name函数,那么就可以重写缺省的名字。
The uevent function will be called when the uevent is about to be sent to userspace to allow more environment variables to be added to the uevent.
当uevent事件即将被发送至用户空间以允许更多的环境变量加入到该uevent事件时,uevent函数将会被调用(不通-_-….)。
One might ask how, exactly, a kobject is added to a kset, given that no functions which perform that function have been presented.
(上面那句不会翻译…)
答案是这个工作由kobject_add()来处理。当一个kobject被传递到kobject_add()时,kobject的kset成员应该指向它(kobject)所属的kset,然后kobject_add()处理剩下的事情。
如果属于一个kset的kobject没有父kobject集合(不懂…),它将被加入到kset的目录。并非所有的kset的成员都需要存在于kset的目录。如果在kobject被加入之前,显示地将一个父kobject赋值给该kobject,那么这个kobject会注册进kset,但是被加入到其父kobject目录下。
八、Kobject的移除
当一个kobject被成功地注册进kobject核心后,在代码结束对它的使用时,必须清除它。你可以调用kobject_put(),调用该函数后,kobject核心将会自动释放分配给该kobject的所有内存。如果一个KOBJ_ADD uevent被发送到kobject,那么一个对应的KOBJ_REMOVE uevent也将被发送,并且所有其他的sysfs的空间管理也将会被处理。
如果你需要两个阶段来删除kobject(就是说当需要销毁kobject的时候不允许睡眠),调用kobject_del(),该函数会将kobject从sysfs中移除,这将使得kobject不可见,但是并未被清除,并且对象的引用计数也未改变。在稍后的时间里调用kobject_put()来完成与kobject相关联的内存的释放。
如果循环引用构成,kobject_del()可以被用来放弃对父节点的引用。这在有些场合很有效,比如一个父节点引用一个子节点。必须使用kobject_del()来破坏循环引用,之后一个release方法将被调用,并且之前环路中的对象互相release。
关于ksets和kobjects更详细的例子,请参考sample/kobject/kset-example.c code。
转载自: http://blog.csdn.net/king_208/article/details/5273689