hello, 大家好,今天和大家一起学习 Linux 内核中常见的两个宏 offsetof 和 container_of。对于初学者,很容易弄懵逼。
offsetof 宏
定义:include/linux/stddef.h
功能:给定一个TYPE结构和其成员,获取其成员相对于首地址的偏移。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
分析: ((size_t) &((TYPE *)0)->MEMBER),假设TYPE 数据类型如下:
typedef struct student {
char name[32];
int age;
};
第一步:我们先看最里层((TYPE*)0)。实际上是将0地址强转为TYPE结构的指针。指向一个TYPE类型的结构体变量,这里就是 student 类型。
第二步:((TYPE*)0)->MEMBER。(TYPE*)0 表示一个TYPE类型的结构体指针。通过指针来访问这个结构体变量的MEMBER元素。student->age
第三步:&((TYPE*)0)->MEMBER 等效于 &(((TYPE*)0)->MEMBER) - &((TYPE *)0)
# 这里取巧 利用了 0地址的结构体指针变量。
&((TYPE*)0)->MEMBER = &(((TYPE*)0)->MEMBER) - &((TYPE *)0)
代入student,去理解。offsetof(student, age),实际上就是获取相对于student首地址的偏移。
实验验证:
#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef struct _student1 {
char name[32];
int age;
} student1_t;
typedef struct _student2 {
char name[32];
int age;
float score;
} student2_t;
int main()
{
size_t addr = 0;
addr = offsetof(student1_t, age);
printf("student1_t addr = %d\n", addr);
addr = offsetof(student2_t, score);
printf("student2_t addr = %d\n", addr);
return 0;
}
可以看到,addr输出的值分别对应age 和 score 相对于其结构体首地址的偏移。
思考一个问题,这样做的好处是什么呢?
container_of 宏
在看另外一个宏container_of 宏。
定义:include/linux/kernel.h
功能:给定一个结构体成员的地址,及成员名称,获取其结构体指针(成员所有者)首地址
/**
* 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) );})
分析containr_of 宏,我们还是以student结构体为例代入:
第一步:先看第一段。
const typeof( ((type *)0)->member ) *__mptr = (ptr);
// 等价于
const typeof(A) *__mptr = (ptr);
继续拆开:A = ((type *)0)->member,和上文offsetof 分析一样。这里指的是student->age.然后作为参数传给typeof。
然后typeof(A) *__mptr = ptr; 等价于 A *__mptr = ptr;
注:typeof() 是gcc的扩展宏,给定一个参数或者变量名,能够自动推导数据类型。此时ptr 就指向 student->age地址处。
第二步:再看第二段。
(type *)( (char *)__mptr - offsetof(type,member) );
#等价于
(type *) (&A - offset)
根据 typeof 推算出 member 所在地址,然后减去 member相对于 type 的偏移,得到结构体变量的首地址。
验证:
#include <stdio.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) );})
typedef struct _student1 {
char name[32];
int age;
} student1_t;
typedef struct _student2 {
char name[32];
int age;
float score;
} student2_t;
int main()
{
student1_t s1[2] = {
{
.name = "s1",
.age = 18,
},
{
.name = "s2",
.age = 20,
},
};
student1_t *ptr = NULL;
ptr = container_of(&s1[1].age, student1_t, age);
printf("s1 addr = %p \n", &s1[0]);
printf("ptr addr = %p \n", ptr);
printf("s2 addr = %p, s2.age = %d \n", &s1[1], s1[1].age);
printf("ptr addr = %p, ptr.age = %d \n", ptr, ptr->age);
return 0;
}
可以看到根据s1[1].age 的地址及成员名称,即可获取到改结构的首地址。即ptr = &s1[1]。
总结
- offsetof:对于给定的一个结构的成员,获取其成员相对于首地址的偏移。
- container_of:对于给定结构成员的地址,返回其结构体指针(成员所有者)首地址。
内核里面为什么要用到这两个宏呢?事实上在内核链表里面大量用到了这两个宏。
struct list_head {
struct list_head *next, *prev;
};
linux 内核链表结构信息非常简单,只包含list_head信息,我们正式通过内核链表中的list_head 指针,获取所有者的节点信息。
list_entry(ptr,type,member)
- list_entry 就是 container_of 宏。
- ptr是指向该数据中list_head成员的指针,也就是存储在链表中的地址值。
- type是数据项的类型。
- member则是数据项类型定义中list_head成员的变量名。