1、前面说的

我在好几年前读linux 驱动代码的时候看到这个宏,百度了好久,知道怎么用了,但是对实现过程和原理还是一知半解。

container_of宏 在linux内核代码里面使用次数非常非常多,对于喜欢linux编程的同学来说,了解其实现方法,对以后看内核代码,写内核驱动的帮助都非常大,当然,我不是说了解这个就可以为所欲为了,内核博大精深,先宏观再微观去学习,不积跬步何以致千里,不要想着一口就能吃成一个胖子,我这篇文章主要剖析一下这个函数的实现原理,希望对大家学习过程中有所帮助。

android7.1/kernel/drivers/inputkernel/drivers/input$ grep -rn container_of ./|wc -l710android7.1/kernel/drivers/input$ 

使用grep -rn container_of ./|wc -l统计了下kernel/drivers/input/目录下的container_of出现的次数,一共有710次使用。

2、container_of的作用

container_of的作用的通过结构体成员变量地址获取这个结构体的地址,假设你的名字叫李光明,你还有一个弟弟叫做XXX,警察叔叔发现你弟弟XXX干了一件坏事,但是警察叔叔不知道你弟弟的名字,抓你来审问,你嘴巴很硬就是不说,警察叔叔就拿到你的名字,查到了你家的户口本,这下你弟弟就被查出来了,原来你弟弟XXX的名字叫做李小明。这种破案手法叫做顺藤摸瓜

内核函数调用常常给函数传入的是结构体成员地址,然后在函数里面又想使用这个结构体里面的其他成员变量,所以就引发了这样的问题,我认为这个也是用C实现面向对象编程的一种方法。
比如这段代码

static void sensor_suspend(struct early_suspend *h)                   {                                                                         struct sensor_private_data *sensor =                                          container_of(h, struct sensor_private_data, early_suspend);       if (sensor->ops->suspend)                                                 sensor->ops->suspend(sensor->client);                         } 

early_suspend是sensor_private_data 里面的一个成员,通过这个成员的地址获取sensor_private_data结构体变量的地址,从而调用里面的成员变量client。这个方法非常优雅。在这里我用到了一个比较叼的词,叫“优雅”
这里简单说下,传进来的h一定在其他地方定义并且操作系统分配了内存空间,h分配了空间,说明他的老爸也有内存了,要不然你顺藤摸瓜摸到一个NULL就傻逼了。

3、如何使用container_of

container_of需要传入三个参数,第一个参数是一个指针,第二个参数是结构体类型,第三个是对应第二个参数里面的结构体里面的成员。

container_of(ptr, type, member)

  • ptr:表示结构体中member的地址 h

  • type:表示结构体类型 struct sensor_private_data

  • member:表示结构体中的成员 early_suspend type里面一定要有这个成员,不能瞎搞啊

  • 返回结构体的首地址


4、container_of 用到的知识点 剖析

4.1、({})的作用

({})、第一个先说这个表达式,很多人可能懂,可能在很多地方见到这个表达式,但是自己却没有注意,这个表达式返回最后一个表达式的值。比如x=({a;b;c;d;}),最终x的值应该是d。

代码例子:

#include<stdio.h>

void main(void){    int a=({1;2;4;})+10;    printf("%d\n",a);//a=14}

4.2、typeof获取变量的类型

这个我们很少看到,这个关键字是C语言关键字的拓展,返回变量的类型,具体可以看GCC里面的介绍
https://gcc.gnu.org/onlinedocs/gcc/Typeof.html

++Another way to refer to the type of an expression is with typeof. The syntax of using of this keyword looks like sizeof, but the construct acts semantically like a type name defined with typedef.++

代码例子:

void main(void){    int a = 6;    typeof(a) b =9;    printf("%d %d\n",a,b);}

4.3、(struct st*)0的作用

尺子大家应该都用过吧,比如我想用尺子量一本书本的长度,我们第一时间就需要找到尺子的0刻度的位置,然后用这个0刻度的位置去对准书本的边,然后再贴合对齐,在书本的另一边查看尺子刻度就可以知道书本的长度了。
现在我们需要量一个结构体的长度,我们也可以用尺子来量,我们只要找到这个0刻度的位置就可以了。同理,即使我们不知道0刻度位置,我们首尾刻度相减一样可以计算出结构体的长度。
但是在C语言里什么事尺子呢?你想到的可能是sizeof,不幸的是,这个并不能满足我们的需要,所以才有了(struct st *),这个当作尺子真的再好不过了。

struct st{    int a;    int b;}*p_st,n_st;

void main(void){    printf("%p\n",&((struct st*)0)->b);}

上面的代码

(struct st*)0

这个的意思就是把这个结构体放到0刻度上面开始量了,然后量到哪里呢?

&((struct st*)0)->b)

