澳门新葡亰网址下载zz linux内核中的list_entry宏

by admin on 2020年1月21日

初学linux内核代码,将学习中的一些知识点保存起来,方便以后查阅。

问题:如何通过结构中的某个变量获取结构本身的指针???

转自:

list_entry宏是用来根据list_head指针查找链表所嵌入的结构体的地址,具体查找方法如下:

关于container_of见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) );})
container_of在Linux
Kernel中的应用非常广泛,它用于获得某结构中某成员的入口地址.

本文首先介绍Linux下的经典宏定义,感受极客的智慧,然后根据该经典定义为下篇文章作铺垫。

#define list_entry(ptr, type, member)
 container_of(ptr, type, member)

关于offsetof见stddef.h中:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
TYPE是某struct的类型
0是一个假想TYPE类型struct,MEMBER是该struct中的一个成员.
由于该struct的基地址为0,
MEMBER的地址就是该成员相对与struct头地址的偏移量.
关于typeof,这是gcc的C语言扩展保留字,用于声明变量类型.
const typeof( ((type *)0->member ) *__mptr =
(ptr);意思是声明一个与member同一个类型的指针常量
*__mptr,并初始化为ptr.
(type *)( (char *)__mptr – offsetof(type,member)
);意思是__mptr的地址减去member在该struct中的偏移量得到的地址,
再转换成type型指针. 该指针就是member的入口地址了.

offsetof宏定义:

list_entry只是一层封装,实际上起作用的是container_of宏,

例一;

// 获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
#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) );})

container_of宏定义在[include/linux/kernel.h]中:

说明:获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量。
1.( (TYPE *)0 ) 将零转型为TYPE类型指针,即TYPE类型的指针的地址是0。
2.((TYPE *)0)->MEMBER    访问结构中的数据成员。
3.&( ( (TYPE *)0 )->MEMBER ) 
取出数据成员的地址。由于TYPE的地址是0,这里获取到的地址就是相对MEMBER在TYPE中的偏移。
4.(size_t)(&(((TYPE*)0)->MEMBER)) 
结果转换类型。对于32位系统而言,size_t是unsigned
int类型;对于64位系统而言,size_t是unsigned long类型。

整理一下格式:

#define container_of(ptr, type, member)     /

使用示例:

#define container_of(ptr, type, member) 

const typeof( ((type *)0)->member ) *__mptr = (ptr); /

struct student
{
 char gender;
 int id;
 int age;
 char name[20];
};

(
    {
     const typeof( ((type *)0)->member ) *__mptr = (ptr);
     (type *)( (char *)__mptr – offsetof(type,member) );  
    }
)

(type *)( (char *)__mptr – offsetof(type,member) );

int _tmain(int argc, _TCHAR* argv[])
{
 int gender_offset, id_offset, age_offset, name_offset;

可以看到,首先是使用typeof(gcc的扩展)获取到member成员的类型,用来定义一个

offsetof宏定义在[include/linux/stddef.h]中:

 gender_offset = offsetof(struct student, gender);
 id_offset = offsetof(struct student, id);
 age_offset = offsetof(struct student, age);
 name_offset = offsetof(struct student, name);

与ptr类型相同的const指针__mptr,之后将ptr赋值给__mptr,然后(char
*)__mptr

#define offsetof(type, member) ((size_t) &((type *)0)->member)
下面用一个测试程序test.c来说明

 printf(“gender_offset = %dn”, gender_offset);
 printf(“id_offset = %dn”, id_offset);
 printf(“age_offset = %dn”, age_offset);
 printf(“name_offset = %dn”, name_offset);

将指针转为char*类型,这样在后续的操作中会按照1字节来处理,offsetof宏可以用来获取

#include<stdio.h>
struct student{
    char name[20]; 
    char sex;
}stu={“zhangsan”,’m’};

 system(“pause”);
 return 0;
}
//结果:
/*
gender_offset = 0
id_offset = 4 //字节对其
age_offset = 8
name_offset = 12
*/

member在type结构中的偏移值,

main()
{
    struct student *stu_ptr;    //存储container_of宏的返回值
    int offset;            //存储offsetof宏的返回值
//下面三行代码等同于 container_of(&stu.sex,struct student, sex
)参数带入的情形

offsetof图解

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

    const typeof(((struct student*)0)->sex) *_mptr = &stu.sex;
//首先定义一个 _mptr指针, 类型为struct student结构体中sex成员的类型
//typeof 为获取(((struct
student*)0)->sex)的类型,此处此类型为char
//((struct student*)0)在offsetof处讲解

澳门新葡亰网址下载 1

首先0被转为(TYPE
*)类型,相当于0地址处的一个TYPE类型的指针,然后引用这个指针的MEMBER

    offset = (int)(&((struct student *)0)->sex);
/*((struct student*)0)为 把 0地址
强制转化为指向student结构体类型的指针
该指针从地址 0 开始的 21个字节用来存放name 与 sex(char name〔20〕与
char sex共21字节)
sex存放在第20个字节出(从0字节开始)
&((struct student *)0)->sex 取出sex地址(此处即为20)
并强制转化为整形
所以offset为20,后面的printf结果将证明这一点*/

TYPE是结构体,它代表”整体”;而MEMBER是成员,它是整体中的某一部分。

