概述:
内核链表往往被包含在目标数据结构中,实现通过统一的链表成员可以找到目标数据结构的其他成员来操作.内核链表的存在意义主要是方便、简化软件上的组织和管理.
1.内核链表简介
1-1.定义:
struct list_head {
struct list_head *next, *prev;
};
注:内核链表定义很简单,里面主要包括指向list_head结构的两个指针next和prev,形成一个双链表.
1-2.使用方法
LINUX内核中,不是在链表结构中包含数据,而是在数据结构中包含链表节点.如下:
struct net_device{
... ...;
struct list_head dev_list;
struct list_head napi_list;
... ...;
};
1-3.内核链表的意义
内核链表,和常用的链表并不一样.它没有数据域,仅作为软件上方便、简化组织管理的一种手段,它的存在可以避免每个目标数据项类型定义自己链表的麻烦.
2.链表的的操作接口
2-1.链表的定义
链表不会横空出世、无中生有.至少它得先有个头.创建一个内核链表头如下:
#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;
}
因此,宏LIST_HEAD(name)定义了一个指向自己的"互撸娃"空链表.依据此可以判断一个链表结构是否为空.
比如我们要创建一个base_head的链表头.操作如下:
struct list_head base_head;
LIST_HEAD_INIT(&base_head);
2-2.判断某链表是否为空
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
返回真链表为空,否则链表非空.
比如我们要判断我们自定义的base_head是否为空.操作如下:
list_empty(&base_head);
2-3.链表的插入、删除、合并
2-3-1.链表的插入:
2-3-1-1.插入表头:
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
2-3.1.2.插入表尾
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
比如有一个链表节点my_list要插入base_head的链表头.操作如下:
list_add(&my_list,&base_head);
2-3-2.链表的删除:
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
比如我们要删除链表头base_head下的my_list节点时.操作如下:
list_del(&my_list);
2-3-3. 链表的搬移
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()源码可知,把节点list从所在的链表头删除,并纳入新链表头head.例如把my_list节点从base_list链表删除并挪到新链表base_list2.
操作如下:
list_move(&my_list,&base_list2);
2-3.4. 链表的合并
static inline void list_splice(const struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
假设有两表头list1和list2,要将两者合并.操作如下:
list_splice(&list1,&list2);
图示如下:
将list1挂载到list2上后,原来的list1的next、prev仍然指向原来的节点.为了避免混乱.内核提供了list_splice_init()函数.如下:
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
与list_splice()函数相比,只多了INIT_LIST_HEAD()宏.这样做的目的是把被合乎的list的next、prev互指,避免了混乱.
3.遍历
内核链表纯粹是一种软件上的组织手段,对我们对目标数据的操作没有任何意义,其意义在于通过这种组织手段去获取我们的目标数据内容.
上面已经说了,我们的目标数据往往包含了链表节点,而链表又被组织进某个表头.在实际编程操作中,我们要操作的对象是具体的数据内容而不是
链表本身.因此,这分两步走:
一、找到我们目标数据包含的链表节点;
二、通过这个链表节点找到我们要操作的目标数据内容.
上述过程由下面两个函数完成:
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
其中,pos为struct list_head类型,为暂存我们遍历过程中的某个链表节点.
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
其中,ptr为struct list_head类型,type为我们具体的目标数据,member为type里面某一成员,member类型必须和ptr一致,这里为struct list_head.
返回值为type类型,即我们获取到了我们要操作的目标数据的内容的首地址.这样一来,我们就可以实现对目标数据内容的操作了.
示意代码如下:
我们的目标数据为:
struct student
{
char name[100];
int num;
struct list_head list;
};
我们要访问目标数据的内容,如name成员.操作如下:
struct list_head *pos;
struct student *tmpstudent;
list_for_each(pos,&student_list)
{
tmpstudent = list_entry(pos,struct student,list);
printk("student%d name is:%s\n",tmpstudent->num,tmpstudent->name);
}
list_for_each()的作用仅仅是遍历,遍历什么时候结束,这由list_entry()函数决定.因此,这两个函数总是成对出现的.
下面附上国嵌关于内核链表操作的一个示意代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h> /*struct head list*/
#include <linux/kernel.h> /*KERN_EMERG,KERN_ALERT,DERN_CRIT and so on*/
#include <linux/slab.h> /*kmalloc and so on*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Seven");
MODULE_DESCRIPTION("Show how to use kernel_list");
struct student
{
char name[100];
int num;
struct list_head list;
};
struct list_head student_list;
struct list_head *pos;
struct student *pstudent;
struct student *tmpstudent;
static int __init module_list_init(void)
{
int i = 0;
INIT_LIST_HEAD(&student_list);
pstudent = kmalloc(sizeof(struct student)*5,GFP_KERNEL);
memset(pstudent,0,sizeof(struct student)*5);
for(i = 0; i < 5; ++i)
{
sprintf(pstudent[i].name,"student%d",i + 1);
pstudent[i].num = i + 1;
list_add(&(pstudent[i].list),&student_list);
}
list_for_each(pos,&student_list)
{
tmpstudent = list_entry(pos,struct student,list);
printk("student%d name is:%s\n",tmpstudent->num,tmpstudent->name);
}
return 0;
}
static void __exit module_list_exit(void)
{
int i = 0;
for(i = 0; i < 5; ++i)
list_del(&(pstudent[i].list));
kfree(pstudent);
}
module_init(module_list_init);
module_exit(module_list_exit);