static void device_release(struct device *dev)
{struct device *rd = to_device(dev);devm_kfree(dev, rd);
}

这段代码突然出现to_device函数,

搜索查看定义:grep -r -n "to_device"

#define to_device(obj) container_of(obj, struct device, dev)

container_of什么鬼?网上一搜吓一跳:内核第一宏。

好吧自己小白学习了记录下。


Linux 内核中的 container_of 宏

container_of 宏介绍

有了上面语句表达式和 typeof 的基础知识,接下来我们就可以分析 Linux 内核第一宏:container_of。这个宏在 Linux 内核中应用甚广。会不会用这个宏,看不看得懂这个宏,也逐渐成为考察一个内核驱动开发者 C 语言功底的不成文标准。废话少说,我们还是先一睹芳容吧。

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

作为内核第一宏,绝对不是盖的:看看这身段,这曲线,高端大气上档次,低调奢华有内涵,不出去再做个头发,简直就是暴殄天物。GNU C 高端扩展特性的综合运用,宏中有宏,不得不佩服内核开发者这天才般地设计。那这个宏到底是干什么的呢?它的主要作用就是:根据结构体某一成员的地址,获取这个结构体的首地址。根据宏定义,我们可以看到,这个宏有三个参数,它们分别是:

  • type:结构体类型
  • member:结构体内的成员
  • ptr:结构体内成员member的地址

也就是说,我们知道了一个结构体的类型,结构体内某一成员的地址,就可以直接获得到这个结构体的首地址。container_of 宏返回的就是这个结构体的首地址。

container_of 宏使用示例

比如现在,我们定义一个结构体类型 student:

struct student
{int age;int num;int math;
};
int main(void)
{struct student stu;struct student *p;p = container_of( &stu.num, struct student, num);return 0;
}

在这个程序中,我们定义一个结构体类型 student,然后定义一个结构体变量 stu,我们现在已经知道了结构体成员变量 stu.num 的地址,那我们就可以通过 container_of 宏来获取结构体变量 stu 的首地址。

这个宏在内核中非常重要。我们知道,Linux 内核驱动中,为了抽象,对数据结构体进行了多次封装,往往一个结构体里面嵌套多层结构体。也就是说,内核驱动中不同层次的子系统或模块,使用的是不同封装程度的结构体,这也是 C 语言的面向对象思想。分层、抽象、封装,可以让我们的程序兼容性更好,适配更多的设备,但同时也增加了代码的复杂度。

我们在内核中,经常会遇到这种情况:我们传给某个函数的参数是某个结构体的成员变量,然后在这个函数中,可能还会用到此结构体的其它成员变量,那这个时候怎么办呢?container_of 就是干这个的,通过它,我们可以首先找到结构体的首地址,然后再通过结构体的成员访问就可以访问其它成员变量了。

struct student
{int age;int num;int math;
};
int main(void)
{struct student stu = { 20, 1001, 99};
​int *p = &stu.math;struct student *stup = NULL;stup = container_of( p, struct student, math);printf("%p\n",stup);printf("age: %d\n",stup->age);printf("num: %d\n",stup->num);
​return 0;
}

在这个程序中,我们定义一个结构体变量 stu,知道了它的成员变量 math 的地址 &stu.math,我们就可以通过 container_of 宏直接获得 stu 结构体变量的首地址,然后就可以直接访问 stu 结构体的其它成员 stup->age 和 stup->num。

4.7 container_of 宏实现分析

知道了 container_of 宏的用法之后,我们接着去分析这个宏的实现。作为一名 Linux 内核驱动开发者,除了要面对各种手册、底层寄存器,有时候还要应付底层造轮子的事情,为了系统的稳定和性能,有时候我们不得不深入底层,死磕某个模块,进行分析和优化。底层的工作虽然很有挑战性,但有时候也是很枯燥的,不像应用开发那样有意思。所以,为了提高对工作的兴趣,大家表面上虽然不说自己牛 X,但内心深处,一定要建立起自己的职位优越感。人不可有傲气,但一定要有傲骨:我们可不像应用开发,知道 API 接口、读读文档、完成功能就 OK 了。作为一名底层开发者,要时刻记住,要和寄存器、内存、硬件电路等各族底层群众打成一片。从群众中来,到群众中去,急群众所急,想群众所想,这样才能构建一个稳定和谐的嵌入式系统:稳定高效、上下通畅、运行365个日出也不崩溃。

