Linux 内核 container_of 宏详解

news2024/9/20 16:54:15

目录

前言

1、container_of 宏介绍

2、container_of 宏的使用示例

3、container_of 宏实现原理分析

3.1 结构体在内存中的存储 

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

3.3 container_of 宏的原理实现

4、总结


前言

本章内容会涉及到的基础知识有 typeof关键字 和 语句表达式。如果大家还不知道它们是什么,有什么作用,建议大家先阅读一下 typeof 关键字的作用 和 语句表达式威力 这两篇文章,巩固一下相关的基础知识。

1、container_of 宏介绍

到这里假设大家都懂了 typeof 和 语句表达式,那么我们就开始一睹 Linux 内核第一宏 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) );})

作为 Linux 内核第一个宏,绝对是实至名归的,看看它外表斯文而内藏八块腹肌的身形,就知道它是不好惹的。宏中有宏,作为 GNU C 高端扩展特性的综合运用,那么它有什么作用呢?它的主要作用是:根据结构体某一成员的地址,获取这个结构体的首地址。根据宏定义,可知这个宏有三个参数:

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

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

2、container_of 宏的使用示例

这个宏在内核中非常重要。在内核中会经常有这样的需求:我们传递给某个函数的参数是某个结构体的成员变量,然后在这个函数中,可能还会用到此结构体的其它成员变量,那么这个时候怎么办呢?我们可以使用 container_of 先通过结构体某一成员的访问找到这个结构体的首地址,然后就可以访问其它成员变量了。

struct _box_t
{
    double length;   // 盒子的长度
    double breadth;  // 盒子的宽度
    double height;   // 盒子的高度
};

int main(void)
{
    struct _box_t box = {30.0, 20.0, 10.0};
    struct _box_t *p_box = NULL;

    p_box = container_of(&box.height, struct _box_t, height);
    printf("%p\n", p_box);
    printf("length: %f\n", p_box->length);
    printf("breadth: %f\n", p_box->breadth);

    return 0;
}

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

3、container_of 宏实现原理分析

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

3.1 结构体在内存中的存储 

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

struct _box_t
{
    double length;   // 盒子的长度
    double breadth;  // 盒子的宽度
    double height;   // 盒子的高度
};

int main(void)
{
    struct _box_t box = {30.0, 20.0, 10.0};
    printf("&box = %p\n", &box);
    printf("&box.length = %p\n", &box.length);
    printf("&box.breadth = %p\n", &box.breadth);
    printf("&box.height = %p\n", &box.height);

    return 0;
}

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

&box         = 2b6c3dd0
&box.length  = 2b6c3dd0
&box.breadth = 2b6c3dd8
&box.height  = 2b6c3de0

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

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

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

struct _box_t
{
    double length;   // 盒子的长度
    double breadth;  // 盒子的宽度
    double height;   // 盒子的高度
};

int main(void)
{
    printf("&length = %p\n", &((struct _box_t*)0)->length);
    printf("&breadth = %p\n", &((struct _box_t*)0)->breadth);
    printf("&height = %p\n", &((struct _box_t*)0)->height);

    return 0;
}

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

&length  = ox0
&breadth = 0x8
&height  = 0x10

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

3.3 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) );})

语句表达式的值即为最后一个表达式的值

