Linux Kernel数据结构:链表

Posted CQ小子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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














以上是关于Linux Kernel数据结构:链表的主要内容,如果未能解决你的问题,请参考以下文章

Linux 内核 内存管理RCU 机制 ① ( RCU 机制简介 | RCU 机制的优势与弊端 | RCU 机制的链表应用场景 )

Linux 内核 内存管理RCU 机制 ② ( RCU 机制适用场景 | RCU 机制特点 | 使用 RCU 机制保护链表 )

Linux 内核 内存管理RCU 机制 ② ( RCU 机制适用场景 | RCU 机制特点 | 使用 RCU 机制保护链表 )

linux kernel的cmdline參数解析原理分析

第127篇 solidity 中链表的实现

linux kernel中timer的使用