[系统技巧] Linux 内核 container_of 宏详解
作者:精品下载站 日期:2023-10-11 23:50:37 浏览:63 分类:玩电脑
前言
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; }
在这个程序中,我们定义一个结构体,里面有三个 double 型数据成员,我们定义一个变量,然后分别打印结构体的地址、各个成员变量的地址,运行结果如下:
&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 等知识点。
当我们掌握了这些基础知识,并且有了分析方法,以后在内核中再遇到这样类似的宏,我们就可以自信从容地去自己分析,而不必总是依赖网上大海捞针式的搜索了。
这就是你的核心竞争力,也是你超越其他工程师、脱颖而出的机会。
猜你还喜欢
- 03-30 [玩转系统] 如何用批处理实现关机,注销,重启和锁定计算机
- 02-14 [系统故障] Win10下报错:该文件没有与之关联的应用来执行该操作
- 01-07 [系统问题] Win10--解决锁屏后会断网的问题
- 01-02 [系统技巧] Windows系统如何关闭防火墙保姆式教程,超详细
- 12-15 [玩转系统] 如何在 Windows 10 和 11 上允许多个 RDP 会话
- 12-15 [玩转系统] 查找 Exchange/Microsoft 365 中不活动(未使用)的通讯组列表
- 12-15 [玩转系统] 如何在 Windows 上安装远程服务器管理工具 (RSAT)
- 12-15 [玩转系统] 如何在 Windows 上重置组策略设置
- 12-15 [玩转系统] 如何获取计算机上的本地管理员列表?
- 12-15 [玩转系统] 在 Visual Studio Code 中连接到 MS SQL Server 数据库
- 12-15 [玩转系统] 如何降级 Windows Server 版本或许可证
- 12-15 [玩转系统] 如何允许非管理员用户在 Windows 中启动/停止服务
取消回复欢迎 你 发表评论:
- 精品推荐!
-
- 最新文章
- 热门文章
- 热评文章
[短剧] 2025年06月03日 精选+付费短剧推荐25部
[软件合集] 25年6月3日 精选软件44个
[短剧合集] 2025年06月2日 精选+付费短剧推荐39部
[软件合集] 25年6月2日 精选软件18个
[软件合集] 25年6月1日 精选软件15个
[短剧合集] 2025年06月1日 精选+付费短剧推荐59部
[短剧] 2025年05月31日 精选+付费短剧推荐58部
[软件合集] 25年5月31日 精选软件66个
[电影] 黄沙漫天(2025) 4K.EDRMAX.杜比全景声 / 4K杜比视界/杜比全景声
[风口福利] 短视频红利新风口!炬焰创作者平台重磅激励来袭
[剧集] [央视][笑傲江湖][2001][DVD-RMVB][高清][40集全]李亚鹏、许晴、苗乙乙
[电视剧] 欢乐颂.5部全 (2016-2024)
[电视剧] [突围] [45集全] [WEB-MP4/每集1.5GB] [国语/内嵌中文字幕] [4K-2160P] [无水印]
[影视] 【稀有资源】香港老片 艺坛照妖镜之96应召名册 (1996)
[剧集] 神经风云(2023)(完结).4K
[剧集] [BT] [TVB] [黑夜彩虹(2003)] [全21集] [粤语中字] [TV-RMVB]
[资源] B站充电视频合集,包含多位重量级up主,全是大佬真金白银买来的~【99GB】
[影视] 内地绝版高清录像带 [mpg]
[书籍] 古今奇书禁书三教九流资料大合集 猎奇必备珍藏资源PDF版 1.14G
[美图] 2W美女个美女小姐姐,饱眼福
[电视剧] [突围] [45集全] [WEB-MP4/每集1.5GB] [国语/内嵌中文字幕] [4K-2160P] [无水印]
[剧集] [央视][笑傲江湖][2001][DVD-RMVB][高清][40集全]李亚鹏、许晴、苗乙乙
[电影] 美国队长4 4K原盘REMUX 杜比视界 内封简繁英双语字幕 49G
[电影] 死神来了(1-6)大合集!
[软件合集] 25年05月13日 精选软件16个
[精品软件] 25年05月15日 精选软件18个
[绝版资源] 南与北 第1-2季 合集 North and South (1985) /美国/豆瓣: 8.8[1080P][中文字幕]
[软件] 25年05月14日 精选软件57个
[短剧] 2025年05月14日 精选+付费短剧推荐39部
[短剧] 2025年05月15日 精选+付费短剧推荐36部
- 最新评论
-
- 热门tag