内核中常用宏定义| container_of

前言

前两篇我们写到内核中两种C语言高级语法__attribute__, __read_mostly。本篇写内核中另外一种常用宏定义之container_of

container_of函数介绍

container_of是内核中使用最为常用的一个函数了,简单来说,它的主要作用是根据结构体中的已知的成员变量的地址,来寻求该结构体的首地址,直接看图,更容易理解。

container_of函数实现

5.10内核源码定义位置: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) ({                              \
           void *__mptr = (void *)(ptr);                                   \
           BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&   \
                           ¦!__same_type(*(ptr), void),                    \
                           ¦"pointer type mismatch in container_of()");    \
           ((type *)(__mptr - offsetof(type, member))); })

传入三个参数

  • ptr, 表示结构体中的某一成员变量
  • type, 表示结构体的类型
  • member, 表示该变量在结构体中的成员具体命名

container_of函数解析

该宏定义下面有三个语句:

  • void *__mptr = (void *)(ptr); \
  •       BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&   \
                         ¦!__same_type(*(ptr), void),                    \
                         ¦"pointer type mismatch in container_of()");    \
    
  •        ((type *)(__mptr - offsetof(type, member))); })
    

从语法角度,我们可以看到,container_of 宏的实现由一个语句表达式构成。说到container_of宏就必须说下offsetof宏语句,因为语句表达式的值即为最后一个表达式的值:

((type *)(__mptr - offsetof(type, member))

最后一句的意义是拿到结构体成员member的地址,减去这个成员在结构体type中的偏移,结果就是结构体type的首地址。那么如何获取结构体的偏移呢?内核用定义了offsetof宏来定义实现这个功能。

#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)

这个宏有两个参数,一个是结构体类型TYPE,一个是结构体成员MEMBER,就是获得0地址常量指针的偏移是一样的,如果是结构体的首地址为0的话,那么获取的变量地址既是绝对地址也是结构体内的偏移。

offsetof的使用

不用内核offsetof宏,我们求地址偏移像下面这样

struct student{
    int num;
    int age;
    int math;
};

int main(void)
{
     printf("%p\n",&((struct student *)0)->num);
    printf("%p\n",&((struct student *)0)->age);
    printf("%p\n",&((struct student *)0)->math); 
    return 0;
}

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

因为常量指针为0,即可以看做结构体首地址为0,所以结构体中每个成员变量的地址即为该成员相对于结构体首地址的偏移

在尝试代入用offsetof的宏

#define container_of(ptr, type, member) ({                              \
           void *__mptr = (void *)(ptr);   \  
           ((type *)(__mptr - offsetof(type, member))); })

#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)

struct ST
{
    int i;
    int j;
    char c;
};

int main()
{
    printf("offset i: %d\n",offsetof(struct ST,i));
    printf("offset j: %d\n",offsetof(struct ST,j));
    printf("offset c: %d\n",offsetof(struct ST,c));

    return 0;
}

运行结果如下:

这样就可以理解结构体成员是以4字节对齐进行偏移的

container_of的使用

理解了offsetof的使用,我们就可以在写个demo来理解下container_of的使用

#define container_of(ptr, type, member) ({				\
	void *__mptr = (void *)(ptr);					\
	((type *)(__mptr - offsetof(type, member))); })



#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)

struct ST
{
    int member1;
    int member2;
    int member3;
};

int main()
{
    struct ST stest;
    
    printf("&stest: 0x%x\n",&stest);
    printf("offsetof(struct ST,member3) : %d\n",offsetof(struct ST,member3));
    printf("&stest.member1: 0x%x\n",&stest.member1);
    printf("&stest.member2: 0x%x\n",&stest.member2);
    printf("&stest.member3: 0x%x\n",&stest.member3);
    printf("container_of member3: 0x%x\n", container_of(&stest.member3, struct ST, member3));

    return 0;
}

运行结果如下


可以看到stest和stest.member1的地址和container_of计算的首地址一样的

画个图可能大家更好理解

结语

相信大家对内核宏container_of的语法有所了解,如果我的文章对你有所收获的话,可以点赞关注转发给你小伙伴们


作者潘小帅, 是一名Linux底层爱好者,不定期写写技术原创文章和分享职场面试文章,徒步,旅游,看电影的爱好,喜欢我的文章可以点赞收藏+关注,感谢你的支持,微信公众号【Linux随笔录】

参考文章:

https://blog.csdn.net/xi_xix_i/article/details/134625972
https://zhuanlan.zhihu.com/p/672129384

本文由mdnice多平台发布

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值