当先锋百科网

首页 1 2 3 4 5 6 7

一. Linux内核链表简介

        Linux内核中需要经常用到双链表,该链表只有指针域,没有数据域。在很多的数据结构中都会嵌入struct list_head结构体变量,它可以使结构体加入到一个双向链表中。链表的初始化,增加,删除等操作的接口在linux-x.x.x/include/linux/list.h里面,内核链表在内核中使用的是如此广泛,所以需要深刻的理解。

二. Linux内核链表使用介绍

1. 基本数据结构

struct list_head {
	struct list_head *next, *prev;
};

        从结构体成员看,内核链表是一个双向链表,没有数据域,next指向下一个链表元素,prev指向上一个链表元素。

2. 链表的初始化

#define LIST_HEAD_INIT(name) { &(name), &(name) }

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

        链表声明和初始化的通常用LIST_HEAD(name)处理。LIST_HEAD定义了一个变量struct list变量name,并且name的next和prev指针都指向name的地址。如下图所示

 3. 链表元素添加

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;
	prev->next = new;
}

# 向头部添加 (在链表头指针和链表第一个元素之间插入元素)
static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

# 向尾部添加 (在链表最后一个元素和链表头指针之间插入元素)
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

        __list_add函数的作用是在prev和next指针指向的元素之间插入new元素。如下图所示。

        list_add函数向__list_add函数传入的prev和next参数分别是head和head->next,所以list_add函数是在链表头指针和链表第一个元素之间插入new元素。

        list_add_tail函数向__list_add函数传入的prev和next指针分别是head->prev和head,head->prev就是指向链表的最后一个元素,所以list_add_tail是在链表最后一个元素和链表头指针之间插入元素。

4. 链表元素删除

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}

static inline void __list_del_entry(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}

static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}

        _list_del函数的作用是删除prev和next指针指向的元素之间的元素,如下图所示

         __list_del_entry函数向__list_del函数传入的的prev和next参数分别指向是要删除元素的前一个元素和后一个元素。

        list_del函数和__list_del_entry函数的区别是将删除元素的前向指针和后向指针都指向一个非法值,如元素从链表中删除后仍然反问,则会产生内存页错误。

5. 链表的遍历

/**
 * 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)

/**
 * list_for_each_prev	-	iterate over a list backwards
 * @pos:	the &struct list_head to use as a loop cursor.
 * @head:	the head for your list.
 */
#define list_for_each_prev(pos, head) \
	for (pos = (head)->prev; pos != (head); pos = pos->prev)


/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop cursor.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
		pos = n, n = pos->next)

/**
 * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop cursor.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_prev_safe(pos, n, head) \
	for (pos = (head)->prev, n = pos->prev; \
	     pos != (head); \
	     pos = n, n = pos->prev)

/**
 * 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_struct 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))

        list_for_each函数是按照从前往后的顺序遍历链表,通过不断指向元素的next元素,直到元素的指针和链表头指针地址相同,则表示链表遍历完成。

        list_for_each_prev函数则是从链表的尾部元素向前遍历。

        list_for_each_safe函数引入了指针n,用于存储pos的下一个元素的地址。引入指针n可以方便在遍历链表的时候删除pos指向的元素,而不影响遍历。list_for_each无法做到这一点。

        list_for_each_prev_safe函数和list_for_each_safe函数的区别是从后往前遍历。

        list_for_each_entry函数是list_for_each和list_entry的结合,有pos,head,member三个参数,pos是一个中间变量,指向当前访问的链表元素,head指链表头,member指pos指向的结构体中链表成员变量的名称,示意图如下:

6. 查找链表元素

/**
 * 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_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

        list_entry宏有三个参数ptr,type,member。ptr是指数据结构中struct list_head变量成员的地址,type是指数据结构的类型,member是指数据结构中struct list_head的变量名。list_entry宏的结果是ptr指向的type类型的数据结构的变量地址。

示意图如下:

        

代码示例

struct task_struct {
    ...
    struct list_head tasks;
    ...
};

        如果要获取某个struct task_struct类型的变量的地址,ptr指向该变量的tasks指针变量的地址,则可以这么写

struct task_struct *addr = list_entry(ptr, struct task_struct, tasks);

举例:list_for_each和list_entry配合使用

        list_for_each函数用于获取数据结构中struct list_head成员的地址,list_entry则是通过struct list_head成员的地址获取到数据结构的地址。

代码示例:

list_for_each(pos, &list)
{
    struct task_struct *addr = list_entry(pos, struct task_struct, tasks);
}

三. 总结

代码示例:

#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/rculist.h>

struct stu_info
{
   char* name;
   int age;
   void (*print)(struct stu_info *stu);
   struct list_head list;
};

void print_info(struct stu_info *stu)
{
    printk("name = %s, age = %d\n", stu->name, stu->age);
}

struct stu_info students[8] = {
	{"Zhao", 20, print_info},
	{"Qian", 21, print_info},
	{"Sun", 22, print_info},
	{"Li", 23, print_info},
};

LIST_HEAD(stu);

static int student_list_init(void)
{
	struct list_head *pos;
	struct stu_info *s;

	list_add(&students[0].list, &stu);
	list_add(&students[1].list, &stu);
	list_add(&students[2].list, &stu);
	list_add(&students[3].list, &stu);

	list_for_each(pos, &stu)
	{
		s = list_entry(pos, struct stu_info, list);
		s->print(s);
	}
	

    return 0;
}

static void student_list_exit(void)
{
   printk("student_list_exit\n");

   return;
}

module_init(student_list_init);
module_exit(student_list_exit);
MODULE_LICENSE("Dual BSD/GPL");

输出结果:

[19764.110000] name = Li, age = 23
[19764.120000] name = Sun, age = 22
[19764.120000] name = Qian, age = 21
[19764.130000] name = Zhao, age = 20