在学习Linux驱动的过程中,遇到一个宏叫做container_of。
该宏定义在include/linux/kernel.h中,首先来贴出它的代码:

/** 
* container_of - cast a member of a structure out to the containing structure 
* @ptr:        the pointer to the member. 
* @type:       the type of the container struct this is embedded in. 
* @member:     the name of the member within the struct. 
* */

#define container_of(ptr, type, member) ({                      \ 
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ 
(type *)( (char *)__mptr - offsetof(type,member) );})

它的作用显而易见,那就是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。比如,有一个结构体变量,其定义如下:

struct demo_struct { 
           type1 member1; 
           type2 member2; 
           type3 member3; 
           type4 member4; 
};      
struct demo_struct demo;
同时,在另一个地方,获得了变量demo中的某一个域成员变量的指针,比如:
      type3  *memp = get_member_pointer_from_somewhere();
此时,如果需要获取指向整个结构体变量的指针,而不仅仅只是其某一个域成员变量的指针,我们就可以这么做:
     struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
这样,我们就通过一个结构体变量的一个域成员变量的指针获得了整个结构体变量的指针。
下面说一说我对于这个container_of的实现的理解:
首先,我们将container_of(memp, struct demo_struct, type3)根据宏的定义进行展开如下:
      struct demo_struct *demop = ({                      \ 
         const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp);    \ 
         (struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})
其 中,typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型。因此,上述代码中的第2行的作用是首先使用typeof获取结构体域变量member3的类型为 type3,然后定义了一个type3指针类型的临时变量__mptr,并将实际结构体变量中的域变量的指针memp的值赋给临时变量__mptr。

(char *)__mptr转换为字节型指针。(char *)__mptr - offsetof(type,member) )用来求出结构体起始地址(为char *型指针),然后(type *)( (char *)__mptr - offsetof(type,member) )在(type *)作用下进行将字节型的结构体起始指针转换为type *型的结构体起始指针。

假设结构体变量demo在实际内存中的位置如下图所示:
     demo
 +-------------+ 0xA000
 |   member1   |
 +-------------+ 0xA004
 |   member2   |
 +-------------+ 0xA010
 |   member3   |
 +-------------+ 0xA018
 |   member4   |
 +-------------+

则,在执行了上述代码的第2行之后__mptr的值即为0xA010。
再看上述代码的第3行,其中需要说明的是offsetof,它定义在include/linux/stddef.h中,其定义如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
先分析一下这个宏的运行机理:
一共4步 
1. ( (TYPE *)0 ) 将零转型为TYPE类型指针; 
2. ((TYPE *)0)->MEMBER 访问结构中的数据成员; 
3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址; 
4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型。巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;
同样,我们将上述的offsetof调用展开,即为:
 (struct demo_struct *)( (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) );