container_of 宏的实现主要用到了我们上两节所学的知识:语句表达式和 typeof,再加上结构体存储的基础知识。为了帮助大家更好地理解这个宏,我们先复习下结构体存储的基础知识。

结构体在内存中的存储

我们知道,结构体作为一个复合类型数据,它里面可以有多个成员。当我们定义一个结构体变量时,编译器要给这个变量在内存中分配存储空间。除了考虑数据类型、字节对齐因素之外,编译器会按照结构体中各个成员的顺序,在内存中分配一片连续的空间来存储它们。

struct student{int age;int num;int math;
};
int main(void)
{struct student stu = { 20, 1001, 99};printf("&stu = %p\n", &stu);printf("&stu.age =%p\n", &stu.age);printf("&stu.num =%p\n", &stu.num);printf("&stu.math =%p\n", &stu.math);
​return 0;
}

在这个程序中,我们定义一个结构体,里面有三个 int 型数据成员,我们定义一个变量,然后分别打印结构体的地址、各个成员变量的地址,运行结果如下:

&stu      = 0028FF30
&stu.age  = 0028FF30
&stu.num  = 0028FF34
&stu.math = 0028FF38

从运行结果我们可以看到,结构体中的每个成员变量,从结构体首地址开始,依次存放。每个成员变量相对于结构体首地址,都有一个固定偏移。比如 num 相对于结构体首地址偏移了4个字节。math 的存储地址,相对于结构体首地址偏移了8个字节。

计算成员变量在结构体内的偏移

一个结构体数据类型,在同一个编译环境下,各个成员相对于结构体首地址的偏移是固定的。我们可以修改一下上面的程序,当结构体的首地址为0时,结构体中的各成员地址在数值上等于结构体各成员相对于结构体首地址的偏移。

struct student{int age;int num;int math;
};
int main(void)
{printf("&age = %p\n",&((struct student*)0)->age);printf("&num = %p\n",&((struct student*)0)->num);printf("&math= %p\n",&((struct student*)0)->math);return 0;
}

在上面的程序中,我们没有直接定义结构体变量,而是将数字0,通过强制类型转换,转换为一个指向结构体类型为 student 的常量指针,然后分别打印这个常量指针指向的结构体的各成员地址。运行结果如下:

&age = 00000000
&num = 00000004
&math= 00000008

因为常量指针为0,即可以看做结构体首地址为0,所以结构体中每个成员变量的地址即为该成员相对于结构体首地址的偏移。container_of 宏的实现就是使用这个技巧来实现的。

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 = (ptr); \(type *)( (char *)__mptr - offsetof(type,member) );})

从语法角度,我们可以看到,container_of 宏的实现由一个语句表达式构成。语句表达式的值即为最后一个表达式的值:

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

最后一句的意义就是,拿结构体某个成员 member 的地址,减去这个成员在结构体 type 中的偏移,结果就是结构体 type 的首地址。因为语句表达式的值等于最后一个表达式的值,所以这个结果也是整个语句表达式的值,container_of 最后就会返回这个地址值给宏的调用者。

那如何计算结构体某个成员在结构体内的偏移呢?内核中定义了 offset 宏来实现这个功能,我们且看它的定义:

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

这个宏有两个参数,一个是结构体类型 TYPE,一个是结构体的成员 MEMBER,它使用的技巧跟我们上面计算0地址常量指针的偏移是一样的:将0强制转换为一个指向 TYPE 的结构体常量指针,然后通过这个常量指针访问成员,获取成员 MEMBER 的地址,其大小在数值上就等于 MEMBER 在结构体 TYPE 中的偏移。

因为结构体的成员数据类型可以是任意数据类型,所以为了让这个宏兼容各种数据类型。我们定义了一个临时指针变量 __mptr,该变量用来存储结构体成员 MEMBER 的地址,即存储 ptr 的值。那如何获取 ptr 指针类型呢,通过下面的方式:

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

