在Linux 内核中双向链表有大量的应用。弄清内核中双向链表的使用及常用的操作函数对学习内核至关重要。完全可以对文件稍作修改后,用到用户空间编程中。
list主要定义在内核源码部分的list.h文件中。/usr/src/kernels/$(uname -r) /include/linux/list.h
本文基于内核:2.6.35.14-106.fc14.i686
1、Linux内核实现了一个双向链表的抽象定义:
struct list_head {
struct list_head *next, *prev;
};
这个链表不含任何数据域,可以嵌入任何结构中,从而形成链表。例如:
struct numlist{
int num;
struct list_head list;
};
注意:一个结构结构中完全可以有多个list域,形成多个相关链表。
2、链表头的初始化
在list.h 对于链表头初始化定了2个宏和一个函数
#define LIST_HEAD_INIT(name) { &(name), &(name) } /* 仅初始化*/
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) /*声明并初始化*/
static inline void INIT_LIST_HEAD(struct list_head *list) /*初始化*/
{
list->next = list;
list->prev = list;
}
其实我们看到,头节点初始化的过程就是将next、prev指向自己,形成了一个空链表。
3、在链表中插入一个节点
static inline void list_add(struct list_head *new, struct list_head *head) /*头插*/
static inline void list_add_tail(struct list_head *new, struct list_head *head) /*尾插*/
定义了一个内部函数,供list_add()与list_add_tail()调用,这与面向对象中的封装类似,而且简化了外部调用时的参数。
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);
}
4、在链表中删除一个节点
static inline void list_del(struct list_head *entry)(常用)
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next); /*也定义了一个内部函数__list_del()*/
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
注意:此处删除并没有将节点 entry 删除,释放。只是将指针next、prev指针指向了固定位置。因为list_head 域只是整个struct的一小部分,所以只是单纯的从链表中摘除,并未对整个数据节点产生影响。
另一个删除节点函数:
static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}
该函数删除节点后,并初始化该节点作为链表头。
5、遍历链表
list.h中定义了一个宏list_for_each,来实现链表遍历。
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
注意:其中pos 是list_head * 类型,是指向当前节点(list_head部分)的指针,head是链表头的指针。
但是,对于试图删除链表节点时,需要进行安全遍历链表,需要使用另一个宏:list_for_each_safe
/**
* 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)
可以以上两个宏的区别是,安全遍历链表宏用一个指针n将将要删除的节点保存下来了。
如果试图用list_for_each来遍历删除,那么当删除pos时,pos->next就丢失了,链就中断了。
6、从指向节点链表域(list_head ),获取整个数据节点,对整个数据节点其他部分进行操作
以上我们都是对一个数据节点的list_head (链表域)部分进行操作。但是获取链表域不是我们本身的目的,我们获取链表域只是为了定位数据节点。那么我们如何通过某个节点的链表域得到该节点的整个起始位置呢?
我们需要由pos位置,找到“节点位置”。
list.h 定义了宏list_entry
/**
* 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)
/*在kernel.h 中定义了container_of*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
注意:type 是数据节点结构体类型。member 是 list _head 域的名字。
分析:container_of :其中offsetof(type,member) 是member在type中的偏移量。((type *)0)->member ) *__mptr = (ptr); __mprt 存放的是ptr的绝对地址。相减就获取了type类型的起始地址。
这样就获取了type 数据结构体的位置,就可以对数据部分进行操作了。
7、链表中节点替换
static inline void list_replace(struct list_head *old,struct list_head *new) //new 替换old
static inline void list_replace_init(struct list_head *old,truct list_head *new) //new替换old,并且对old初始化
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace(struct list_head *old,struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
static inline void list_replace_init(struct list_head *old,struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
注意:在list_repalce 中虽然new节点成功替换了链表中的old 。但是old节点自身pre、next指针任然指向链表中,这或许是一个不安全的因素。如果old是个NULL那么显然会出现问题了。
在来看另外两个相关的函数:删除一个节点,在头尾插入新的节点
static inline void list_move(struct list_head *list, struct list_head *head) //将“节点lis”t从链表中删除,然后在链表头部插入“节点head”
static inline void list_move_tail(struct list_head *list,struct list_head *head) //将“节点lis”t从链表中删除,然后在链表尾部插入“节点head”
/**
* list_move - delete from one list and add as another's head
* @list: the entry to move
* @head: the head that will precede our entry
*/
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
/**
* list_move_tail - delete from one list and add as another's tail
* @list: the entry to move
* @head: the head that will follow our entry
*/
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}
8、判断一个链表是否为空
static inline int list_empty(const struct list_head *head) /*为空返回1*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
来看另外一个判空函数:
static inline int list_empty_careful(const struct list_head *head) /*稍稍安全一点,但是局限性*/
/**
* list_empty_careful - tests whether a list is empty and not being modified
* @head: the list to test
*
* Description:
* tests whether a list is empty _and_ checks that no other CPU might be
* in the process of modifying either member (next or prev)
*
* NOTE: using list_empty_careful() without synchronization
* can only be safe if the only activity that can happen
* to the list entry is list_del_init(). Eg. it cannot be used
* if another CPU could re-list_add() it.
*/
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
我们看到这个判空的方法是:检查自己的下一个是否指向自己,并且自己的下一个节点的prev也指向自己。
从注释了解到:该函数主要是为了防止判空的同时另一个cpu正在修改链表,使判空不准确。但是注释也说明,仅当另一个CPU做list_del_init操作时是安全的。其实还是需要加锁。
9、判断节点是否为链表的最后一个节点。
static inline int list_is_last(const struct list_head *list, const struct list_head *head) /*是最后一个返回1*/
/**
* list_is_last - tests whether @list is the last entry in list @head
* @list: the entry to test
* @head: the head of the list
*/
static inline int list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
10、测试链表是否只有一个节点(除头节点外)
static inline int list_is_singular(const struct list_head *head) /*判断除头结点外,是否只剩一个节点*/
/**
* list_is_singular - tests whether a list has just one entry.
* @head: the list to test.
*/
static inline int list_is_singular(const struct list_head *head)
{
return !list_empty(head) && (head->next == head->prev);
}
11、链表的左旋转
static inline void list_rotate_left(struct list_head *head) /*将head从头节点旋转到尾节点*/
/**
* list_rotate_left - rotate the list to the left
* @head: the head of the list
*/
static inline void list_rotate_left(struct list_head *head)
{
struct list_head *first;
if (!list_empty(head)) {
first = head->next;
list_move_tail(first, head);
}
}
看到类似于将一个从左->右的链表,将头节点右半部分“甩”到头节点的左边。这时头结点已然成为了最后一个节点。
后续继续分析