内核链表操作举例(上)

#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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值