可见,offsetof的实现原理如上所述,就是取结构体中的域成员相对于地址0的偏移地址,也就是域成员变量相对于结构体变量首地址的偏移。
因 此,offsetof(struct demo_struct, member3)调用返回的值就是member3相对于demo变量的偏移。结合上述给出的变量地址分布图可知,offsetof(struct demo_struct, member3)将返回0x10。
于是,由上述分析可知,此时,__mptr==0xA010,offsetof(struct demo_struct, member3)==0x10。
因此, (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000,也就是结构体变量demo的首地址(如上图所示)。
这就是从结构体某成员变量指针来求出该结构体的首指针。指针类型从结构体某成员变量类型转换为该结构体类型。
由此,container_of实现了根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针的功能。

以上内容载自网络,这篇文章分析的很透彻,顺便说一下,宋宝华的《linux设备驱动开发详解》P132 最后一行当中对该宏的参数解释是错误的!当然了,暇不掩瑜!
以下是我自己的一些理解:
首先,我定义了一个字符设备结构体
struct    globalmem_dev
{
    struct cdev   my_cdev;            //字符设备之基础结构体 
    unsigned char mem[GLOBALMEM_SIZE];    
    struct semaphore sem;/
};
接下来我实例化了一个该设备的指针对象
struct globalmem_dev    *pdev;

后来在open函数中我是这么来用的
int globalmem_open(struct inode *inode, struct file *filp)关于filp的产生和消亡参见《驱动详解》P92
{                                   
    struct globalmem_dev *pdev;    
    printk("\nFunction globalmem_open Invoked\n");    
    pdev = container_of(inode->i_cdev,  struct   globalmem_dev,  my_cdev);
    filp->private_da

ta = pdev;    
    if(down_trylock(&pdev->sem))//获得信号量,  真的我爱  container_of!!!!  我爱死container_of 了!!!
        return -EBUSY;     
    return 0;
}
对以上用法的说明:
参数3是参数2这个结构体的一个成员的名字!而不是类型名!参数1是一个指针,它指向参数3这个成员
inode 中的i_cdev字段是一个指针,当我们成功insmod了一个设备驱动的时候,我们会通过mknod创建一个设备文件节点并和具体设备(驱动)想关联, 这个设备文件节点所对应的就是struct inode结构体的一个实例,这个结构体有一个字段i_cdev,是个struct   cdev类型的指针,它会指向设备结构体的my_cdev字段。至此你已经有了一个指向某个 globalmem_dev的my_cdev字段的一个指针(在调用open前pdev的内存分配假定已经完成)由此container_of可以帮你计
算出指向该设备结构体的指针。
当一个设备驱动对应多个设备(子设备)时,你就知道container_of发挥的作用了!当你针对每一个设备调用 open时,因为每个设备对应的设备文件节点不一样,那么根据该节点的i_cdev字段所计算的设备结构体指针也不一样,你就可以找到特定节点所对应的设 备结构体!而不至于对不同的子设备编写大同小异的各自的设备驱动。

转自:http://dev.firnow.com/course/6_system/linux/linuxjq/20100313/198611.html

(linux)container_of()宏的更多相关文章

  1. linux中offsetof与container_of宏定义

    linux内核中offsetof与container_of的宏定义 #define offsetof(TYPE, MEMBER)    ((size_t) &((TYPE *)0)->M ...

  2. linux内核宏container_of

    首先来个简单版本 /* given a pointer @ptr to the field @member embedded into type (usually * struct) @type, r ...

  3. container_of宏定义分析---linux内核

    问题:如何通过结构中的某个变量获取结构本身的指针??? 关于container_of宏定义在[include/linux/kernel.h]中:/*_** container_of - cast a ...

  4. Linux内核中container_of宏的详细解释

    上一节拒绝造轮子!如何移植并使用Linux内核的通用链表(附完整代码实现)我们在分析Linux内核链表的时候注意到内核在求解结构体偏移的时候巧妙的使用了container_of宏定义,今天我们来详细剖 ...

  5. (转)offsetof与container_of宏[总结]

    1.前言 今天在看代码时,遇到offsetof和container_of两个宏,觉得很有意思,功能很强大.offsetof是用来判断结构体中成员的偏移位置,container_of宏用来根据成员的地址 ...

  6. container_of宏剖析

    container_of宏剖析//该宏位于include/linux/kernel.h 1.定义格式 /** * container_of - cast a member of a structure ...

  7. linux初始化宏__init, __exit

    我们在内核中经常遇到初始化函数是这样定义的:static int __init init_func(); ,与普通函数相比,定义中多了__init.那么,__init是什么意思呢?还有与其匹配的__e ...

  8. offsetof与container_of宏[总结]

    1.前言 今天在看代码时,遇到offsetof和container_of两个宏,觉得很有意思,功能很强大.offsetof是用来判断结构体中成员的偏移位置,container_of宏用来根据成员的地址 ...

  9. container_of宏

    title: container_of宏 date: 2019/7/24 15:49:26 toc: true --- container_of宏 解析 在linux链表结构中有这样一个宏,通过成员变 ...

随机推荐

  1. ftp链接、上传、下载、断开

    开发环境:Jdk 1.8 引入第三方库:commons-net-2.2.jar(针对第一种方法) 一.基于第三方库FtpClient的FTP服务器数据传输 由于是基于第三方库,所以这里基本上没有太多要 ...

  2. 发展城市 BZOJ 3700

    发展城市 [问题描述] 众所周知,Hzwer学长是一名高富帅,他打算投入巨资发展一些小城市. Hzwer打算在城市中开N个宾馆,由于Hzwer非常壕,所以宾馆必须建在空中,但是这样就必须建立宾馆之间的 ...

  3. laravel的视图

    //输出视图 //建立控制器方法public function hello_test(){ return view('member/hello_test',['name'=>'张三','age' ...

  4. Redis数据结构之整数集合

    整数集合是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t .int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素. 一.整数集合数据结构定义 参数说明: ...

  5. PAT (Advanced Level) 1087. All Roads Lead to Rome (30)

    暴力DFS. #include<cstdio> #include<cstring> #include<cmath> #include<vector> # ...

  6. 从Activity的启动流程理解Binder

    简述 关于Activity启动流程和Binder的文章很多,大多数是分开来讲的,本文将二者结合起来,着重分析启动流程中跨进程方面的细节,其实,启动流程看似调用繁多,主要是复杂在Activity栈管理等 ...

  7. ZOJ - 4020 Traffic Light (BFS)

    [传送门]http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4020 [题目大意]从起点(sx, sy)出发,要到达(ex , ...

  8. Spring整合SSM的配置文件详解

    在整合三大框架SSM , 即 Spring 和 SpingMVC和Mybatis的时候,搭建项目最初需要先配置好配置文件. 有人在刚开始学习框架的时候会纠结项目搭建的顺序,因为频繁的报错提示是会很影响 ...

  9. Maven错误 diamond operator is not supported in -source 1.5 (use -source 7 or higher to enable diamond operator)问题解决

    如果在Maven构建时出现: diamond operator is not supported in -source 1.5 (use -source 7 or higher to enable d ...

  10. phpQuery用法总结

    项目下载地址:http://code.google.com/p/phpquery/ 获取内容的方法: 第一种:newDocumentFile phpQuery::newDocumentFile($ur ...