/* 整理于2012.11.27 于广工大 */
在linux内核中,有大量的数据结构需要用到双循环链表,例如进程、文件、模块、页面等。若采用双循环链表的传统实现方式,需要为这些数据结构维护各自的链表,并且为每个链表都要设计插入、删除等操作函数。由于用来维持链表的next和prev指针指向对应类型的对象,因此一种数据结构的链表操作函数不能用于操作其它数据结构的链表。所以,在Linux源代码树的include/linux/list.h文件中,采用了一种(数据结构)类型无关的双循环链表实现方式。其思想是将指针prev和next从具体的数据结构中提取处理构成一种通用的“双链表”数据结构list_head,而list_head被作为一个成员嵌入到要拉链的数据结构(被称为宿主数据结构)中。这样,只需要一套通用的链表操作函数就可以将list_head成员作为“连接件”,把宿主数据结构链接起来。
linux内核的链表定义比较简单
struct list_head{ struct list_head *next,*prev; };
list_head 结构包含两个指向list_head结构的指针,是一个双链表。但list_head没有数据域,
linux 内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点 list_head.
由链表节点到数据项变量是通过list_entry(ptr, type, member)宏来实现的,
ptr是指向该数据项中list_head成员的指针,
type是数据项的类型,
member为数据结构中list_head成员,
双循环链表实现方式下:
1.链表结构作为一个成员嵌入到宿主数据结构内;
2.可以将链表结构放在宿主结构内的任何地方;
3.可以为链表结构取任何名字;
4.宿主结构可以有多个链表结构。
list_head的一些常用操作(内核已经是完全的自带):
添加
内核的所有链表(包括添加、删除、移动和拼接等)操作都是针对数据结构list_head进行的。
对链表的添加操作有两种:表头添加和表尾添加。
Linux双循环链表中有一个链表头,表头添加是指添加到链表头之后,而表尾添加则是添加到链
表头的prev所指链表节点(如果是空链表,这个链表节点为链表头自身)之后。
Linux为此提供了两个接口:
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完成的
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next);
删除
如果要从链表中删除某个链表节点,则可以调用list_del或list_del_init。
static inline void list_del(struct list_head *entry);
static inline void list_del_init(struct list_head *entry);
两者都调用__list_del将节点从链表中取出,之后,list_del将要删除节点的prev和next指针均设为NULL,
保证不可通过该节点进行访问;
而list_del则调用LIST_INIT_HEAD()把被删除节点为作为链表头构建一个新的双循环链表。
注意:上述操作均仅仅是把节点从双循环链表中拿掉,用户需要自己负责释放该节点对应的数据结构所占用的空间,
而这个空间本来就是用户分配的。
移动
Linux还提供了两个移动操作:list_move和list_move_tail。
前者将指定节点从其所在链表中取出,添加到另一个链表的头部。
而后者在取出后添加到新链表的尾部。
static inline void list_move(struct list_head *list, struct list_head *head);
static inline void list_move_tail(struct list_head *list, struct list_head *head);
拼接
Linux支持两个链表的拼接,具体函数是list_splice和list_splice_init:
static inline void list_splice(struct list_head *list, struct list_head *head);
static inline void list_splice_init(struct list_head *list, struct list_head *head);
两个函数都将一个链表的所有节点依次添加到另一个链表的头部,新链表将一原链表的第一个节点为首节点,
而尾节点不变。
区别在于:前者,原链表头的next和prev仍然指向原来的地方;
而后者调用INIT_LIST_HEAD()为原链表头初始化一个空的双循环链表。
遍历
遍历是双循环链表的基本操作,为此Linux定义了一些宏。
list_for_each对遍历链表中的所有list_head节点,不涉及到对宿主结构的处理。
list_for_each实际是一个 for 循环,利用传入的指向list_head结构的指针作为循环变量,从链表头开始(并跳过链表头),
逐项向后移动指针,直至又回到链表头。为提高遍历速度,还使用了预取。
#define list_for_each(pos, head) \
for (pos = (head)->next, prefetch(pos->next); pos != (head); \
pos = pos->next, prefetch(pos->next))
如果需要反向遍历list_head链表,可以使用list_for_each_prev宏。
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
pos = pos->prev, prefetch(pos->prev))
上述两个操作都是通过移动(指向list_head结构的)指针来达到遍历的目的。但如果在遍历过程中,包含有删除或移动
当前链接节点的操作,由于这些操作会修改遍历指针,这样会导致遍历的中断。这种情况下,必须使用list_for_each_safe宏,
在操作之前将遍历指针缓存下来:
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
对此,Linux提供了list_for_each_entry()宏,第一个参数为传入的遍历指针,指向宿主数据结构,第二个参数为链表头,
为list_head结构,第三个参数为list_head结构在宿主结构中的成员名。
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
prefetch(pos->member.next); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member),\
prefetch(pos->member.next))
实现方法是:
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
Linux为此提供了list_entry()宏,获取当前list_head链表节点所在的宿主结构项。
其中还设计到的一个container_of(ptr, type, member) 成员,container_of分析如下:
ptr是成员变量的指针,
type是指结构体的类型,
member是成员变量的名字。
container_of 的作用就是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。
使用方法:
struct demo_struct { type1 member1; type2 member2; type3 member3; type4 member4; };
获得了type3类型的成员member3的指针,可通过container_of获得整个结构体的指针
struct demo_struct *demop =
container_of(memp, struct demo_struct, member3);
关于container_of的更加详细介绍请见我的另一篇文章《详解container_of宏点击打开链接》
至此,内核自带的list_head的介绍到此为止,下面给出一个使用实例,已经在Red Hat下编译测试通过:
#include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/list.h> #define STUDENT_NUM 10 static int del_num = 3; module_param(del_num, int, 0); MODULE_PARM_DESC(del_num, "delete which node"); /* 自定义结构体当中必须的加入list_head */ struct student { char name; int number; struct list_head list; }; /* 创建list_head */ struct list_head student_list; int create_list(void) { struct student *pnode = NULL; int cnt; /* 初始化list_head,即list->next = list;list->prev = list; */ INIT_LIST_HEAD(&student_list); printk("entry %s.\n",__FUNCTION__); for(cnt = 0; cnt < STUDENT_NUM; cnt++) { pnode = (struct student *)kmalloc(sizeof(struct student), GFP_KERNEL); if(pnode < 0) { printk("%s:kmalloc fail.\n", __FUNCTION__); return -ENOMEM; } pnode->name = 'A' + cnt; pnode->number = cnt; list_add_tail(&pnode->list, &student_list); } return 1; } void print_list(void) { struct student *pnode = NULL; struct list_head *plist = NULL; printk("entry %s.\n",__FUNCTION__); /* list_for_each遍历list_head */ list_for_each(plist, &student_list) { /* list_entry获取当前list_head链表节点所在的宿主结构项 */ pnode = list_entry(plist, struct student, list); printk("student name is %c, number is %d.\n", pnode->name, pnode->number); } } void del_list(int del_num) { struct student *pnode = NULL; struct list_head *plist = NULL, *pnext = NULL; printk("entry %s.\n",__FUNCTION__); /* 安全模式遍历 */ list_for_each_safe(plist, pnext, &student_list) { /* list_entry获取当前list_head链表节点所在的宿主结构项 */ pnode = list_entry(plist, struct student, list); if(pnode->number == del_num) { list_del(plist); printk("delete student name is %c, number is %d.\n",pnode->name, pnode->number); kfree(pnode); } } } static int __init mylist_init(void) { int ret; ret = create_list(); if(ret < 0) { printk("create list failed.\n"); return -EFAULT; } print_list(); del_list(del_num); print_list(); return 0; } static void __exit mylist_exit(void) { printk("mylist module exit.\n"); } module_init(mylist_init); module_exit(mylist_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("list module");