这个就体现出来了,量到b的位置。所以上面的输出应该是4

看完上面的解释,应该知道下面这两个代码的功能是一样的。

typeof ((struct st*)0)->b) c; // 取b的类型来声明cint c;

其实不只是对于0,用其他数字一样是有效的,比如下面的代码,编译器关心的是类型,而不在乎这个数字。

printf("%p\n",&((struct st*)4)->b  -4 );

这文章写了有几天了,但是一直不想直接发出去,因为我觉得这个核心点总是没有找到一个特别好的论证方法,看完上面后,大概对这种测量应该有点感觉了吧,如果现在需要你把一个数组的首地址设置为0,要怎么做呢?

先思考一下,假设这里延迟了几分钟。

代码如下:

struct A {    short array[100];};

int main(int argc, char *argv[]){    int i = 10;

    A* a = (A*)0;    printf("%p %d %d\n",a,sizeof(short), &a->array[20]);    getchar();    return 1;}//输出 00000000 2 40

有什么办法不使用==struct A *== 直接把数组的地址放到0位置呢?目前我还没有找到其他更好的办法,如果有好的建议的,请留言给我。

4.4、offsetof(TYPE, MEMBER)

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

size_t 这个有不懂的可以百度下,就是unsigned 的整数,在32位和64位下长度不同,所以这个offsetof就是获取结构体的偏移长度

4.5、const int* p的作用

上面的宏定义里面还有一个小知识点

const typeof( ((type *)0)->member ) *__mptr

上面的代码可以简写成

const int * __mptr

这个说明什么问题呢?这个说明__mptr指向的整型数据是一个const(常数)。
这就涉及到两外两个知识

int * const __mptr;//表示__mptr的值不能改变//和const int * const __mptr; //表示__mptr不能改变而且指向的内容也不能改变

5、 container_of 剖析

看完上面的几个知识点,再来看container_of这个宏就显得非常清晰了。我把解析部分写在下面的代码注释里面。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)#define container_of(ptr, type, member) ({          \        const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \        (type *)( (char *)__mptr - offsetof(type,member) );})//-----分割线struct st{    int a;    int b;}*pt;//用这个来举例container_of(&pt->a,struct st,a) const typeof( ((struct st *)0)->a ) *__mptr = (const typeof( ((struct st *)0)->a ) *)(&pt->a);const int *__mptr = (int *)(&pt->a);//第一句解析完,实际上就是获取a的地址。(type *)( (char *)__mptr - offsetof(type,member) );//这个变成(struct st *)( (char *)__mptr - ((unsigned int) &((struct st*)0)->a));//这句的意思,把a的地址减去a对结构体的偏移地址长度,那就是结构体的地址位置了。

6、实例代码

经过上面的解释,至少对这个宏有感觉了吧,写个代码来测试一下,让自己与代码融合为一体,这样才能做到人码合一的境界。
代码如下:

#include<stdio.h>#include<stddef.h>#include<stdlib.h>

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

/*ptr 成员指针* type 结构体 比如struct Stu* member 成员变量,跟指针对应* */#define container_of(ptr, type, member) ({          \        const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \        (type *)( (char *)__mptr - offsetof(type,member) );})

typedef struct Stu{        int age;        char name[10];        int id;        unsigned long phone_num;}*p_stu,str_stu;

void print_all(void *p_str){

    p_stu m1p_stu = NULL;

    m1p_stu = container_of(p_str,struct Stu,age);

    printf("age:%d\n",m1p_stu->age);    printf("name:%s\n",m1p_stu->name);    printf("id:%d\n",m1p_stu->id);    printf("phone_num:%d\n",m1p_stu->phone_num);}

void main(void){    p_stu m_stu = (p_stu)malloc(sizeof(str_stu));

    m_stu->age = 25;    m_stu->id  = 1;    m_stu->name[0]='w';    m_stu->name[1]='e';    m_stu->name[2]='i';    m_stu->name[3]='q';    m_stu->name[4]='i';    m_stu->name[5]='f';    m_stu->name[6]='a';    m_stu->name[7]='\0';    m_stu->phone_num=13267;

    /*传结构体成员指针进去*/    print_all(&m_stu->age);

    printf("main end\n");

    if(m_stu!=NULL)        free(m_stu);}

7、程序输出

age:25name:weiqifaid:1phone_num:13267main end

当你看到这里的时候,说明你已经阅读完上面的内容

