对Linux内核链表的list_entry宏的理解

Linux内核方式与众不同,它不是将数据结构塞入链表,而是将链表节点塞入数据结构!

这一点一定要时刻铭记于心。

 

链表节点定义:

struct list_head {

    struct list_head *next, *prev;

};

 

链表的初始化可以由两个宏来静态的实现:

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \

    struct list_head name = LIST_HEAD_INIT(name)

 

当然也有用函数动态实现的形式:

/* INIT_LIST_HEAD - Initialize a list_head structure

 * @list: list_head structure to be initialized.

 * Initializes the list_head to point to itself.  If it is a list header,

 * the result is an empty list. */

static inline void INIT_LIST_HEAD(struct list_head *list)

{

    WRITE_ONCE(list->next, list);

    list->prev = list;

}

 

【list_entry宏】

由于是把节点嵌入数据结构,而不是把数据结构嵌入节点,于是引发了一个问题:

作为一个节点,它是如何探知外面的世界的?它如何知道自己被怎样的一个数据结构包围着?如何去访问包围它的这个数据结构中的其他成员变量?

 

于是有了list_entry,是一个宏,调用另一个宏,用来获取包含某个链表节点的结构体:

/* list_entry - get the struct for this entry

 * @ptr: the &struct list_head pointer.

 * @type: the type of the struct this is embedded in. //该结构体的类型

 * @member: the name of the list_head within the struct. */ //该链表节点的名称

#define list_entry(ptr, type, member) \

    container_of(ptr, type, member)

 

list_entry宏调用了container_of宏,后者的作用是已知结构体中一成员名(member)和其地址(ptr),求得整个结构体的地址,这个宏返回指向type结构的指针。ptr为成员地址,type为结构体类型,member为结构体成员名。

这里的三个参数,ptr是指向list_head类型链表的指针,type为一个结构体,而member为结构体type中的一个域,类型为list_head。在内核代码中大量引用了这个宏,因此,搞清楚这个宏的含义和用法非常重要。

 

于是我们进一步地,来看container_of宏的具体实现:

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

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

    (type *)((char *)__mptr - offsetof(type, member)); \

})

 

首先,typeof()的作用是获取变量的类型。

于是这里做的第一步是,把0地址强转成结构体type类型的指针,然后利用这个type结构体指针去访问其成员变量member。

然后这个时候再对这个访问到的member变量作typeof操作,其获取其变量类型。

然后利用该变量类型创建一个临时指针变量__mptr,指向ptr

——也就是说,这么一长串const typeof(((type *)0)->member) *__mptr,也只是新建了一个和ptr同样类型的指针,然后执行ptr,在后面代替ptr来使用。

 

紧接着,第二行的offsetof又是一个宏:

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

该宏的作用是计算某个结构体TYPE中,某个成员MEMBER的偏移量。

具体原理是,把0地址强转成结构体TYPE类型的指针,然后利用这个TYPE结构体指针去访问其成员变量MEMBER。这一步相当于,假想在0地址处有一个TYPE类型的结构体,然后访问假想的MEMBER成员。然后再对访问的结果取地址,显然这就是这个假想的MEMBER成员相对于0地址的偏移——即成员变量MEMBER在TYPE中的偏移。

 

知道了offsetof宏的作用,再回到container_of宏的第二行。

我们看到,这一步用__mptr指针的地址减去member相对于type的偏移量——(这里把__mptr强转成char *的目的大概就是要强转成地址量?)。也就是,用指向ptr的指针的地址,减去ptr在type中的偏移量,自然就得到了外部的这个type的地址。

最后再把它强转成type类型的指针。

 

这样,就获取到了整个结构体的地址。

有了这个结构体的地址,相当于我们能够访问包含链表节点的这个外面的结构体,即获得一个指向外部结构体的指针,那么就可以再对其进行访问操作,访问其中的其他成员变量啦。

 

【Question】

这里有个疑问,既然ptr已经指向了结构体中的链表节点,那为什么不直接用它来和偏移量作相减,而是要大费周折地新建一个变量__mptr?为啥呢?

猜你喜欢

转载自blog.csdn.net/weixin_44692935/article/details/113759755
今日推荐