Linux内核如何计算链表的位置

参考文章:http://blog.csdn.net/yunsongice/article/details/5471096

如果需要有某种数据结构的队列,就在这种数据结构定义内部放上一个list_head数据结构。例如,建立数据结构foo链表的方式是,在foo的定义中,嵌入了一个list_head成员list。这里foo就是所指的"宿主":

    typedef struct foo {
        …
        struct list_head list;
        …
    };

    但是,如何通过list_head成员访问到宿主结构项呢?毕竟list_head不过是个连接件,而我们需要的是一个"特定"的数据结构链表。先介绍几个基本宏:offsetof、typeof、containerof。

    -------/linux/stddef.h----------------
    #define __compiler_offsetof(a,b) __builtin_offsetof(a,b)
而__builtin_offsetof()宏就是在编译器中已经设计好了的函数,直接调用即可。
    -------------------------------
    #undef offsetof //取消先前的任何定义,可以保证下面的定义生效
    #ifdef __compiler_offsetof
    #define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
    #else
    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    #endif

解释:一共4步
1. ( (TYPE *)0 ) 0地址强制 "转换" 为 TYPE结构的指针;
2. ((TYPE *)0)->MEMBER 访问结构中的数据成员;
3. &( ( (TYPE *)0 )->MEMBER)取出数据成员的地址;
4. (size_t)(&(((TYPE*)0)->MEMBER))结果转换类型.巧妙之处在于将0转换成(TYPE*),结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;

举例说明:
#include<stdio.h>
typedef struct _test
{
    char i;
    int j;
    char k;
}Test;
int main()
{
    Test *p = 0;
    printf("%p/n", &(p->k));
}
这里使用的是一个利用编译器技术的小技巧(编译器自动算出成员的偏移量),即先求得结构成员变量在结构体中的相对于结构体的首地址的偏移地址,然后根据结构体的首地址为0,从而得出该偏移地址就是该结构体变量在该结构体中的偏移,即:该结构体成员变量距离结构体首的距离。在offsetof()中,这个member成员的地址实际上就是type数据结构中member成员相对于结构变量的偏移量。对于给定一个结构,offsetof(type,member)是一个常量,list_entry()正是利用这个不变的偏移量来求得链表数据项的变量地址。

---------------------typeof()--------------------
unsigned int i;
typeof(i) x;
x=100;
printf("x:%d/n",x);
typeof() 是 gcc 的扩展,和 sizeof() 类似。
------------------------
在 container_of 宏中,它用来给 typeof() 提供参数,以获得 member 成员的数据类型;

---------------container_of()--------------------
container_of() 来自/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) );})
自己分析:

    1.(type*)0->member为设计一个type类型的结构体,起始地址为0,编译器将结构体的起始的地址加上此结构体成员变量的偏移得到此结构体成员变量的地址,由于结构体起始地址为0,所以此结构体成员变量的偏移地址就等于其成员变量在结构体内距离结构体开始部分的偏移量。即:&(type*)0->member就是取出其成员变量的偏移地址。而其等于其在结构体内的偏移量:即为:(size_t)(& ((type*)0)->member)经过size_t的强制类型转换后,其数值为结构体内的偏移量。该偏移量这里由offsetof()求出。

    2.typeof( ( (type *)0)->member )为取出member成员的变量类型。用其定义__mptr指针;ptr为指向该成员变量的指针。__mptr为member数据类型的常量指针,其指向ptr所指向的变量处。

    3.(char*)__mptr转换为字节型指针。(char *)__mptr - offsetof(type,member))用来求出结构体起始地址(为char *型指针),然后(type *)( (char *)__mptr -offsetof(type,member) )在(type *)作用下进行将字节型的结构体起始指针转换为type *型的结构体起始指针。

    这就是从结构体某成员变量指针来求出该结构体的首指针。指针类型从结构体某成员变量类型转换为该结构体类型。

    介绍了上面的几种基本宏后,对list_entry的理解就容易了。list_entry()宏,获取当前list_head链表节点所在的宿主结构项。第一个参数为当前list_head节点的指针,即指向宿主结构项的list_head成员。第二个参数是宿主数据结构的定义类型。第三个参数为宿主结构类型定义中list_head成员名。

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

扩展替换即为:

#define list_entry(ptr, type, member) /
        ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

例:page = list_entry(curr, struct page, list)
则:page = ((struct page *)((char *)(curr)-(unsigned long)(&((struct page *)0)->list)))
   curr是page结构内部成分list的地址,而我们要找的是list所处page结构本身的地址,所以要从curr减去一个位移量,即成份list在page内部的位移量:&((struct page*)0)->list表示当结构page正好在地址0上时list的(绝对)地址。

Linux内核如何计算链表的位置_第1张图片

获取宿主对象指针的原理如上图所示。我们考虑list_head类型成员member相对于宿主结构(类型为type)起始地址的偏移量。对于所有该类型的宿主对象,这个偏移量是固定的。并且可以在假设宿主对象地址值为0,通过返回member成员的地址获得,即等于(unsignedlong)(&((type*)0)->member)。这样,将当前宿主对象的"连接件"地址(ptr)减去这个偏移量,得到宿主对象地址,再将它转换为宿主数据结构类型的指针。

    需要重申的是,链表头没有被嵌入到宿主对象中,因此对链表头执行宿主对象指针获取操作是没有意义的。



你可能感兴趣的:(数据结构,list,struct,compiler,编译器,linux内核)