我们知道,宏的参数 ptr 代表的是一个结构体成员变量 MEMBER 的地址,所以 ptr 的类型是一个指向 MEMBER 数据类型的指针,当我们使用临时指针变量 __mptr 来存储 ptr 的值时,必须确保 __mptr 的指针类型是一个指向 MEMBER 类型的指针变量。typeof( ((type *)0)->member )表达式使用 typeof 关键字,用来获取结构体成员 member 的数据类型,然后使用该类型,使用 typeof( ((type *)0)->member ) *__mptr 这行程序语句,就可以定义一个指向该类型的指针变量了。

还有一个需要注意的细节就是:在语句表达式的最后,因为返回的是结构体的首地址,所以数据类型还必须强制转换一下,转换为 TYPE* ,即返回一个指向 TYPE 结构体类型的指针,所以你会在最后一个表达之中看到一个强制类型转换(TYPE *)。


看见一个哥们分析container_of很好,转来留给自己看

一、#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )
1. ( (TYPE *)0 ) 将零转型为TYPE类型指针;
2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;
3. &( ( (TYPE *)0 )->MEMBER )取出数据成员的地址;
4.(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型;
巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址。
举例说明:
#include<stdio.h>
typedef struct _test
{
      char i;
      int j;
      char k;
}Test;
int main()
{
      Test *p = 0;
      printf("%p ", &(p->k));
}
答案:00000008
自 己分析:这里使用的是一个利用编译器技术的小技巧,即先求得结构成员变量在结构体中的相对于结构体的首地址的偏移地址,然后根据结构体的首地址为0,从而 得出该偏移地址就是该结构体变量在该结构体中的偏移,即:该结构体成员变量距离结构体首的距离。在offsetof()中,这个member成员的地址实 际上就是type数据结构中member成员相对于结构变量的偏移量。对于给定一个结构,offsetof(type,member)是一个常 量,list_entry()正是利用这个不变的偏移量来求得链表数据项的变量地址。

二、container_of()
container_of() 来自linuxkernel.h
内核中的注释:container_of - cast a member of a tructure 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 truct.

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

自己分析:
1.(type *)0->member为设计一个type类型的结构体,起始地址为0,编译器将结构体的起始的地址加上此结构体成员变量的偏移得到此结构体成员变 量的偏移地址,由于结构体起始地址为0,所以此结构体成员变量的偏移地址就等于其成员变量在结构体内的距离结构体开始部分的偏移量。即:& (type *)0->member就是取出其成员变量的偏移地址。而其等于其在结构体内的偏移量:即为:(size_t)(& ((type *)0)->member)经过size_t的强制类型转换后,其数值为结构体内的偏移量。该偏移量这里由offsetof()求出。
2.typeof( ( (type *)0)->member )为取出member成员的变量类型。用其定义__mptr指针.ptr为指向该成员变量的指针。__mptr为member数据类型的常量指针,其指向ptr所指向的变量处。
3.(char *)__mptr转换为字节型指针。(char *)__mptr - offsetof(type,member))用来求出结构体起始地址(为char *型指针),然后(type *)( (char *)__mptr -offsetof(type,member) )在(type *)作用下进行将字节型的结构体起始指针转换为type *型的结构体起始指针。
4.({ })这个扩展返回程序块中最后一个表达式的值。
      这就是从结构体某成员变量指针来求出该结构体的首指针。指针类型从结构体某成员变量类型转换为该结构体类型。


某度经验:container_of原来这么简单

container_of是linux内核数据结构中经常用到的宏,看了好多帖子终于明白了,其实简单的不行!^_^

方法/步骤

  1. 1

    这个宏定义在include/linux/kernel.h里面了

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

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

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

    这个宏是做什么的? 他可以根据结构体的成员变量获取所在结构体的首地址。

  2. 2

    话说有个结构体a,  地址表示为 &a,  这个结构体里面有个成员叫b

    地址表示为 &b,  现在请问 “  &b - &a  ” 表示什么含义?

    答案:偏移量,成员变量的首地址相对于结构体首地址的偏移量。

    如果  &a 碰巧又等于0 ,那么  &b - &a = &b - 0 = &b

    这样话,上面的答案就变成了:成员变量的首地址,就是偏移量

    这个说的就是 offsetof的作用

  3. 3

    现在我们有了偏移量,再拿到成员变量的实际地址,减去上面说的偏移量,不就是当前结构体的首地址了吗!?

