Linux内核链表

     链表是一种常用的数据结构,早在大一学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 串在了一起,而且我们只需要有头结点,我们就可以直接访问链表中的所有结点的所有成员。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值