在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 - 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); }
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; }
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); } }看到类似于将一个从左->右的链表,将头节点右半部分“甩”到头节点的左边。这时头结点已然成为了最后一个节点。
后续继续分析