成员,最后取MEMBER成员的地址,由于TYPE类型的指针位于0地址处,取MEMBER成员的地址后就

    stu_ptr = (struct student *)((char*)_mptr – offset);
/*((char*)_mptr – offset)此处先把_mptr指针转化为字符形指针
(为什么这么做呢? 如果_mptr为整形指针 _mptr – offset 相当于减去
sizeof(int)*offset个字节)
减去 offset值 相当于 得到_mptr所在结构体的首地址(即stu的地址)
然后我们把 该地址 强制转化为 struct student类型即可正常使用了*/

container_of宏定义:

得到了MEMBER成员在TYPE类型中的地址偏移值,最后转为size_t类型。

    printf(“offsetof stu.sex = %d/n”,offset); 
    printf(“stu_ptr->name:%s/tstu_ptr->sex:%c/n”,
stu_ptr->name, stu_ptr->sex);
    return 0;
}

#define container_of(ptr, type, member) ({         
    const typeof( ((type *)0)->member ) *__mptr = (ptr);   
    (type *)( (char *)__mptr – offsetof(type,member) );})

在container_of中,(char *)__mptr – offsetof(type,member)
,即member成员的地址 – member

例二:

说明:根据”结构体(type)变量”中的”域成员变量(member)的指针(ptr)”来获取指向整个结构体变量的指针。

成员相当于type结构的地址偏移值,就能够得到type结构的首地址,最后将这一地址转为(type*)类型,

它的作用显而易见,那就是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。比如,有一个结构体变量,其定义如下:

  1. typeof( ( (type *)0)->member )    取出member成员的变量类型。
    2.const typeof( ((type *)0)->member ) *__mptr = (ptr)   
    定义变量__mptr指针,并将ptr赋值给__mptr。经过这一步,__mptr为member数据类型的常量指针,其指向ptr所指向的地址。
  2. (char *)__mptr    将__mptr转换为字节型指针。
  3. offsetof(type,member))   
    就是获取”member成员”在”结构体type”中的位置偏移。
  4. (char *)__mptr – offsetof(type,member))   
    就是用来获取”结构体type”的指针的起始地址(为char *型指针)。
    6.(type *)( (char *)__mptr – offsetof(type,member) )    就是将”char
    *类型的结构体type的指针”转换为”type *类型的结构体type的指针”。
    7.反斜杠“/”表示行连接

大功告成!

   1. struct demo_struct {
   2.     type1 member1;
   3.     type2 member2;
   4.     type3 member3;
   5.     type4 member4;
   6. };
   7.
   8. struct demo_struct demo;

更多详情见请继续阅读下一页的精彩内容

转载自:

同时,在另一个地方,获得了变量demo中的某一个域成员变量的指针,比如:

   1. type3 *memp = get_member_pointer_from_somewhere();

此时,如果需要获取指向整个结构体变量的指针,而不仅仅只是其某一个域成员变量的指针,我们就可以这么做:

   1. struct demo_struct *demop = container_of(memp, struct
demo_struct, member3);
首先,我们将container_of(memp, struct demo_struct,
type3)根据宏的定义进行展开如下:

   1. struct demo_struct *demop = ({                      /
   2.     const typeof( ((struct demo_struct *)0)->member3 )
*__mptr = (memp);    /
   3.     (struct demo_struct *)( (char *)__mptr – offsetof(struct
demo_struct, member3) );})

其中,typeof是GNU
C对标准C的扩展,它的作用是根据变量获取变量的类型。因此,上述代码中的第2行的作用是首先使用typeof获取结构体域变量member3的类型为
type3,然后定义了一个type3指针类型的临时变量__mptr,并将实际结构体变量中的域变量的指针memp的值赋给临时变量__mptr。

假设结构体变量demo在实际内存中的位置如下图所示:
     demo
 +————-+ 0xA000
 |   member1              |
 +————-+ 0xA004
 |   member2             |
 |                                |
 +————-+ 0xA010
 |   member3             |
 |                                |
 +————-+ 0xA018
 |   member4             |
 +————-+

则,在执行了上述代码的第2行之后__mptr的值即为0xA010。

再看上述代码的第3行,其中需要说明的是offsetof,它定义在include/linux/stddef.h中,其定义如下:

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

同样,我们将上述的offsetof调用展开,即为:

   1. (struct demo_struct *)( (char *)__mptr – ((size_t) &((struct
demo_struct *)0)->member3) );

可见,offsetof的实现原理就是取结构体中的域成员相对于地址0的偏移地址,也就是域成员变量相对于结构体变量首地址的偏移。

因此,offsetof(struct demo_struct,
member3)调用返回的值就是member3相对于demo变量的偏移。结合上述给出的变量地址分布图可知,offsetof(struct
demo_struct, member3)将返回0x10。

于是,由上述分析可知,此时,__mptr==0xA010,offsetof(struct
demo_struct, member3)==0x10。
因此, (char *)__mptr – ((size_t) &((struct demo_struct
*)0)->member3) == 0xA010 – 0x10 ==
0xA000,也就是结构体变量demo的首地址(如上图所示)。

由此,container_of实现了根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针的功能。

作者“programmer”

关于container_of见kernel.h中: /** * container_of – cast a member of
a structure out to the con…

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图