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?为啥呢?