linux设备驱动模型-浅析-转

时间:2021-03-30 21:07:17

1、  typeof

typeof并非ISO C的关键字,而是gcc对C的一个扩展。typeof是一个关键字(类似sizeof),用于获取一个表达式的类型。

举个简单的例子:

char tt;

typeof(tt) cc;

则typeof(tt)等价于char,即相当于声明了char cc;

2、  offsetof

位置:

用途:获取结构类型TYPE里的 成员MEMBER 在结构体内的偏移

分析:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

假设有结构体uio_mem:

struct uio_mem

{

struct kobject kobj;

unsigned long addr;

unsigned long size;

int memtype;

void __iomem *internal_addr;

};

如果想获得成员memtype在uio_mem内的偏移,那么该怎么做呢?

很简单,只要使用offsetof(struct uio_mem, memtype)就可以了。

可以将上述表达式展开为:

(size_t) &(( struct uio_mem *)0)-> memtype

( struct uio_mem *)0将0强制转换为struct uio_mem的指针,记为p = ( struct uio_mem *)0,p是指向struct uio_mem的指针,值为0。而p->memtype则指向struct uio_mem的成员memtype,再取地址&(p->memtype),这样就得到了成员memtype的地址。由于结构体基地址p为0,所以&(p->memtype)就是相对于p的偏移了。最后再把成员的地址强制转换为size_t,其实就是转换为int。

typedef __kernel_size_t  size_t;
typedef unsigned int __kernel_size_t;

此例中offsetof(struct uio_mem, memtype) = sizeof(struct kobject) + sizeof(unsigned long) + sizeof(unsigned long)

参考:http://cutebunny.blog.51cto.com/301216/67517

3、  container_of

位置:

用途:通过指向成员member的指针ptr,获取包含该成员的结构体type的指针

分析:

#define container_of(ptr, type, member) ({                /

const typeof( ((type *)0)->member ) *__mptr = (ptr);       /

(type *)( (char *)__mptr - offsetof(type,member) );})

假设有结构体uio_mem:

struct uio_mem

{

struct kobject kobj;

unsigned long addr;

unsigned long size;

int memtype;

void __iomem *internal_addr;

};

现有指向uio_mem的成员memtype的指针p(因此p的类型为int *),那么如何获取结构体uio_mem的指针?恭喜你,答对了,使用container_of(p, struct uio_mem, memtype)。

将该宏展开为:

const typeof( ((struct uio_mem *)0)-> memtype) *__mptr = (p);// 1

(struct uio_mem *)( (char *)__mptr - offsetof(struct uio_mem, memtype) ); // 2

根据之前的分析,可以很容易理解语句1等价于:

const int *__mptr = p;

即__mptr指向uio_mem的成员memtype,也即__mptr是uio_mem的成员memtype的地址。那么如果我们知道memtype在struct uio_mem中的偏移的话,用memtype的地址减去这个偏移不就知道结构体uio_mem的“首”地址了吗?

根据上述分析,通过offsetof(struct uio_mem, memtype)获取memtype在struct uio_mem中的偏移,然后再用memtype的实际地址(也即__mptr)减去这个偏移,这样就得到了结构体uio_mem的地址了,最后再将uio_mem的地址强制转换为struct uio_mem *,就获得了结构体uio_mem的指针了,这就是上述语句2所做的事情。很容易理解语句1等价于:

const int *__mptr = p;

即__mptr指向uio_mem的成员memtype,也即__mptr是uio_mem的成员memtype的地址。那么如果我们知道memtype在struct uio_mem中的偏移的话,用memtype的地址减去这个偏移不就知道结构体uio_mem的“首”地址了吗?

根据上述分析,通过offsetof(struct uio_mem, memtype)获取memtype在struct uio_mem中的偏移,然后再用memtype的实际地址(也即__mptr)减去这个偏移,这样就得到了结构体uio_mem的地址了,最后再将uio_mem的地址强制转换为struct uio_mem *,就获得了结构体uio_mem的指针了,这就是上述语句2所做的事情。

=======================================================================================

驱动模型和建立在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