(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 的指针类型也是一个指向 MEMBER 类型的指针变量,通过 typeof( ((type *)0)->member ) 表达式,使用 typeof 关键字来获取结构体成员 member 的数据类型,然后使用 typeof( ((type *)0)->member ) *__mptr 就可以定义一个指向该类型的指针变量了。 

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

4、总结

  • 通过对 container_of 宏的整体分析后,这个过程到底对我们有什么启发呢?
  • 对于任何一个复杂的技术,我们都可以把它由上而下的逐步分解,然后运用所学的基础知识一点一点剖析:先进行小模块分析,然后再进行综合分析。
  • 比如  container_of 宏的定义,就运用了结构体的存储、语句表达式、typeof 等知识点。
  • 当我们掌握了这些基础知识,并且有了分析方法,以后在内核中再遇到这样类似的宏,我们就可以自信从容地去自己分析,而不必总是依赖网上大海捞针式的搜索了。
  • 这就是你的核心竞争力,也是你超越其他工程师、脱颖而出的机会。


 

 

 

 

 

 

 

 

 

 

 


 


 



 

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

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

相关文章

django-博客(一)

一、 1、环境:pycharm,python3.6,django3,mysql8.0 2、创建项目 3、把html和css样式那些导入到文件夹中,​​​​​​然后配置这些文件夹的路径,再添加首页视图。 改成反向解析 python manage.py runserv…

ElasticSearch 学习笔记总结(三)

文章目录一、ES 相关名词 专业介绍二、ES 系统架构三、ES 创建分片副本 和 elasticsearch-head插件四、ES 故障转移五、ES 应对故障六、ES 路由计算 和 分片控制七、ES集群 数据写流程八、ES集群 数据读流程九、ES集群 更新流程 和 批量操作十、ES 相关重要 概念 和 名词十一、…

熵,线性规划,半监督自监督聚类打标签

1.熵 信息熵是消除不确定性所需信息量的度量。 信息熵就是信息的不确定程度,信息熵越小,信息越确定。 对象的信息熵是正比于它的概率的负对数的,也就是 I©−log(pc) 其中n为事件的所有可能性。 为什么使用交叉熵?在机器学习…

分析设备故障时间和次数,打破生产瓶颈?包在虹科身上

前言 生产设备的稳定性和可靠性是保证企业正常生产的重要条件之一,设备故障的频发严重影响企业的正常生产,那么如何分析设备故障时间和次数,查找设备故障原因,协助企业打破生产瓶颈,有效地实现生产目标呢?…

面试总结——react生命周期

react生命周期总结 生命周期主要分为以下几个阶段: Mounting:创建虚拟DOM,渲染UI(初始化)Updating:更新虚拟DOM,重新渲染UI;(更新)UnMounting:删除虚拟DOM,移除UI;(销毁) 生命周期…

docker-compose安装kafka和php简单测试

docker-compose.yml内容: version: 3.1 services: zookeeper: container_name: zookeeper image: zookeeper:3.6 ports: - 2181:2181 kafka: image: wurstmeister/kafka container_name: kafka depends_on: - zookeeper …

java基础系列(八) synchronized关键字

一. 认识synchronized 先看一下如下Demo public class Test {public static void main(String[] args) {Count obj new Count();//only one objectMyThread1 t1 new MyThread1(obj);MyThread2 t2 new MyThread2(obj);t1.start();t2.start();} }class MyThread2 extends Th…

面试题1-JAVA的执行流程

JAVA的执行流程 分两步,1.翻译 2执行 Java属于两种类型: 编译型和解释型 编译型 ​ 概念: 把源代码翻译成.class文件 >意思是: 把程序员的源代码翻译成虚拟电脑能看得懂的代码 解释型 ​ 概念: 把.class文件进行解析执行 >把翻译后的代码交给虚拟电脑执行指令 执行流程…

揭开Salesforce Accredited Professional证书神秘面纱,到底含金量有多高?

自从Salesforce宣布Accredited Professional计划以来,已经过去了将近两年。这些认证旨在证明备考者在Salesforce平台特定领域的广泛知识,并且仅供Salesforce合作伙伴使用。Accredited Professional中有近40项Salesforce认证,涵盖平台、销售、…

学校机房4大安全隐患,赶紧自查

无论是企业、医院、政府机构还是学校,都有机房。传统机房出现事故时无法及时发现和处理,导致影响范围大,损失严重。 随着信息技术的不断发展,各行业信息化程度不断提升,配套的数据中心机房日益增多,为业务信…

LeetCode 147. 对链表进行插入排序 | C/C++版

LeetCode 147. 对链表进行插入排序 | C语言版LeetCode 147. 对链表进行插入排序题目描述解题思路思路一:使用栈代码实现运行结果参考文章:思路二:减少遍历节点数代码实现运行结果参考文章:[]()LeetCode 147. 对链表进行插入排序 …

Sharding Sphere学习

一、基本概念 1.什么是Sharding Sphere 2.分库分表3.分库分表的方式 4.分库分表应用和问题 5.功能 5.1数据分片 —核心概念 —使用限制 5.2分布式事务 —核心概念 —使用限制 5.3读写分离 —核心概念 —使用限制 5.4高可用 —核心概念 —使用限制 5.5数据库网关 —核心概念…

shield分析

本文仅供学习交流,只提供关键思路不会给出完整代码,严禁用于非法用途,若有侵权请联系我删除!技术交流合作请私信! 熟练打开Fiddler设置好手机代理,摆弄半天一直抓不到包,应该是小红书监测到了F…

对 Dom 树的理解

什么是 DOM 从网络传给渲染引擎的 HTML 文件字节流是无法直接被渲染引擎理解的,所以要将其转化为渲染引擎能够理解的内部结构,这个结构就是 DOM。 DOM 提供了对 HTML 文档结构化的表述。 在渲染引擎中,DOM 有三个层面的作用: …

攻略 | 6步帮助中小微企业开拓东盟机电产品市场

如何帮助中小微外贸企业在东盟市场拓展机电产品一般贸易?随着全球化的发展,越来越多的中小微外贸企业开始涉足国际贸易。对于机电产品行业而言,东盟市场是一个非常重要的出口目的地。本文将为您介绍如何帮助中小微外贸企业在东盟市场拓展机电…

用一个例子告诉你 怎样使用Spark中RDD的算子

目录 1. 前言 1.1 操作分类 1.2 语法知识 2. transformations 2.1 map 2.2 mapPartitions 2.3 flatMap 2.4 glom 2.5 groupBy 2.6 filter 2.7 sample 2.8 distinct 2.9 coalesce 2.10 repartition 2.11 sortBy 2.12 partitionBy 2.13 reduceByKey 2.14 gro…

从软件的角度看待PCI和PCIE(一)

1.最容易访问的设备是什么? 是内存! 要读写内存,知道它的地址就可以了,不需要什么驱动程序; volatile unsigned int *p 0xffff8811; unsigned int val; *p val; val *p;只有内存能这样简单、方便的使用吗&#xf…

python中的序列——笔记

一、介绍 ABC语言时一个致力于为初学者设计编程环境的长达十年的研究项目。 Python也从ABC那里继承了用统一的风格去处理序列数据这一特点。不管是哪种数据结构,字符串、列表、字节序列、数组、XML元素,抑或是数据库查询结果,它们都共用一套…

SD卡损坏了?储存卡恢复数据就靠这3个方法

作为一种方便的储存设备,SD卡在我们的日常生活中使用非常广泛。但是,有时候我们可能会遇到SD卡损坏的情况,这时候里面存储的数据就会受到影响。SD卡里面保存着我们很多重要的数据,有些还是工作必须要使用的。 如果您遇到了这种情…

百度CTO王海峰:深度学习平台+大模型,夯实产业智能化基座

2月27日,中国人工智能学会首届智能融合产业论坛在成都顺利举办。本届论坛由中国人工智能学会(CAAI)主办,中国人工智能学会智能融合专委会、百度公司、深度学习技术及应用国家工程研究中心和电子科技大学联合承办。中国工程院多名院…