linux内核学习——list链表

链表使用基础:

链表是linux内核中最简单,最普遍的数据结构。刚开始接触内核的人可能会对linux的链表操作有点不习惯。因为内核链表与大家平时用的链表不同,它不是把数据结构放到链表里面,而是把链表节点放到数据结构里面。linux内核的链表代码在<linux/list.h>中声明。

链表代码在头文件<linux/list.h>中声明,其数据结构很简单:

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

next指针指向下一个链表节点,prev指向前一个。从这里可以看出,list链表是双向链表。但链表存储的具体内容是什么呢?其关键就在于理解list_head结构是怎么被使用的。

struct fox{
unsigned long tail_length;
unsigned long weight;
bool          is_fantastic;
struct list_head list;       //所有fox结构体形成链表
}
使用container_of()宏可以很方便地从链表指针找到父结构中包含的任何变量,这是因为在C语言中,一个给定结构的变量偏移在编译时地址就被ABI固定下来了。

现在,如果我们要访问fox中的一个变量,得先获得该struct fox结构体的地址,如下:

struct fox *one = ***;

struct fox *another;

struct list_head *p = &one->list; //如果我们已经知道结构体中的一个变量地址

则another = container_of(p, struct fox, list)就获得了该结构体的指针,然后我们就可以访问结构体中任意成员了。

内核中contain_of()宏的实现:

#define container_of(ptr, type, member)({              \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);   \
        (type *)( (char*)__mtr - offsetof(type, member) );})

#define list_entry(ptr, type, member) \
        container_of(ptr, type, member)

参数ptr为结构体中某一变量的地址,type为该结构体类型,member为该结构体类型中指针p对应的成员变量。

内核里链表实现为带有头节点的双向循环链表。所以我们定义一个链表时要有一个链表头,继续上面例子,我们定义并初始化一个关于fox的链表:

list_head fox_list;

static LIST_HEAD(fox_list);      //链表头也是一个list_head类型

链表操作:

链表操作无非就是增加、删除和遍历,下面就逐一介绍。

向链表增加一个节点:

list_add(struct list_head *new, struct list_head *head)

该函数向指定链表的head节点后插入一个new节点,实际上就是把new节点放到了链表元素的第一个位置(头节点不包含数据的哦)。

假如我们创建一个新的struct fox节点,并把它加入fox_list,那么我们这样做:

struct fox f;

list_add(&f->list, &fox_list);

如果想想链表尾部添加一个节点,可以使用下面函数:

list_add_tail(struct list_head *new, struct list_head *head)

该函数向指定链表的head节点之前插入一个new节点。

看一下内核的实现:

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
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;
}

/**
 * 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_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);
}

向链表删除一个节点:

list_del(struct list_head *entry)

该函数从链表中删除entry函数,但不会释放entry或释放包含entry的数据结构体锁占用的内存,该函数仅仅是把entry元素从链表中一走,如果需要释放内存,则另需操作。

其内核代码实现:

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

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
#ifndef CONFIG_DEBUG_LIST
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = (void *)0xDEADBEEF;
	entry->prev = (void *)0xBEEFDEAD;
} 

移动和合并链表节点:

把节点从一个链表移动到另一个链表:

list_move(struct list_head *list, struct list_head *head)

该函数从一个链表中移除list项,然后将其放入到另一个链表的head节点后面,如果想移动到另一个链表的末尾,则使用下面函数:

list_move_tail(struct list_head *list, struct list_head *head)

把两个未连接的链表合并到一起:

list_splice(struct list_head *list, struct list_head *head)

将list指向的链表插入到指定链表的head元素的后面。

遍历整个链表:

链表遍历访问是链表操作中非常重要的一个操作,也是经常使用的。我们创建链表的目的就是为了访问链表中的数据。

链表访问用法如下(还是用上面的例子):

struct list_head *p;
struct fox *f;

list_for_each(p, fox_list){
        f = list_entry(p, struct fox, list);  //p 指向链表中的元素
        /* 开始对元素进行访问 */
}

链表遍历有个更方便的用法:list_for_each_entry(pos, head, member)
上面的例子也可以这样访问:
struct fox *f;

list_for_each_entry(f, &fox_list, list){
        /* 开始对元素进行访问 */
}


反向遍历链表:
list_for_each_entry_reverse(pos, head, member)

遍历的同时删除:

标准的链表遍历方法在遍历链表的同时要想删除节点是不行的,具体原因就根据内核源码自己琢磨吧。

linux内核提供了如下的操作方法:

list_for_each_entry_safe(pos, next, head, member)

list_for_each_entry_safe_reverse(pos, next, head, member)
其中next和pos是相同类型,也是list_head指针,内核实现:

/**
 * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
 * @pos:	the type * to use as a loop cursor.
 * @n:		another type * to use as temporary storage
 * @head:	the head for your list.
 * @member:	the name of the list_struct within the struct.
 */
#define list_for_each_entry_safe(pos, n, head, member)			\
	for (pos = list_entry((head)->next, typeof(*pos), member),	\
		n = list_entry(pos->member.next, typeof(*pos), member);	\
	     &pos->member != (head); 					\
	     pos = n, n = list_entry(n->member.next, typeof(*n), member))

/**
 * list_for_each_entry_safe_reverse
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate backwards over list of given type, safe against removal
 * of list entry.
 */
#define list_for_each_entry_safe_reverse(pos, n, head, member)        \
    for (pos = list_entry((head)->prev, typeof(*pos), member),    \
        n = list_entry(pos->member.prev, typeof(*pos), member);    \
         &pos->member != (head);                     \
         pos = n, n = list_entry(n->member.prev, typeof(*n), member)) 


posted @ 2013-12-15 20:39  菜鸟天空  阅读(471)  评论(0编辑  收藏  举报