linux 内核宏container_of剖析


640?wx_fmt=png

640?wx_fmt=jpeg

640?wx_fmt=png

1、前面说的

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

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

android7.1/kernel/drivers/input
kernel/drivers/input$ grep -rn container_of ./|wc -l
710
android7.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里面一定要有这个成员,不能瞎搞啊

  • 返回结构体的首地址

    640?wx_fmt=png

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的类型来声明c
int 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:25
name:weiqifa
id:1
phone_num:13267
main end


640?wx_fmt=png

2_14.png

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

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

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

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

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



640?wx_fmt=jpeg




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/468834.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

目录树 删除 数据结构_数据结构:B树和B+树的插入、删除图文详解

B树1.1B树的定义B树也称B-树,它是一颗多路平衡查找树。我们描述一颗B树时需要指定它的阶数&#xff0c;阶数表示了一个结点最多有多少个孩子结点&#xff0c;一般用字母m表示阶数。当m取2时&#xff0c;就是我们常见的二叉搜索树。一颗m阶的B树定义如下&#xff1a;1)每个结点最…

MMdetection框架速成系列 第01部分:学习路线图与步骤+优先学习的两个目标检测模型代码+loss计算流程+遇到问题如何求助+Anaconda3下的安装教程(mmdet+mmdet3d)

mmdetection 学习目录1 mmdetection 学习建议1.1 mmdetection 学习的第一件事1.2 mmdetection学习路线图1.2.1 优先看的两个库1.2.2 阅读代码1.2.3 代码学习步骤1.2.4 建议优先学习的两个目标检测模型代码1.2.5 loss计算流程的攻坚克难1.3 遇到问题如何求助2 Anaconda3下的安装…

机器人循迹小车资料

前言 我记得在大学的时候&#xff0c;参加电子比赛&#xff0c;我们有一个题目是平衡小车项目&#xff0c;那个对基础要求还是比较高的&#xff0c;总结了一些平衡车机器的资料&#xff0c;希望对大家有帮助。 正文 关注公众号&#xff0c;回复【机器人资料】获取

单片机实现环形队列_稀疏数组和队列(二)

队列的介绍队列以一种先入先出(FIFO)的线性表&#xff0c;还有一种先入后出的线性表(FILO)叫做栈。教科书上有明确的定义与描述。类似于现实中排队时的队列(队尾进&#xff0c;队头出)&#xff0c;队列只在线性表两端进行操作&#xff0c;插入元素的一端称为表尾&#xff0c;删…

漫画-Linux中断子系统综述

1、中断引发的面试教训2、什么是中断&#xff1f;中断&#xff1a; &#xff08;英语&#xff1a;Interrupt&#xff09;指当出现需要时&#xff0c;CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程。即在程序运行过程中&#xff0c;系统出现了一个必须由CPU立即…

SQL强化(二) 在Oracle 中写代码

一 : 关于查询中的转换 -- 字符串转换 一 : decode 函数 转换 SELECT DECODE ( PROTYPE.PRO_TYPE_DATE, L, 长, m, 短, 默认值 )FROM PROTYPE -- 字符串转换 二 : case 转换 SELECT T1.PRO_TYPE_ID, T1.PRO_TYPE_NAME, CASE T1.PRO_TYPE_DATEWHEN S THEN 短WHEN L THEN 长EL…

vue 非es6 写法怎么按须加载_Vue源码必学指南:flow(语法检查)以及rollup(模板打包)...

点击上方蓝色字关注我们~一、前言虽然 Vue3 已经公开了代码,但是Vue3.0还处于开发阶段&#xff0c;直接上手使用Typescript是不合适的 , 对于前端的老手是不错的选择, 但是如果没有研究源码经验的开发者还是建议使用完善, 成熟的源码进行入手. 而 Vue 2.x 中使用的 flow 是一个…

漫画|创业到底有多难?

我有一群同事&#xff0c;我们一起经历了一个产品从无到有的过程&#xff0c;从开始的斗志满满到最后跟老板的不欢而散&#xff0c;其中的辛酸苦楚也许只能我们自己能体味&#xff0c;在这过程中&#xff0c;我们共同经历过的事情&#xff0c;有快乐的&#xff0c;悲伤的&#…

生成step文件_利用opencv给彦女王生成一副蒙太奇画像

大家好呀&#xff0c;前两天烈阳天道1上映了&#xff0c;不知道大家看没看呢&#xff0c;里面还有一小段彦穿越虫洞与猴哥相遇的画面&#xff0c;彦女王啊啊啊~~所以我去网上爬了二百来张我大学的风景画&#xff0c;然后找了以前存的彦女王的图片&#xff0c;生成了一幅蒙太奇画…

