#include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct student {
int age;
struct list_head list; // 既然链表不能包含万事万物,那么就让万事万物来包含链表
// 功能上相当于从list_head继承
};
#define STUDENT_COUNT 5
int main(int argc, char **argv) {
int i;
struct student head; //必须有个空闲头结点
struct student *p; //用于新结点
struct student *pos; //内核循环链表函数或宏需要的;起临时变量的作用
struct student *n; //内核循环链表函数或宏需要的;起临时变量的作用
struct list_head *position; //内核循环链表函数或宏需要的;起临时变量的作用
INIT_LIST_HEAD(&head.list); //链表初始化;注意参数是头指针,不是头结点
for(i = 0; i < STUDENT_COUNT; i++) {
p = (struct student *)malloc(sizeof(struct student));
if( p == NULL) {
printf("malloc failed\n");
continue;
}
p -> age = 10+i;
/*
*list_add: 将新的结点加入到链表;头插法,即每次插入的节点都位于上一个节点之前
* 因此最后插入的结点最先被访问
*/
list_add(&p->list, &head.list);
}
printf("The output of list_for_each:\n");
/*list_for_each:遍历链表
*第一个参数类型是struct list_head结构体指针
*第二个参数是用INIT_LIST_HEAD初始化的那个对象,即头指针,注意,不是头结点
*/
list_for_each(position,&head.list) {
p = list_entry(position,struct student,list); //第一个参数类型是struct list_head结构体指针
//第二个参数是链表容器结构体类型
//第三个参数是第一个参数指向的链表容器中的成员变量
printf("age:%d\n", p->age);
}
//大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说 list_for_each()和list_entry() 总是同时使用。
//对此内核给出了一个 list_for_each_entry() 宏
printf("\nThe output of list_for_each_entry:\n");
list_for_each_entry(pos,&head.list,list) { //list_for_each是遍历链表,增加entry后缀,
//表示遍历的时候,还要获取entry(入口),即获取链表容器结构的地址。
//注意这里的第一个参数类型是链表容器结构体的指针;这点和list_for_each不同!
//第二个参数是用INIT_LIST_HEAD初始化的那个对象,即头指针,注意,不是头结点
//第三个参数是容器结构中的链表元素对象
printf("age:%d\n", pos->age);
}
//某些应用需要反向遍历链表,Linux 提供了 list_for_each_prev() 和 list_for_each_entry_reverse() 来完成这一操作
//使用方法和上面介绍的 list_for_each()、list_for_each_entry() 完全相同。
printf("\nThe output of list_for_each_prev:\n");
list_for_each_prev(position,&head.list) {
p = list_entry(position,struct student,list);
printf("age:%d\n", p->age);
}
printf("\nThe output of list_for_each_entry_reverse:\n");
list_for_each_entry_reverse(pos,&head.list,list) {
printf("age:%d\n", pos->age);
}
//
printf("\nAdd a element with list_add_tail func\n");
p = (struct student *)malloc(sizeof(struct student));
if( p != NULL) {
p -> age = 100;
list_add_tail(&p->list, &head.list); //尾插法,和头插法刚好相反
}
printf("\nThe output of adding a element with list_add_tail func\n");
list_for_each_entry(pos,&head.list,list)
printf("age:%d\n", pos->age);
printf("\nReplace age 100 with 20\n");
p = (struct student *)malloc(sizeof(struct student));
if( p == NULL)
return 0; //这里仅仅是示例程序,所以只是简单的返回了
p -> age = 20;
list_for_each_entry(pos,&head.list,list) {
if(pos->age == 100) {
list_replace(&pos->list, &p->list); //通过其名字我们就能知道,该函数是替换链表的
}
}
printf("\nThe output of running list_replace:\n");
list_for_each_entry(pos,&head.list,list)
printf("age:%d\n", pos->age);
printf("\nReplace age 20 with 15\n");
p = (struct student *)malloc(sizeof(struct student));
if( p == NULL)
return 0; //这里仅仅是示例程序,所以只是简单的返回了
p -> age = 15;
list_for_each_entry(pos,&head.list,list) {
if(pos->age == 20) {
list_replace_init(&pos->list, &p->list); //由于list_replace没有将old的前驱和后继断开,
//所以内核又提供了:list_replace_init
//通常应该使用list_replace_init
break; //这里break不能少
}
}
printf("\nThe output of running list_replace_init:\n");
list_for_each_entry(pos,&head.list,list)
printf("age:%d\n", pos->age);
/
list_for_each_entry_safe(pos, n, &head.list, list) { //如果想要在遍历链表的时候执行删除链表的操作,需要对list_for_each_entry进行改进。
//内核链表设计者们早已给我们考虑到了这一情况,所以内核又提供了个宏
//值得注意的是,如果链表数据域中的元素都相等,使用list_for_each_entry_safe反而会无限循环,
//而list_for_each_entry却能正常工作
//所以,具体的内核链表API,需要根据自己的应用场景选择
printf("\nDelete age=10\n");
if(pos->age == 10) {
list_del(&pos->list); //删除链表查找的项
//链表删除之后,entry的前驱和后继会分别指向LIST_POISON1和LIST_POISON2,这个是内核设置的一个区域
//list_del让删除的节点前驱和后继指向LIST_POISON1和LIST_POISON2的位置,另一个删除函数list_del_init
//删除节点后,前驱和后继指向不同,具体查看list.h
//根据业务需要,可以自行选择适合自己的函数
free(pos);
}
}
printf("\nThe output of runnig list_del:\n");
list_for_each_entry(pos,&head.list,list)
printf("age:%d\n", pos->age);
/
printf("\nMove age 15 to head node\n");
list_for_each_entry_safe(pos, n, &head.list, list) {
if(pos->age == 15) {
list_move(&pos->list, &head.list); //list_move就是移动list指针所处的容器结构节点,然后将其重新以头插法添加到另一个头结点中去
}
}
printf("\nThe output of runnig list_move:\n");
list_for_each_entry(pos,&head.list,list)
printf("age:%d\n", pos->age);
printf("\nMove age 12 to tail node\n");
list_for_each_entry_safe(pos, n, &head.list, list) { //对比list_for_each_entry,多了一个参数n,n的数据类型是容器结构体指针
//list_for_each_entry_safe可以在遍历链表时安全地执行删除操作,其原理就是先把后一个节点取出来使用n作为缓存,
//这样在还没删除节点时,就得到了要删除节点的下一个节点的地址,从而避免了程序出错。
if(pos->age == 12) {
list_move_tail(&pos->list, &head.list); //既然有头插法的list_move,那么也同样有尾插法的list_move_tail
}
}
printf("\nThe output of runnig list_move_tail:\n");
list_for_each_entry(pos,&head.list,list)
printf("age:%d\n", pos->age);
/
printf("\nThe output of list_first_entry\n");
p = list_first_entry(position,struct student,list); //得到第一个entry(条目,入口)
printf("age:%d\n", p->age);
//Others
//在并发执行的环境下,链表操作通常都应该考虑同步安全性问题
//如果遍历不是从链表头开始,而是从已知的某个节点pos开始,则可以使用 list_for_each_entry_continue(pos,head,member)。
//有时还会出现这种需求,即经过一系列计算后,如果pos有值,则从pos开始遍历,如果没有,则从链表头开始,
//为此,Linux专门提供了一个list_prepare_entry(pos,head,member)宏,将它的返回值作为list_for_each_entry_continue()的 pos参数,就可以满足这一要求。
//基本的list_empty()仅以头指针的next是否指向自己来判断链表是否为空,Linux 链表另行提供了一个list_empty_careful()宏,它同时判断头指针的next和prev,
//仅当两者都指向自己时才返回真。这主要是为了应付另一个cpu 正在处理同一个链表而造成 next、prev 不一致的情况。
//但代码注释也承认,这一安全保障能力有限:除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。
return 0;
}