1.介绍
linux内核中双向链表的核心数据结构struct list_head如下:
<linux/list.h>
struct list_head {
struct list_head *next, *prev;
};
从上面定义可以看出,linux将双向链表关系关系抽离成单独的数据结构,只要包含了list_head类型成员的对象都能成为链表节点,这样就可以将具体的数据类型与数据之间的关系解耦合。
如果把包含list_head的对象称为节点,那么:
next指向下个节点的list_head;
prev指向上个节点的list_head。
使用list_head时,一般将list_head嵌套到自己的结构体中。如下:
struct my_struct {
struct list_head list;
void *data
};
链表使用前需要初始化,有两种初始化方式:
a.动态初始化,则运行时初始化
struct my_struct *p;
p = (struct my_struct *)kmalloc(sizeof(struct my_struct), GFP_KERNEL);
INIT_LIST_HEAD(&p->list);
p->data = NULL;
INIT_LIST_HEAD是一个inline函数。
b.静态初始化,即编译时初始化
struct my_struct mine = {
.list = LIST_HEAD_INIT(mine.list),
.data = NULL
};
LIST_HEAD_INIT是一个宏。
使用LIST_HEAD(list)直接创建一个list节点.
2.链表操作
如果把head当作头节点,则head->next为first节点,head->prev为last节点。
+---------------------------------------------------------------------+
| +-----------+ +-----------+ +----------+ |
+-----| prev | <-----| prev |<--- <---| prev | <---+
+-----------+ +-----------+ ---- +----------+
+---> | next | ----->| next |---> --->| next | ----+
| +-----------+ +-----------+ +----------+ |
+---------------------------------------------------------------------+
head first last
a.添加新节点
list_add(struct list_head *new, struct list_head *head);
list_add_tail(struct list_head *new, struct list_head *head);
list_add将new节点添加在head与head->next之间;而list_add_tail则添加在head->prev与head之间。
b.删除节点
list_del(struct list_head *entry);
list_del_init(struct list_head *entry);
删除entry节点后,list_del把entry->next赋值为LIST_POISON1,entry->prev赋值为LIST_POISON2;
而list_del_init对entry执行了INIT_LIST_HEAD(entry)操作,即将entry的prev和next指针指向自身.
c.移动节点
list_move(struct list_head *list, struct list_head *head);
list_move_tail(struct list_head *list, struct list_head *head);
list_move节点list移到head之后,成为first节点;而list_move_tail则移动了head之前,即成为last节点.
d.判断链表是否为空
list_empty(struct list_head *head);
e.拼接两个链接
list_splice(struct list_head *list, struct list_head *head);
list_splice_init(struct list_head *list, struct list_head *head);
list_splice将list的first到last链表拼接到head与head->next之间;list_splice_init同时把list执行了INIT_LIST_HEAD操作.
f.获取包含list_head的结构体
list_entry(ptr, type, member)
prt : 为结构体中list_head指针;
type : 为结构体类型,如struct my_struct;
member : 为结构体内的list_head成员名称.
struct my_struct *my;
my = list_entry(p, struct my_struct, list);
g.遍历链表
list_for_each(pos, head)
list_for_each_safe(pos, n, head)
pos : 用于存放每次遍历时的元素指针
head : 遍历的链表头节点
list_for_each_safe用n指针临时存放pos的下个节点,避免被删除操作破坏.
struct list_head *p;
struct my_struct *my;
list_for_each(p, &mine->list) {
my = list_entry(p, struct my_struct, list);
}
3.示例
a.目录结构
|--list_test.c
|--Makefile
b.源代码
文件Makefile:
linux内核中双向链表的核心数据结构struct list_head如下:
<linux/list.h>
struct list_head {
struct list_head *next, *prev;
};
从上面定义可以看出,linux将双向链表关系关系抽离成单独的数据结构,只要包含了list_head类型成员的对象都能成为链表节点,这样就可以将具体的数据类型与数据之间的关系解耦合。
如果把包含list_head的对象称为节点,那么:
next指向下个节点的list_head;
prev指向上个节点的list_head。
使用list_head时,一般将list_head嵌套到自己的结构体中。如下:
struct my_struct {
struct list_head list;
void *data
};
链表使用前需要初始化,有两种初始化方式:
a.动态初始化,则运行时初始化
struct my_struct *p;
p = (struct my_struct *)kmalloc(sizeof(struct my_struct), GFP_KERNEL);
INIT_LIST_HEAD(&p->list);
p->data = NULL;
INIT_LIST_HEAD是一个inline函数。
b.静态初始化,即编译时初始化
struct my_struct mine = {
.list = LIST_HEAD_INIT(mine.list),
.data = NULL
};
LIST_HEAD_INIT是一个宏。
使用LIST_HEAD(list)直接创建一个list节点.
2.链表操作
如果把head当作头节点,则head->next为first节点,head->prev为last节点。
+---------------------------------------------------------------------+
| +-----------+ +-----------+ +----------+ |
+-----| prev | <-----| prev |<--- <---| prev | <---+
+-----------+ +-----------+ ---- +----------+
+---> | next | ----->| next |---> --->| next | ----+
| +-----------+ +-----------+ +----------+ |
+---------------------------------------------------------------------+
head first last
a.添加新节点
list_add(struct list_head *new, struct list_head *head);
list_add_tail(struct list_head *new, struct list_head *head);
list_add将new节点添加在head与head->next之间;而list_add_tail则添加在head->prev与head之间。
b.删除节点
list_del(struct list_head *entry);
list_del_init(struct list_head *entry);
删除entry节点后,list_del把entry->next赋值为LIST_POISON1,entry->prev赋值为LIST_POISON2;
而list_del_init对entry执行了INIT_LIST_HEAD(entry)操作,即将entry的prev和next指针指向自身.
c.移动节点
list_move(struct list_head *list, struct list_head *head);
list_move_tail(struct list_head *list, struct list_head *head);
list_move节点list移到head之后,成为first节点;而list_move_tail则移动了head之前,即成为last节点.
d.判断链表是否为空
list_empty(struct list_head *head);
e.拼接两个链接
list_splice(struct list_head *list, struct list_head *head);
list_splice_init(struct list_head *list, struct list_head *head);
list_splice将list的first到last链表拼接到head与head->next之间;list_splice_init同时把list执行了INIT_LIST_HEAD操作.
f.获取包含list_head的结构体
list_entry(ptr, type, member)
prt : 为结构体中list_head指针;
type : 为结构体类型,如struct my_struct;
member : 为结构体内的list_head成员名称.
struct my_struct *my;
my = list_entry(p, struct my_struct, list);
g.遍历链表
list_for_each(pos, head)
list_for_each_safe(pos, n, head)
pos : 用于存放每次遍历时的元素指针
head : 遍历的链表头节点
list_for_each_safe用n指针临时存放pos的下个节点,避免被删除操作破坏.
struct list_head *p;
struct my_struct *my;
list_for_each(p, &mine->list) {
my = list_entry(p, struct my_struct, list);
}
3.示例
a.目录结构
|--list_test.c
|--Makefile
b.源代码
文件list_test.c:
#include <linux/list.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
struct my_struct {
struct list_head list;
char ch;
};
static void prt_list(const char *msg, struct list_head *head)
{
struct list_head *p;
struct my_struct *my;
printk(KERN_ALERT "%s", msg);
list_for_each(p, head) {
my = list_entry(p, struct my_struct, list);
printk(KERN_ALERT "%c\n", my->ch);
}
}
static int test_init(void)
{
struct my_struct a, b, c, d;
struct my_struct oa, ob, oc;
/*define head node*/
LIST_HEAD(head);
LIST_HEAD(ohead);
a.ch = 'a';
b.ch = 'b';
c.ch = 'c';
list_add(&a.list, &head);
list_add(&b.list, &head);
list_add(&c.list, &head);
prt_list("print list:\n", &head);
list_del(&b.list);
prt_list("after delete 'b':\n", &head);
d.ch = 'd';
list_add_tail(&d.list, &head);/*add 'd' to tail */
prt_list("after add 'd' to tail:\n", &head);
list_move(&d.list, &head);/*move 'd' to head*/
prt_list("after move 'd' to head:\n", &head);
oa.ch = 'A';
ob.ch = 'B';
oc.ch = 'C';
list_add_tail(&oa.list, &ohead);
list_add_tail(&ob.list, &ohead);
list_add_tail(&oc.list, &ohead);
prt_list("print other list:\n", &ohead);
list_splice_init(&ohead, &head);
prt_list("after splice other list to first list:\n", &head);
if (list_empty(&ohead))
printk(KERN_ALERT "other list is empty\n");
return 0;
}
static void test_exit(void)
{
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ice");
/*
print list:
c
b
a
after delete 'b':
c
a
after add 'd' to tail:
c
a
d
after move 'd' to head:
d
c
a
print other list:
A
B
C
after splice other list to first list:
A
B
C
d
c
a
other list is empty
*/
文件Makefile:
obj-m := list_test.o
KERNELDIR ?= /lib/modules/`uname -r`/build
PWD := `pwd`
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules