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

linux设备驱动模型-浅析-转的更多相关文章

  1. Linux设备驱动模型之platform(平台)总线详解

    /********************************************************/ 内核版本:2.6.35.7 运行平台:三星s5pv210 /*********** ...

  2. LINUX设备驱动模型之class

    转自 https://blog.csdn.net/qq_20678703/article/details/52754661 1.LINUX设备驱动模型中的bus.device.driver,.其中bu ...

  3. Linux设备驱动模型底层架构及组织方式

    1.什么是设备驱动模型? 设备驱动模型,说实话这个概念真的不好解释,他是一个比较抽象的概念,我在网上也是没有找到关于设备驱动模型的一个定义,那么今天就我所学.所了解 到的,我对设备驱动模型的一个理解: ...

  4. 探究linux设备驱动模型之——platform虚拟总线(一)

    说在前面的话 :      设备驱动模型系列的文章主要依据的内核版本是2.6.32的,因为我装的Linux系统差不多就是这个版本的(实际上我用的fedora 14的内核版本是2.6.35.13的.) ...

  5. Linux 设备驱动模型

    Linux系统将设备和驱动归一到设备驱动模型中了来管理 设备驱动程序功能: 1,对硬件设备初始化和释放 2,对设备进行管理,包括实参设置,以及提供对设备的统一操作接口 3,读取应用程序传递给设备文件的 ...

  6. linux设备驱动模型之Kobject、kobj_type、kset【转】

    本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/74838165 版权声明:本文为博主原创文章,转载请注明http://blog.c ...

  7. Linux设备驱动模型简述(源码剖析)

    1. Linux设备驱动模型和sysfs文件系统 Linux内核在2.6版本中引入设备驱动模型,简化了驱动程序的编写.Linux设备驱动模型包含设备(device).总线(bus).类(class)和 ...

  8. linux设备驱动模型

    尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要. Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统 ...

  9. linux设备驱动模型(kobject与kset)

    Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要 ...

随机推荐

  1. 第182天:HTML5——地理定位

    HTML5 Geolocation(地理定位) HTML5 Geolocation API 用于获得用户的地理位置. 鉴于该特性可能侵犯用户的隐私,除非用户同意,否则用户位置信息是不可用的. 浏览器支 ...

  2. hdu 6434 Count (欧拉函数)

    题目链接 Problem Description Multiple query, for each n, you need to get $$$$$$ \sum_{i=1}^{n} \sum_{j=1 ...

  3. Impala:新一代开源大数据分析引擎--转载

    原文地址:http://www.parallellabs.com/2013/08/25/impala-big-data-analytics/ 文 / 耿益锋 陈冠诚 大数据处理是云计算中非常重要的问题 ...

  4. 洛谷 P1678 烦恼的高考志愿

    题目背景 计算机竞赛小组的神牛V神终于结束了万恶的高考,然而作为班长的他还不能闲下来,班主任老t给了他一个艰巨的任务:帮同学找出最合理的大学填报方案.可是v神太忙了,身后还有一群小姑娘等着和他约会,于 ...

  5. 【BZOJ1176】Mokia(CDQ分治)

    [BZOJ1176]Mokia(CDQ分治) 题面 BZOJ权限题啊,,,, dbzoj真好 Description 维护一个W*W的矩阵,初始值均为S.每次操作可以增加某格子的权值,或询问某子矩阵的 ...

  6. unity3d模型不接受光照

    9楼 发表于 2015-4-21 16:34 | 只看该作者 sailo 发表于 2015-4-14 11:15 你好.遇到同样问题,请问要什么解决 1.你可以选择你不受光线照射的模型,模型属性lay ...

  7. BZOJ3451 Tyvj1953 Normal 【期望 + 点分治 + NTT】

    题目链接 BZOJ3451 题解 考虑每个点产生的贡献,即为该点在点分树中的深度期望值 由于期望的线性,最后的答案就是每个点贡献之和 对于点对\((i,j)\),考虑\(j\)成为\(i\)祖先的概率 ...

  8. C++——内存对象 禁止产生堆对象 禁止产生栈对象

    用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存的分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内 ...

  9. Codeforces 894.E Ralph and Mushrooms

    E. Ralph and Mushrooms time limit per test 2.5 seconds memory limit per test 512 megabytes input sta ...

  10. 手脱nSPack 3.7

    方法一: 1.   OD查壳—nSpack3.7的壳 2. 载入OD 看起来很眼熟,F8一次,然后下面就可以使用ESP定律了,使用ESP定律下断点,然后F9四次 3.   F9四次后落到这个位置 接下 ...