内核中的 offsetofcontainer_of 宏
Posted Li-Yongjun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核中的 offsetofcontainer_of 宏相关的知识,希望对你有一定的参考价值。
回顾
上一篇我们讲到内核链表和普通链表的区别,就有小伙伴追问:内核链表是怎么通过指针域来访问数据域的呢?这篇文章我们就来解答这个问题。
问题具象化
上述问题可以具体描述为:
① 有一个结构体变量 led
struct led_dev {
char *name;
int brightness;
struct list_head link;
int flags;
};
struct led_dev led;
② 变量 led 不在当前代码的作用域内,无法直接操作其成员(可以理解为 led 变量在别的 .c 文件中,当前 .c 无法拿到这个变量直接使用)。
③ 已知 led.link 的地址,求 led 的地址是多少?
上工具
这时候,就用到了 linux 内核中提供的两个宏了
#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) );})
简单介绍下:
offsetof
宏用来计算某个成员变量在结构体中的偏移量。
container_of
宏用来在给定一个变量的结构体类型,和这个变量的某个成员的地址的条件下,计算出这个变量的地址。
offsetof 实例分析
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
struct list_head {
struct list_head *next, *prev;
};
struct led_dev {
char *name;
int brightness;
struct list_head link;
int flags;
};
int main()
{
size_t off_set = 0;
off_set = offsetof(struct led_dev, name);
printf("off_set of name = %ld\\n", off_set);
off_set = offsetof(struct led_dev, brightness);
printf("off_set of brightness = %ld\\n", off_set);
off_set = offsetof(struct led_dev, link);
printf("off_set of link = %ld\\n", off_set);
off_set = offsetof(struct led_dev, flags);
printf("off_set of flags = %ld\\n", off_set);
return 0;
}
运行输出
off_set of name = 0
off_set of brightness = 8
off_set of link = 16
off_set of flags = 32
- name 是结构体的第一个元素,所以它的偏移量是 0
- brightness 的偏移量是前面元素的大小,即一个指针变量的大小,我使用的是 64 位的机器,所以一个指针大小为 8 字节,所以 brightness 的偏移量为 8
- 同理可求得 link 的偏移量为 16,flags 的偏移量为 32
container_of 实例解析
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure 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 struct.
*
*/
#define container_of(ptr, type, member) ({ \\
const typeof( ((type *)0)->member ) *__mptr = (ptr); \\
(type *)( (char *)__mptr - offsetof(type,member) );})
struct list_head {
struct list_head *next, *prev;
};
struct led_dev {
char *name;
int brightness;
struct list_head link;
int flags;
};
struct led_dev led = {
"green",
1,
{NULL, NULL},
0xFF,
};
int main()
{
printf("led address : %p\\n", &led); // 打印 led 的地址,这里仅仅打印,用来和后面计算的结果进行正确性对比,因为场景是 led 地址我们是不知道的。
printf("led.link address : %p\\n", &led.link); // 假设通过链表拿到了下一个节点的指针域的地址:&led.link (隐含信息 &led.link = &led.link.next)
// 下面就想办法通过这个地址来推出节点的首地址,进而也就知道了所有成员的地址
struct led_dev *ptr = container_of(&(led.link), struct led_dev, link);
printf("ptr address : %p\\n", ptr); // 检查 ptr 的地址是否和 led 的地址相同,想同的话,就表示我们成功拿到了 led 的地址。后面就能使用 ptr 来访问结构体中的其它成员变量了
printf("ptr->name = %s\\n", ptr->name);
printf("ptr->brightness = %d\\n", ptr->brightness);
printf("ptr->flags = 0x%x\\n", ptr->flags);
return 0;
}
运行结果
led address : 0x55745d380020
led.link address : 0x55745d380030
ptr address : 0x55745d380020
ptr->name = green
ptr->brightness = 1
ptr->flags = 0xff
- 我们定义了一个 led 变量,不过这里我们假设 led 变量是在别处定义的,我们拿不到。main 函数第一句仅仅是打印其地址,用作和后面求得的 led 变量地址做正确性验证。
- 我们已知 led 的一个成员变量的地址,即 led.link 的地址
- 我们的目的是通过 led.link 的地址求 led 的地址
struct led_dev *ptr = container_of(&(led.link), struct led_dev, link);
可以看到,上述这句就是得到 led 地址的语句,入参是:led.link 的地址,led 的结构体类型,成员
返回结果:led 变量的地址。
从运行结果也可以看到,我们已知 led.link 的地址为 0x55745d380030,求得 led 的地址为 0x55745d380020,和代码一开始打印的 led 的地址相同,故结果正确。
offsetof 原理
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
对于这个宏,我们逐层去理解
1. 0
2. (TYPE *)0
3. ((TYPE *)0)->MEMBER
4. &((TYPE *)0)->MEMBER
5. (size_t) &((TYPE *)0)->MEMBER
1、内存地址开始于 0
2、将 0 转换成 TYPE 类型的结构体指针,换句话说就是让编译器认为这个结构体开始于程序段的起始位置
3、引用结构体中的 MEMBER 成员
4、取地址
5、将取到的地址强制转换为 size_t 类型
因为这个结构体的起始地址被指定为 0,所以取到的结构体成员的绝对地址(当转换为数字)就是这个成员在结构体中的偏移量。
这个代码之所以没有风险,是因为这里没有对任何内存进行写操作,甚至没有读操作。只是操作了指向这些位置的指针,而指针一般存储在机器寄存器或是通常的本地堆栈。
container_of 原理
#define container_of(ptr, type, member) ({ \\
const typeof( ((type *)0)->member ) *__mptr = (ptr); \\
(type *)( (char *)__mptr - offsetof(type,member) );})
同样进行逐层分析
1. 0
2. (type *)0
3. ((type *)0)->member
4. typeof( ((type *)0)->member )
5. typeof( ((type *)0)->member ) *__mptr
6. typeof( ((type *)0)->member ) *__mptr = (ptr);
7. (type *)( (char *)__mptr - offsetof(type,member) );
1、2、3、 同 offsetof
4、typeof 获取变量类型
5、使用获取到的类型定义一个临时指针变量 __mptr
6、将传入的成员变量地址赋值给 __mptr
7、用 __mptr 减去成员在结构体中的偏移量,就得到了结构体变量的地址
参考
以上是关于内核中的 offsetofcontainer_of 宏的主要内容,如果未能解决你的问题,请参考以下文章
Linux中的两个宏(offsetofcontainer_of)
Binder 机制分析 Android 内核源码中的 Binder 驱动源码 binder.c ( googlesource 中的 Android 内核源码 | 内核源码下载 )