不管怎样,感谢您有心或者无意的关注和支持

公众号接入了AI功能,随意回复任意消息哦(比如笑话,天气

觉得不错,请帮忙转发,点好看,您的每一次支持,我都将铭记于心

想获取学习资料,请点击状态栏公众号福利按钮

linux 内核宏container_of剖析相关推荐

  1. Linux 内核宏 time_after解析

    同学们留言回复答案看看 可能很多老鸟对这样的Linux 内核宏已经见惯不怪了,但是作为新手的Linux内核开发者,我觉得非常有必要了解其中的原理和作用. jiffies 这个想必大家已经非常熟悉,ji ...

  2. Linux内核链表实现剖析

    Linux内核使用环形双向链表,无所谓头结点和尾节点. 内核链表详细信息见 include/ linux / list.h . 1. 定义和初始化内核链表 struct list_head { str ...

  3. linux内核源码剖析 博客,【Linux内存源码分析】页面迁移

    页面迁移其实是伙伴管理算法中的一部分,鉴于其特殊性,特地另行分析.它是2007年的时候,2.6.24内核版本开发时,新增碎片减少策略(the fragmentation reduction strat ...

  4. 【Linux内核源码剖析】内核源码的组织结构

    文章目录 前言 一.Linux内核的特征 二.为什么要学Linux内核 三.Linux操作系统结构 1. Linux内核在系统中的位置 2. Linux内核子系统之间的关系 3. 系统主要的数据结构 ...

  5. Linux内核中container_of宏的理解

    对 typeof 的理解: 实际上, typeof 并不是宏定义,它是GCC的关键字,是GCC特有的特性.如果只知道一个变量的名字要得到其类型,并不是宏定义能够完成的,这需要编译时的信息.所以,typ ...

  6. linux内核代码container_of

    它的作用显而易见,那就是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针. typedef unsigned int__kernel_size_t; typedef __ker ...

  7. linux内核中链表代码分析---list.h头文件分析(一)

    linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17:13:14 在学习数据结构时,有一个重要的知识点就是链表.对于链表的一些基本操作,它的最好学习资料就是内核中的li ...

  8. linux内核中IP数据包的输入与输出

    IP层主要函数之间的调用关系如下图所示: 上面的图主要是拷贝的<Linux内核源码剖析----TCP/IP实现上册>中的图11.3,原图中有部分错误,所以这里重新绘制了一下,并且去掉了一些 ...

  9. 嵌入式C语言自我修养 04:Linux 内核第一宏:container_of

    4.1 typeof 关键字 ANSI C 定义了 sizeof 关键字,用来获取一个变量或数据类型在内存中所占的存储字节数.GNU C 扩展了一个关键字 typeof,用来获取一个变量或表达式的类型 ...

最新文章

  1. 敏捷开发日常跟进系列之二:燃尽图(中)
  2. Elasticsearch设置最大返回条数
  3. idea中自定义修改jdk源码,加注释
  4. python水印_用python来给图片加水印
  5. 强烈推荐:Spring 核心知识点总结.pdf
  6. Python中正反斜杠('/'和'\')的意义
  7. 小白都能看懂的缓存入门
  8. BERT源码分析PART III
  9. silverlight体验之三:简单控件堆成个Login
  10. apt 和 apt-get 区别
  11. TensorFlow2.0 学习笔记(四):迁移学习(MobileNetV2)
  12. CAD二次开发——cad查看对象C#:MdgDbg.dll
  13. python mysql就业情况_影响员工离职因素分析—Mysql/python
  14. C++ 线段树,树状数组
  15. 九大免费自学编程网站,带你告别学习焦虑
  16. 个推技术 | 厂商和App必看!统一推送UPS最全解读和快速接入指南
  17. Linux应用开发(十一)——I^2C接口
  18. oracle例外使用
  19. 想搞机器学习,不会特征工程?你TM逗我那!
  20. 查看linux网口连接时间,linux检测网口连接状态

热门文章

  1. SharePoint 2013 Nintex Workflow 工作流帮助(六)
  2. poj 1274 The Perfect Stall
  3. 申请实习生,你准备好了吗?
  4. abstract的一些用法
  5. git解除与远程分支的关联
  6. 关闭网页如何接收服务器消息,WebSocket服务端发消息给客户端,浏览器收到消息就关闭了...
  7. c3p0-config.xml文件简单说明与备忘
  8. models.ForeignKey( ,on_delete=models.CASCADE)    # 关联外键
  9. SparkHiveSQL中Join操作的谓词下推?
  10. 剑指offer:二叉树中和为某一值的路径