浪漫情人节|C语言画心型

1.前言新年第一天上班&#xff0c;先祝大家新年快乐&#xff0c;巧的是&#xff0c;今天刚好又是情人节&#xff0c;所以想了下用C实现画心形符号~过年的时候&#xff0c;跟我表哥去接新娘&#xff0c;实地看了下&#xff0c;如果一个汉字内心没有点浪漫的细胞&#xff0c;很难…

CS190.1x Scalable Machine Learning

这门课是CS100.1x的后续课&#xff0c;看课程名字就知道这门课主要讲机器学习。难度也会比上一门课大一点。如果你对这门课感兴趣&#xff0c;可以看看我这篇博客&#xff0c;如果对PySpark感兴趣&#xff0c;可以看我分析作业的博客。 Course Software Setup 这门课的环境配置…

./4.sh: No such file or directory

sh push到目标板后提示出错 #!/bin/bash echo "ladjfaosdjfoia"头bin/bash 我们要看&#xff0c;sh在哪里 130|rk3399_idpad:/data # which sh /system/bin/sh rk3399_idpad:/data # 所以上面的代码应该写成 #!/system/bin/sh echo "ladjfaosdjfoia"修…

Spring总结四:IOC和DI 注解方式

首先我们要了解注解和xml配置的区别&#xff1a; 作用一样&#xff0c;但是注解写在Bean的上方来代替我们之前在xml文件中所做的bean配置&#xff0c;也就是说我们使用了注解的方式&#xff0c;就不用再xml里面进行配置了&#xff0c;相对来说注解方式更为简便。 IOC获取对象注…

和后台如何对接_业务系统如何对接第三方服务?

在产品工作中&#xff0c;我们时常要对接第三方服务。本文作者从过往的对接项目经历中&#xff0c;提炼的关于业务系统&#xff0c;如何对接第三方服务的方法论&#xff0c;希望能对你有所帮助。随着公司业务的发展&#xff0c;我们有时会遇到&#xff0c;需要在自身业务系统中…

adb 启动某个apk

有时候需要用apk来启动某个apk adb shell am start -n com.android.launcher3/com.android.launcher3.Launcher 具体查看~ /rk3399_7in1/packages/apps/Launcher3/AndroidManifest.xml

Makefile 文件中的:obj-$(CONFIG_TEST) += test.o,这一类的是什么意思?

1、obj-$ $(CONFIG_TEST) 是一个整体&#xff0c;$(bbb)表示引用变量 bbb 比如定义 CONFIG_TESTy $(CONFIG_TEST) 就是 y obj-$(CONFIG_TEST) 就是 obj-y 又比如定义 CONFIG_TESTm $(CONFIG_TEST) 就是 m obj-$(CONFIG_TEST) 就是 obj-m obj-y foo.o 该例子告诉Kbuild在这目…

Kconfig中的“depends on”和“select”

在Kconfig文件中&#xff1a; config Adepends on Bselect C它的含义是&#xff1a;CONFIG_A配置与否&#xff0c;取决于CONFIG_B是否配置。一旦CONFIG_A配置了&#xff0c;CONFIG_C也自动配置了。 参考资料&#xff1a;“select” vs “depends” in kernel Kconfig。 所以去…

数组的合并和升序排列_leetcode 33 搜索旋转排序数组

给你一个升序排列的整数数组 nums &#xff0c;和一个整数 target 。假设按照升序排序的数组在预先未知的某个点上进行了旋转。&#xff08;例如&#xff0c;数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] &#xff09;。请你在数组中搜索 target &#xff0c;如果数组中存在…

[LeetCode] [C++] 206 Reverse Linked List 反转单项链表

题目要求 Reverse a singly linked list.LeetCode 206在线测试 问题描述 给定一个单项链表&#xff0c;将其反转后返回链表头节点。 思路分析1 可以完整的遍历一遍链表&#xff0c;将链表的每个节点的值存在数组中&#xff0c;然后反向遍历数组重新生存一个新 链表。这样做需要…

qq面板(仿版,未完待续中。。。。)---2017-04-24

主要实现效果&#xff1a; 1、点击对话&#xff0c;显示对话&#xff1b;点击联系人&#xff0c;显示联系人 2、在联系人界面&#xff1a; 实现好友列表的展开与折叠&#xff1b;&#xff08;图12&#xff09; 实现鼠标移到好友列表上的背景颜色的变化&#xff1b;&#xff08;…