链表是一种常用的数据结构,早在大一学C语言的时候就已经学过,之后又在数据结构、算法等课上多次提及和使用。但是最近在学习Linux的内核,发现在Linux内核中的链表跟我们平常所使用的链表有很大的不同,现来总结一下。
首先,我们之前所使用的链表的每一个结点都是一样的。无论是单向链表还是双向链表、还是循环链表,使用链表之前都要声明一个结构体Node,在Node中存放数据域和指针域,以双向链表为例:
typedef struct Node
{
struct Node* next;
struct Node* prev;
int num;
}Node;
在链表中,我们需要维护一个头结点,每次访问链表中的数据就需要从头结点开始,依次往后寻找。找到结点之后,再在结点中找出数据。简单来说,一般的链表是结点“包裹”着数据。
这样带来一个不便就是所有的结点的类型必须是一样的,如果是不同类型的结点,他们的数据域的类型、个数等不一样的时候,就不能把它们串在一起。
但是,在Linux内核中,经常会需要把不同数据结构的结点串在一起形成链表,这是我们就需要对链表进行改进:我们在得到链表的某个结点后,不是再在结点内部寻找数据,而是在外面找。如下面两张图所示:
这样的设计,使得指针域单独出来形成一个结点,作为整个结点的一个成员,然后再和其他数据域的成员形成一个大的结点。
现在的问题是,在得到指针域的结点后,如何访问其他的数据域?利用指针的偏移。
Linux提供了一个宏:list_entry,具体的源码如下:#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)); })
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
首先,传入的三个参数分别代表:
指向结构体中某个成员的指针
结构体
结构体的该成员
返回该结构体的首地址,也就是得到结构体,这样我们就能访问结构体中的成员。
例如:结构体S中有以下三个成员,我们可以通过其中一个成员的地址来获得整个结构体的地址:
typedef struct S
{
int first;
double second;
char third;
}S;
S s;
S* ss = list_entry(&s.second, S, second);
下面解释一下这个宏:
①const typeof(((type *)0)->member)*__mptr= (ptr);
这句话声明一个与menber类型一样的真正_mptr,并保存ptr的值,在上面的例子中,就是:
const typeof(((S *)0)->second)*__mptr= (&s.second);
也就是const int* _mptr= &s.second;
②offsetof(TYPE,MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这句话其实是获得成员MEMBER相对于结构体的偏移值,在上面的例子中,就是:
(size_t) &((
S
*)0)-
>second
在上面的例子中:
offsetof( S, first)= 0;
offsetof( S, second)= 8;
offsetof( S, third)= 16;
③(type *)((char*)__mptr - offsetof(type, member));
这句话就是用刚刚声明的临时变量_mptr减去偏移值,这样就得到了结构体的首地址,如图:
有了这个“根据结构体的某个成员的地址来获得结构体的首地址”的宏,就可以通过链表结点的指针域来获得“外部”的数据域。
下面举个例子,例子中的双向循环链表有添加结点(在头结点之前加、在头结点之后加)和删除结点的功能。每次操作之后输出整个链表。
#include <stdio.h>
#include <stdlib.h>
typedef struct list_head
{
struct list_head* next;
struct list_head* prev;
}list_head;
#define list_entry(ptr, type, member) container_of(ptr, 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) );})
//初始化链表,结点的前驱和后继指向自己
static inline void init_list_head( list_head* list )
{
list->next = list;
list->prev = list;
}
//插入结点
static inline void __list_add( list_head* newNode, list_head* prevNode, list_head* nextNode )
{
nextNode->prev = newNode;
newNode->next = nextNode;
newNode->prev = prevNode;
prevNode->next = newNode;
}
//在头结点之后插入
static inline void _list_add_behind_head( list_head* newNode, list_head* head )
{
__list_add( newNode, head, head->next );
}
//在头结点之前插入
static inline void _list_add_front_head( list_head* newNode, list_head* head )
{
__list_add( newNode, head->prev, head );
}
static inline void __list_del( list_head* delNode )
{
delNode->prev->next = delNode->next;
delNode->next->prev = delNode->prev;
delNode->next = NULL;
delNode->prev = NULL;
free( delNode );
}
typedef struct A
{
int first;
list_head list;
}A;
typedef struct B
{
int first;
int second;
list_head list;
}B;
typedef struct C
{
int first;
int second;
int third;
list_head list;
}C;
int main()
{
A a = { 11 };
B b = { 21, 22 };
C c = { 31, 32, 33 };
//初始化头结点
init_list_head( &a.list );
printf("%d\n",a.first);
_list_add_behind_head( &b.list, &a.list ); //在头结点后插入b
_list_add_behind_head( &c.list, &a.list ); //在头结点后插入c,此时链表为a→c→b→a(循环)
//输出a后面的结点,C类型
printf("%d\n",list_entry( a.list.next, C, list )->first);
printf("%d\n",list_entry( a.list.next, C, list )->second);
printf("%d\n",list_entry( a.list.next, C, list )->third);
//输出a后面的后面的结点,B类型
printf("%d\n",list_entry( a.list.next->next, B, list )->first);
printf("%d\n",list_entry( a.list.next->next, B, list )->second);
//输出a后面的后面的后面的结点,本身
printf("%d\n\n",list_entry( a.list.next->next->next, A, list )->first);
C d = { 41, 42, 43 };
_list_add_front_head( &d.list, &a.list ); //在头结点a之前插入一个C类型的结d,此时链表为a→c→b→d→a(循环)
printf("%d\n",list_entry( a.list.prev, C, list )->first);
printf("%d\n",list_entry( a.list.prev, C, list )->second);
printf("%d\n",list_entry( a.list.prev, C, list )->third);
printf("%d\n",list_entry( a.list.prev->prev, B, list )->first);
printf("%d\n",list_entry( a.list.prev->prev, B, list )->second);
printf("%d\n",list_entry( a.list.prev->prev->prev, C, list )->first);
printf("%d\n",list_entry( a.list.prev->prev->prev, C, list )->second);
printf("%d\n",list_entry( a.list.prev->prev->prev, C, list )->third);
printf("%d\n\n",list_entry( a.list.prev->prev->prev->prev, A, list )->first);
__list_del( &c.list ); //删除结点c,此时链表为a→b→d→a(循环)
printf("%d\n",list_entry( a.list.next, B, list )->first);
printf("%d\n",list_entry( a.list.next, B, list )->second);
printf("%d\n",list_entry( a.list.next->next, C, list )->first);
printf("%d\n",list_entry( a.list.next->next, C, list )->second);
printf("%d\n",list_entry( a.list.next->next, C, list )->third);
printf("%d\n\n",list_entry( a.list.next->next->next, A, list )->first);
return 0;
}
在本例中我们可以看到,我们讲不同类型的结点 A 、 B 、 C 串在了一起,而且我们只需要有头结点,我们就可以直接访问链表中的所有结点的所有成员。