Linux Kernel数据结构:链表

数据结构中链表是 节点中包含数据 , kernel中的链表是链表包含在数据结构中

内核链表的优势

尽可能的代码重用,将大堆的链表设计变为一个链表操作就可以搞定,总结起来可以为可扩展性,封装性。在数据结构的中的链表一般情况下都是一个节点中包含数据域和指针域,数据域用于存储数据信息,指针域用于连接下一个节点,不过这样的话有一个弊端就是当我设计一个学生信息链表时,我需要写一套关于这个链表的操作函数,假设我再设计一个老师信息的链表时,我又要写一套老师信息链表的操作函数,这样如果很多的话,做起来非常费劲。kernel中的链表设计就巧妙的避开了这个弊端。

链表的构造

如果需要构造某个结构的链表,则在这个结构中定义一个类型为list_head的指针成员,通过这个成员将每个结构连接起来,形成链表,通过通用的链表函数来进行操作。有点可想而知,这个通用的链表操作函数可以搞定所有的链表,实现了代码的重用。如果想得到对应结构的指针,可以使用list_entry计算出来。

比如我这边有一个单纯的结构,我想让他组成一个链表。

struct mystruct {
     int data ;
} ;
在结构体内部增加一个list_head, list_head为

struct list_head {
	struct list_head *next, *prev;
};
组合之后为

struct mystruct {
     int data ;
     struct list_head mylist ;
} ;
初始化第一个变量

struct mystruct first = {
     .data = 10,
     .mylist = LIST_HEAD_INIT(first.mylist)
} ;
data = 10 , LIST_HEAD_INIT 实际上是让这个mylist的变量自己指向自己了.

#define LIST_HEAD_INIT(name) { &(name), &(name) } 
接下来再加入第二个变量second ,data = 20

struct mystruct second ;
second.data = 20 ;
INIT_LIST_HEAD( & second.mylist ) ;
INIT_LIST_HEAD初始化指向自己

static inline void INIT_LIST_HEAD(struct list_head *list)
{
	WRITE_ONCE(list->next, list);
	list->prev = list;
}
以上我们声明并初始化了两个data,然后这个地方我们需要一个头list_head

LIST_HEAD(mylinkedlist) ;

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

链表节点初始化之后如下图


链表的插入

看下如何将first,second插入到链表mylinkedlist中

list_add ( &first.mylist , &mylinkedlist ) ;
list_add ( &second.mylist , &mylinkedlist ) ;
看下list_add的操作:

/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}
__list_add

static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = new;
	new->next = next;
	new->prev = prev;
	WRITE_ONCE(prev->next, new);
}
可以使用一个图来表示:


这里list_add是从表头插入的,kernel也提供了一种从表尾插入的方式

/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

遍历

将链表上所有的元素都走一遍,内核中也提供一个宏来实现这个功能,从下面的宏可以看出, pos指向的是第一个元素,如果pos不等于head(如果等于head的话,应该是到了表尾的时候了),获取到这个节点之后,可以进行操作,操作完毕之后移到下一个节点。

/**
 * list_for_each	-	iterate over a list
 * @pos:	the &struct list_head to use as a loop cursor.
 * @head:	the head for your list.
 */
#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); pos = pos->next)
从上面看出实际上我们拿到的都是struct list_head的位置,并没有拿到数据域,如何拿到数据域,也就是整个struct的指针,kernel也给出了接口

/**
 * 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)
container_of就是通过数据结果中的一个元素计算出数据的第一个元素的地址

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
	const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
	(type *)( (char *)__mptr - offsetof(type,member) );})

  1.首先定义一个临时的数据类型(通过typeof( ((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值。

  2.用(char *)__mptr减去member在结构体中的偏移量,得到的值就是整个结构体变量的首地址(整个宏的返回值就是这个首地址)。

获取到数据域,就可以获得到数据信息了,如下面,但因出每一个节点的data值。

struct list_head *position = NULL ; 
struct mystruct  *datastructureptr  = NULL ; 
list_for_each ( position , & mylinkedlist ) 
    { 
         datastructureptr = list_entry ( position, struct mystruct , mylist ); 
         printk ("data  =  %d\n" , datastructureptr->data ); 
    }
kernel同时也提供了这样一个比较简单的宏

/**
 * list_for_each_entry	-	iterate over list of given type
 * @pos:	the type * to use as a loop cursor.
 * @head:	the head for your list.
 * @member:	the name of the list_head within the struct.
 */
#define list_for_each_entry(pos, head, member)				\
	for (pos = list_first_entry(head, typeof(*pos), member);	\
	     &pos->member != (head);					\
	     pos = list_next_entry(pos, member))

链表的释放

释放就比较简单了,首先要就是断掉prev和next的联系,让二者关联起来。

static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}
/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	WRITE_ONCE(prev->next, next);
}

当然链表还提供了很多相关的接口,实现在kernelxx/include/linux/list.h中,可以参阅。

参考文章

1.https://kernelnewbies.org/FAQ/LinkedLists

2.http://blog.chinaunix.net/uid-27033491-id-3296804.html

3.http://www.cnblogs.com/Daniel-G/archive/2013/09/06/3305834.html

4.http://blog.csdn.net/npy_lp/article/details/7010752














  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值