Linux内核中的offsetof和container_of


原文链接: Linux内核中的offsetof和container_of

关于list_for_each_entry相关函数 - 简书

链表是内核最经典的数据结构之一,说到链表就不得不提及内核最经典 (没有之一) 的宏container_of

先看看代码:

#include <stddef.h>
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)

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

几个关键内容分析一下:

typeof

typeof是 GNU 对 C 新增的一个扩展关键字,用于获取一个对象的类型,在很多时候我们处理的对象通常是一个指针,而此时如果想知道指针所指向的对象的类型,typeof 就派上用场了,详见 GNU 的官方文档:http://gcc.gnu.org/onlinedocs/gcc/Typeof.html

offsetof

offsetof是返回结构体TYPEMEMBER成员相对于结构体首地址的偏移量,以字节为单位。

(TYPE *)0, 将 0 强制转换为TYPE型指针,记 p = (TYPE *)0p是指向TYPE的指针,它的值是 0。那么 p->MEMBER 就是 MEMBER 这个元素了,而&(p->MEMBER)就是MEMBER的地址,编译器认为 0 是一个有效的地址,则基地址为 0,这样就巧妙的转化为了TYPE中的偏移量。再把结果强制转换为size_t型的就 OK 了。

container_of

container_of的主要作用是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。

创建一个类型为const typeof( ((type *)0)->member ) *,即类型为type结构的member域所对应的对象类型的常指针__mptr,并用ptr初始化之,这样一来,__mptr就指向了某一个typemember域。因为数据结构是顺序存储的,此时如果知道membertype结构中的相对偏移,那么用__mptr减去此偏移便是ptr所属的type的地址。

container_of 最典型的应用就是根据链表节点获取链表上的元素对象。

list_for_each_entry解析
双向链表及链表头:

建立一个双向链表通常有一个独立的用于管理链表的链表头,链表头一般是不含有实体数据的,必须用INIT_LIST_HEAD()进行初始化,表头建立以后,就可以将带有数据结构的实体链表成员加入到链。

INIT_LIST_HEAD (&nphy_dev_list);

定义:

#define list_for_each_entry(pos, head, member)

for (pos = list_entry((head)->next, typeof(*pos), member);    \
     &pos->member != (head);     \
     pos = list_entry(pos->member.next, typeof(*pos), member))

它实际上是一个 for 循环,利用传入的 pos 作为循环变量,从表头 head 开始,逐项向后(next 方向)移动 pos,直至又回head.

static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
{
    struct i2c_dev *i2c_dev;

    spin_lock(&i2c_dev_list_lock);
    
    //这个i2c_dev_list是链表串起来
    list_for_each_entry(i2c_dev, &i2c_dev_list, list) {
        if (i2c_dev->adap->nr == index)
            goto found;
    }
    i2c_dev = NULL;
found:
    spin_unlock(&i2c_dev_list_lock);
    return i2c_dev;
}

我们将for循环分解为一下三点:

  1. for循环初始化 pos = list_entry((head)->next, typeof(*pos), member);
  2. for循环执行条件 &pos->member != (head);
  3. 每循环一次执行 pos = list_entry(pos->member.next, typeof(*pos), member))

typeof()是取变量的类型,这里是取指针pos所指向数据的类型。

先看第一个:

#define list_entry(ptr, type, member)

container_of(ptr, type, member)

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

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

讲讲container_of:作用:根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。

所以(type *)0)就是将0强转为一个地址,这个地址(0x0000)指向的是类型type的数据。当然,这里是一个技巧,并不是真的在地址0x0000存放了我们的数据。

((type *)0)->member的作用,这里的‘->’很显然是通过指针指取结构体成员的操作。指针就是刚才通过0强转的地址。所以也就是相当于地址0x0000 是结构体类型type的首地址,通过->’取其中的成员变量member。

typeof( ((type *)0)->member ) *mptr = (ptr):知道member成员的类型,定义一个指针变量mptr,指向的类型是member的类型,其初始化为ptr的值

offsetof(type,member):求出member在结构体中的偏移量

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
根据优先级的顺序,最里面的小括号优先级最高,TYPE *将整型常量0强制转换为TYPE型的指针,且这个指针指向的地址为0,也就是将地址0开始的一块存储空间映射为TYPE型的对象,接下来再对结构体中MEMBER成员进行取址,而整个TYPE结构体的首地址是0,这里获得的地址就是MEMBER成员在TYPE中的相对偏移量。再将这个偏移量强制转换成size_t型数据(无符号整型)。

(char *)mptr - offsetof(type,member)就是mptr - offset,即member类型的指针减去member在结构体中的偏移量。

小结:通过上面的分析,和定义出的注释,container_of的作用很明显了 -- 获得结构体的地址。那么我们需要给他提供三个参数,分别是:ptr:member成员的指针 type:结构体类型 member:成员member的名字。

这样我们就能通过container_of(ptr, type, member)的返回值,得到结构体的地址。

`