Linux内核第一宏:container_of相关推荐

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

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

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

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

  3. 转载 linux内核 asmlinkage宏

    转载http://blog.chinaunix.net/uid-7390305-id-2057287.html 看一下/usr/include/asm/linkage.h里面的定义: #define ...

  4. linux内核第一个函数,通过内核源码看函数调用之前世今生 - 极光 - CSDN博客

    通过内核源码看函数调用之前世今生 作者:杨小华 栈(Stack):一个有序的积累或堆积 韦氏词典 对每一位孜孜不倦的程序员来说,栈已深深的烙在其脑海中,甚至已经发生变异.栈可以用来传递函数参数.存储局 ...

  5. LINUX内核第一霸

    近日有同行十万火急找到我,说遇到个极其古怪的问题,请求救援.我说什么问题,他说是一个诡异的编译错误. 我心中暗笑,编译错误也需要找老雷么.在软件世界的诸般错误中,编译错误按说是最简单的啊. 我说,发个 ...

  6. linux内核current宏介绍

    1.概述 本文主要介绍linux current宏在arm和arm64上的实现 内核版本:Linux 5.3 2.current在arm和arm64上的实现 在linux 内核中,有一个current ...

  7. 【Linux 内核】宏内核与微内核架构 ( 操作系统需要满足的要素 | 宏内核 | 微内核 | Linux 内核动态加载机制 )

    文章目录 一.操作系统需要满足的要素 二.宏内核 三.微内核 四.Linux 内核动态加载机制 一.操作系统需要满足的要素 电脑上运行的 操作系统 , 是一个 软件 ; 设备管理 : 操作系统需要 为 ...

  8. Linux内核中的container_of宏

    container_of宏用于根据已知结构体某个成员的地址得到整个结构体变量的地址,宏定义如下: #define container_of(ptr, type, member) ({ \ const ...

  9. linux内核系统调用宏SYSCALL_DEFINE

    注:源码来自内核版本3.13 在Linux的系统中,系统调用在内核的入口函数都是以 sys_xxx 命名的(如:sys_read) ,但是如果在内核源码去搜索相关函数时,很遗憾,搜索不到 sys_xx ...

最新文章

  1. 牛客网 PAT乙级(Basic Level)练习题 1023 考新郎
  2. Hadoop项目实战-用户行为分析之应用概述(二)
  3. Monkey原理初步和改良优化--Android自动化测试学习历程
  4. 基于docker在Ubuntu上搭建TensorFlow-GPU计算环境
  5. sqlserver2017 +SSMS+ VS2017+SSDT 安装要点及相关组件下载地址
  6. centos7 重置root 密码
  7. 编写函数实现员工信息录入和输出_Excel---最牛的员工档案模板,非常智能化
  8. UVA11577 Letter Frequency【文本】
  9. WPS配置工具参数 ksomisc.exe
  10. golang protobuf 动态消息获取_干货|Golang拦截器的一种实现
  11. delphi多线程TThread详解
  12. 马哥linux视频笔记,马哥linux学习笔记(示例代码)
  13. 三原色是红黄蓝对吗_为什么三原色是红黄蓝而?
  14. 汉子转五笔SQL脚本函数
  15. 星际争霸游戏中的操作心得
  16. 如何备份整个硬盘为镜像文件?
  17. FXCM福汇官网 fx-aisa.com外汇交易中,你必须了解的八种主流货币知识
  18. java使用poi给excel文件插入数据
  19. python简笔画大全_只用C++和Python,让你的简笔画实时动起来!
  20. Python类王者荣耀小游戏

热门文章

  1. 适配android P
  2. C语言软件版本号组成和定义
  3. mac移动硬盘安装linux系统安装教程,yong移动硬盘安装Linux的最新方法
  4. LWN: Linux下的打印机和扫描仪支持得怎样了?
  5. 从金蝶云星空到畅捷通T+通过接口配置打通数据
  6. java case 枚举_Java switch 枚举
  7. quartz 时间表达式转换
  8. Python学习笔记#4:快速生成二维矩阵的方法
  9. 【思想改变世界】TED演讲视频锦集
  10. java详解动态代理中的代理对象