对于链表,我想每一个程序员都很了解。结构就像一根链条一样,一节接一节,而对它的访问有点像冰糖葫芦,如果你要吃第二个,你必须先吃掉第一个。这就是单向链表。当然它也有更高级的,比如,循环链表,双向链表,双向循环链表。Linux内核的标准链表就是环形双向循环链表。
Linux中的链表有点特殊,它没有头节点,它的尾节点就直接指向首节点。于是构成了一个很大的环,所以每一个节点都是头节点,你可以从任意节点出发,沿任意的方向循环访问链表或者是它的一部分。
Linux内核中的链表是很优秀的设计,内核中所欲用到链表的地方都使用了它,所以在你的代码中也最好用这些已有的链表接口,别螳臂挡车,阻挡潮流了。
下面我们来看看Linux内核中的链表给我们提供了什么样的功能。
首先,链表结构体定义在头文件<linux/list.h>中,形式很简单:
struct list_head {
struct list_head *next, prev;
};
一个list_head结构体本身没有什么意义,,通常需要把它嵌入到你自己的结构体中:
struct my_struct {
struct list_head list;
unsigned long dog;
void *cat;
};
链表在使用前必须初始化,由于多数元素都是动态创建(也许这就是你需要使用链表的原因),所以最常见的情况是在运行时初始化链表。
struct my_struct *p;
p->dog = 0;
p->cat = NULL;
INIT_LIST_HEAD(&p->list);
直接声明和初始化一个静态链表:
static LIST_HEAD(fox);
上述语句声明并初始化名为fox的静态链表。
不需要与任何链表的内部成员打交到,你仅仅将链表结构插入到你自己的数据中就可以了。
操作链表 内核提供了一组函数来操作链表,这些函数都要使用一个或多个list_head结构体指针做参数。因为函数都是用C语言以内联函数形式实现的,所以它们的原型都在头文件<linuxlist.h>中。
有趣的是,所有这些函数的复杂度都是O(1),也就是说,无论这些函数操作的链表大小如何,无论它们得到额参数如何,它们都在恒定的时间里完成。
1,给链表增加一个节点:在给定节点之后
list_add(struct list_head *new, struct list_head *head)
2,给链表增加一个节点:在给定节点之前
list_add_tail(struct list_head *new, struct list_head *head)
3,从链表删除一个节点:
lsit_del(struct list_head *entry)
需要自己手动释放entry结构体
4,从链表中删除一个节点,并对其进行重新初始化
lsit_del_init(struct list_head *entry)
5,把节点从一个链表移到另一个链表:
list_move(struct list_head *list, struct list_head *head)
list_move_tail(struct list_head *list, struct list_head *head)
6,检查链表是否为空:
lsit_empty(struct list_head *head)
如果指定的链表为空,该函数返回非0值,否则返回0
7,合并两个链表
list_splice(struct list_head *list, struct list_head *head)
list_splice_init(struct list_head *list, struct list_head *head)
如果你已经得到了next和prev指针,那你可以使用内部操作函数,从而省下提领指针的时间,内部函数与外部函数重名,只是在前面加了两条下划线。
遍历链表:
内核给我们提供了一组非常好用的接口来遍历链表,不过它们的复杂度为O(n),n为链表中的元素数目。list_for_each()宏就是用来遍历链表,它有两个参数,第一个用来指向当前项,第二个是需要遍历的链表,但是它得到的只是lsit_head的指针,而我么您需要的是插入了list_head的结构体。list_entry()宏可以帮助我们它有三个参数,一个是指 向给定的链表元素的指针,一个是其中嵌入了链表的结构体类型的引用,另一个是 结构体中链表成员的名称:如下是用法:
struct list_head *p;
struct my_struct * my;
list_for_each(p, &mine->list) {
my = list_entry(p, struct my_struct, list);
}
还有一些宏:list_for_each_prev() list_for_each_safe() 可以选择使用
最后一个宏只能用来防止链表删除操作,为了防止并发访问实际的链表数据,应该使用其他的锁
Linux中的链表有点特殊,它没有头节点,它的尾节点就直接指向首节点。于是构成了一个很大的环,所以每一个节点都是头节点,你可以从任意节点出发,沿任意的方向循环访问链表或者是它的一部分。
Linux内核中的链表是很优秀的设计,内核中所欲用到链表的地方都使用了它,所以在你的代码中也最好用这些已有的链表接口,别螳臂挡车,阻挡潮流了。
下面我们来看看Linux内核中的链表给我们提供了什么样的功能。
首先,链表结构体定义在头文件<linux/list.h>中,形式很简单:
struct list_head {
struct list_head *next, prev;
};
一个list_head结构体本身没有什么意义,,通常需要把它嵌入到你自己的结构体中:
struct my_struct {
struct list_head list;
unsigned long dog;
void *cat;
};
链表在使用前必须初始化,由于多数元素都是动态创建(也许这就是你需要使用链表的原因),所以最常见的情况是在运行时初始化链表。
struct my_struct *p;
p->dog = 0;
p->cat = NULL;
INIT_LIST_HEAD(&p->list);
直接声明和初始化一个静态链表:
static LIST_HEAD(fox);
上述语句声明并初始化名为fox的静态链表。
不需要与任何链表的内部成员打交到,你仅仅将链表结构插入到你自己的数据中就可以了。
操作链表 内核提供了一组函数来操作链表,这些函数都要使用一个或多个list_head结构体指针做参数。因为函数都是用C语言以内联函数形式实现的,所以它们的原型都在头文件<linuxlist.h>中。
有趣的是,所有这些函数的复杂度都是O(1),也就是说,无论这些函数操作的链表大小如何,无论它们得到额参数如何,它们都在恒定的时间里完成。
1,给链表增加一个节点:在给定节点之后
list_add(struct list_head *new, struct list_head *head)
2,给链表增加一个节点:在给定节点之前
list_add_tail(struct list_head *new, struct list_head *head)
3,从链表删除一个节点:
lsit_del(struct list_head *entry)
需要自己手动释放entry结构体
4,从链表中删除一个节点,并对其进行重新初始化
lsit_del_init(struct list_head *entry)
5,把节点从一个链表移到另一个链表:
list_move(struct list_head *list, struct list_head *head)
list_move_tail(struct list_head *list, struct list_head *head)
6,检查链表是否为空:
lsit_empty(struct list_head *head)
如果指定的链表为空,该函数返回非0值,否则返回0
7,合并两个链表
list_splice(struct list_head *list, struct list_head *head)
list_splice_init(struct list_head *list, struct list_head *head)
如果你已经得到了next和prev指针,那你可以使用内部操作函数,从而省下提领指针的时间,内部函数与外部函数重名,只是在前面加了两条下划线。
遍历链表:
内核给我们提供了一组非常好用的接口来遍历链表,不过它们的复杂度为O(n),n为链表中的元素数目。list_for_each()宏就是用来遍历链表,它有两个参数,第一个用来指向当前项,第二个是需要遍历的链表,但是它得到的只是lsit_head的指针,而我么您需要的是插入了list_head的结构体。list_entry()宏可以帮助我们它有三个参数,一个是指 向给定的链表元素的指针,一个是其中嵌入了链表的结构体类型的引用,另一个是 结构体中链表成员的名称:如下是用法:
struct list_head *p;
struct my_struct * my;
list_for_each(p, &mine->list) {
my = list_entry(p, struct my_struct, list);
}
还有一些宏:list_for_each_prev() list_for_each_safe() 可以选择使用
最后一个宏只能用来防止链表删除操作,为了防止并发访问实际的链表数据,应该使用其他的锁