zoukankan      html  css  js  c++  java
  • Linux 内核链表实现和使用(一阴一阳,太极生两仪~)

    0. 概述

    学习使用一下 linux 内核链表,在实际开发中我们可以高效的使用该链表帮我们做点事,

    链表是Linux 内核中常用的最普通的内建数据结构,链表是一种存放和操作可变数据元

    素(常称为节点)的数据结构,链表和静态的数组不同之处在于,它所包含的元素都是动

    态创建插入链表的,在编译时不必知道具体需要创建多少个元素。   另外也因为链表中

    每个元素的创建时间各不相同,所以它们在内存中无须占用连续内存区,正是因为元素

    不连续存放,所以各元素需要通过某种方式被连接在一起,于是每个元素都包含一个指

    向下一个元素的指针,当有元素加入链表或从链表中删除元素时,简单调整指向下一个

    节点的指针就可以了。

    Linux 内核链表采用双向循环链表形式实现, 它的经典在于和普通的链表实现方式相比

    可谓独树一帜, 我们普通的一个数据(比如一个 struct) 通过在内部添加一个该数据类型

    的next或previous指针,才能串联在链表中,  Linux 内核方式与众不同,它不是将数据

    塞入链表,而是将链表节点塞入数据结构。

    // 普通链表节点 - 将数据类型的指针嵌在里面实现串联
    typedef int data_t;
    typedef struct Node* PNode;
    
    typedef struct Node {
        data_t value;
        PNode next;
        PNode prev
    }Node;
    
    // 使用内核链表
    struct person {
        int age;
        char name[20];
        struct list_head list; // 将链表内嵌在数据结构中
    };

    过去,内核中有许多链表的实现,该选一个即简单、  又高效的链表来一统江湖,在2.1内

    核开发系列中首次引入了官方内核链表实现,从此内核中的所有链表现在都用官方的链表

    实现了,OK 预热就到这里,这一段话选自<LINUX 内核设计与实现> O ^_^ O

    1. 两个牛逼的宏开胃甜点~)

    1.1 offsetof

     testOffsetof.c 测试代码

    #include <stdio.h>
    
    // offsetof 宏
    /*
        图解
        
        TYPE代表整个结构体
        
        |-----|------|
        |     |      |
        |TYPE |------| 
        |     |MEMBER|---> TYPE 中的某一个成员
        |     |------|
        |     |      |
        |-----|------|
        
    
        说明:获得结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量
        
        1. ((TYPE *)0)  将0转型为TYPE类型指针,即TYPE类型的指针第地址是0
        2. ((TYPE *)0)->MEMBER  访问结构中的数据成员
        3. &(((TYPE *)0)->MEMBER)   取出成员的地址, 由于TYPE的地址是0,这里获取到的地址
                                    就是相对MEMBER在TYEP中的偏移量
        4. (size_t)(&(((TYPE *)0)->MEMBER)) 结果转换类型,对于32位系统, size_t是unsigned int
                                            对于64位系统,size_t是unsigned long类型
                                            
                                            
        TYPE是结构体,它代表"整体";而MEMBER是成员,它是整体中某一部分。
        将offsetof看作一个数学问题看待,问题就相当简单了:已知'整体'和'整体中一部分',
        而计算该部分在整体中的偏移
    
     */
    #define offsetof(TYPE, MEMBER)  ((size_t) &((TYPE *)0)->MEMBER)
    
    /*
        struct student 是4字节对齐
        
            ------------|
            |   name    |
            |-----------|<----- 12
            |   age     |
            |-----------|<----- 8
            |   id      |   
            |-----------|<----- 4
            |  gender   |
            |-----------|<----- 0
    */
    
    struct student {
        char gender;
        int id;
        int age;
        char name[20];
    };
    
    int main()
    {
        int gender_offset, id_offset, age_offset, name_offset;
        
        gender_offset = offsetof(struct student, gender);
        id_offset     = offsetof(struct student, id);
        age_offset    = offsetof(struct student, age);
        name_offset   = offsetof(struct student, name);
        
        printf("gender_offset = %d
    ", gender_offset);  // 0
        printf("id_offset = %d
    ", id_offset);          // 4
        printf("age_offset = %d
    ", age_offset);        // 8
        printf("name_offset = %d
    ", name_offset);      // 12
        
        struct student zhao;
        printf("sizeof zhao = [%d]
    ", sizeof(zhao));   // 32个字节
        
        return 0;
    }

    1.2 container_of

     testContainer_of.c 测试代码

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    /*
        container_of 宏
        
    定义:      
        #define container_of(ptr, type, member) ({  
            const typeof( ((type *)0)->member )*__mptr = (ptr); 
            (type *)( (char *)__mptr - offsetof(type,member));})
    
    说明:
        根据"结构体(type)变量"中的"成员变量(member)的指针(ptr)"来
        获取指向整个结构体变量的指针。
        
        1. typeof(((type *)0)->member)  取出member成员的变量类型
        
        2. const typeof(((type *)0)->member)*__mptr = (ptr) 定义
           变量__mptr指针,并将ptr赋值给__mptr,经过这一步, __mptr
           为member数据类型的常量指针,其指向ptr所指向的地址。
         
        3. (char *)__mptr  将__mptr转换为字符型指针。
        
        4. offsetof(type,member) 就是获取"member"成员在结构体"type"
           中的偏移量。
           
        5. ((char *)__mptr - offsetof(type,member)) 就是用来获取
           "结构体type"的指针的起始地址(为cha *型指针)
           
        6. (type *)((char *)__mptr - offsetof(type, member)) 就是
            将"char *"类型的结构体type的指针转换为"type *"类型的
            结构体type 的指针。
        
    */
    
    /* offsetof 宏
     *
     * 获取结构体(TYPE)的变量成员(MEMBER)在此结构体中的偏移量
     *
     */
    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
    
    /* container_of 宏
     *
     * 根据结构体(type)变量中的成员变量(member)的指针(ptr),来获取
     * 指向整个结构体变量的指针
     */
    #define container_of(ptr, type, member) ({  
        const typeof( ((type *)0)->member) *__mptr = (ptr); 
        (type *)( (char *)__mptr - offsetof(type,member)); })
    
    struct student {
        char gender;
        int id;
        int age;
        char name[20];
    };
    
    int main(void)
    {
        struct student stu;   
        struct student *pstu;
        
        stu.gender = '1';
        stu.id = 9527;
        stu.age = 30;
        strcpy(stu.name, "James");
        
        // 根据 'id地址' 获取结构体的地址
        //     container_of(ptr, type, member)
        pstu = container_of(&stu.id, struct student, id);
        
        // 根据获取到的结构体student的地址,访问其他成员
        printf("gender = %c
    ", pstu->gender);
        printf("age = %d
    ", pstu->age);
        printf("name = %s
    ", pstu->name);
        
        
        return 0;
    }

    2. Linux 内核链表实现及使用Demo

     2.1 内核链表实现 list.h 文件(部分函数,主要在学会怎么用)

    #ifndef _LINUX_LIST_H
    #define _LINUX_LIST_H
    
    
    /* 双向链表节点*/
    struct list_head {
        struct list_head *next, *prev;
    };
    
    /*
     * 初始化节点 -
     *
     * 设置name节点的前继节点和后继节点都指向name本身
     *
     * 相当于:
     *          struct list_head name;
     *          name->next = &name;
     *          name->prev = &name;
     * 即前驱指针和后继指针都指向自己
     */
    #define LIST_HEAD_INIT(name) { &(name), &(name) }
    
    /* 定义表头(节点) + 初始化 -
     *
     * 新建双向循环链表表头name,并设置name的前继节点
     * 和后继节点都是指向name本身
     */
    #define LIST_HEAD(name) 
        struct list_head name = LIST_HEAD_INIT(name)
    
    /* 初始化节点 -
     *
     * 将list节点的前继节点和后继节点都是指向list本身
     */
    static inline void INIT_LIST_HEAD(struct list_head *list)
    {
        list->next = list;
        list->prev = list;
    }
    
    /* 添加节点 -
     *
     * 将new插入到prev和next之间 在linux中 以'__'开头的函数
     * 意味着是内核内部接口,外部不应该调用该接口
     */
    static inline __list_add(struct list_head *new,
                           struct list_head *prev,
                           struct list_head *next)
    {
        next->prev = new;
        new->next = next;
        new->prev = prev;
        prev->next = new;
    }
    
    /* 添加new节点 -
     *
     * 将new添加到head之后,new称为head的后继节点
     */
    static inline void list_add(struct list_head *new, struct list_head *head)
    {
        __list_add(new, head, head->next);
    }
    
    /* 添加new节点 -
     *
     * 将new添加到head之前,即将new添加到双链表的尾部
     */
    static inline void list_add_tail(struct list_head *new, struct list_head *head)
    {
        __list_add(new, head->prev, head);  
    }
    
    /* 从双链表中删除节点 -
     *
     * 内核的内部接口,作用是从双链表中删除prev和next之间的节点
     */
    static inline void __list_del(struct list_head * prev, struct list_head * next)
    {
        next->prev = prev;
        prev->next = next;
    }
    
    /* 从双链表中删除entry节点 -
     *
     * 内核对外接口,从链表中删除entry节点
     */
    static inline void list_del(struct list_head *entry)
    {
        __list_del(entry->prev, entry->next);
    }
    
    /* 从双链表中删除entry节点 -
     *
     * 在双链表中删除entry节点,内核内部接口
     */
    static inline void __list_del_entry(struct list_head *entry)
    {
        __list_del(entry->prev, entry->next);
    }
    
    /* 从双链表中删除entry节点 -
     *
     * 内核对外接口,从双链表中删除entry节点,并将entry节点的前继节点
     * 和后继节点都指向entry本身
     */
    static inline void list_del_init(struct list_head * entry)
    {
        __list_del_entry(entry);
        INIT_LIST_HEAD(entry);
    }
    
    /* 
     * 用new 节点替换old节点 -
     */
    static inline void list_replace(struct list_head *old, struct list_head *new)
    {
        new->next = old->next;
        new->next->prev = new;
        new->prev = old->prev;
        new->prev->next = new;   
    }
    
    /* 
     * 用new 节点替换old节点 - 将替换的old随即又初始化
     */
    static inline void list_replace_init(struct list_head *old,
                        struct list_head *new)
    {
        list_replace(old, new);
        INIT_LIST_HEAD(old);
    }
    
    /* 
     * 判断双链表是否为空 -
     */
    static inline list_empty(const struct list_head *head)
    {
        return head->next == head; // 判读链表头的后继节点是不是头本身
    }
    
    /* offsetof 宏
     *
     * 获取'MEMBER'成员在结构体'TYPE'中的偏移量
     */
    #define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
    
    /* container_of 宏
     * 
     * 根据结构体'type'变量中的域成员变量(member)的指针(ptr)
     * 来获取指向整个结构体变量的指针
     */
    #define container_of(ptr,type,member) ({    
        const typeof( ((type *)0)->member) *__mptr = (ptr); 
        (type *)( (char *)__mptr - offsetof(type,member));})
    
    
    /* 
     * 遍历双向循环链表
     * 
     * 通常用于获取节点,而不能用到删除节点的场景
     */
    #define list_for_each(pos, head)    
        for(pos =(head)->next; pos != (head); pos = pos->next)
            
    
    /* 
     * 遍历双向循环链表
     *
     * 通常删除节点的场景
     */    
    #define list_for_each_safe(pos, n, head)    
        for(pos = (head)->next, n = pos->next; pos != (head);   
            pos = n, n = pos->next)
    
            
    /*
     *  获取节点 -
     *
     * 调用container_of 宏, 根据结构体(type)变量中的域成员变量(member)
     * 的指针(ptr)来获取指向整个结构体变量的指针
     */ 
    #define list_entry(ptr, type, member)   
        container_of(ptr, type, member);
    
    
    #endif //_LINUX_LIST_H

    2.2 使用内核链表Demo   test.c文件

    /*
        Linux 内核链表使用测试代码   
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include "list.h"
    
    struct person {
        int age;
        char name[20];
        struct list_head list;  //将链表嵌入到结构中
    };
    
    
    int main(int argc, char *argv[])
    {
        struct person *Pperson, *new;
        struct person person_head;
        struct list_head *pos, *next;
        int i;
        
        // 初始化双向循环链表头
        // INIT_LIST_HEAD(struct list_head *list)
        INIT_LIST_HEAD(&person_head.list);
        
        // 添加节点
        for(i=0; i<5; i++) {
            Pperson = (struct person *)malloc(sizeof(struct person));
            Pperson->age = (i+1)*10;
            sprintf(Pperson->name, "%d", i+1);
            
            // 将节点插入到链表的末尾
            // 要插入到头,使用list_add
            // list_add(struct list_head *new, struct list_head *head)
            // list_add_tail(struct list_head *new, struct list_head *head)       
            list_add_tail(&(Pperson->list), &(person_head.list));   
        }
        
        // 遍历链表
        printf("===== 1st iterator d-link ====
    ");
        // 判断链表是否为空
        // list_empty(const struct list_head *head)
        if(!list_empty(&person_head.list)) {
            // list_for_each(pos, head)
            list_for_each(pos, &person_head.list) {
                // list_entry(ptr, type, member)
                Pperson = list_entry(pos, struct person, list); 
                printf("name:%-2s, age:%d
    ", Pperson->name, Pperson->age);
            }
        }
        
        // 删除节点为10的节点
        printf("==== delete node(age:10) ====
    ");
        // list_for_each_safe(pos, n, head)
        list_for_each_safe(pos, next, &person_head.list) {
            Pperson = list_entry(pos, struct person, list);
            if(Pperson->age == 10) {
                // list_del_init(struct list_head * entry)
                list_del_init(pos);
                free(Pperson);
            }
        }
        
        // 再次遍历链表
        printf("==== 2nd iterator d-link ====
    ");
        list_for_each(pos, &person_head.list) {
            Pperson = list_entry(pos, struct person, list); 
            printf("name:%-2s, age:%d
    ", Pperson->name, Pperson->age);
        }
        
        // 替换节点
        printf("==== replace node(age:20) ====
    ");
        new = (struct person *)malloc(sizeof(struct person));
        new->age = 200;
        list_for_each_safe(pos, next, &person_head.list) {
            Pperson = list_entry(pos, struct person, list);
            if(Pperson->age == 20) {
                // list_replace(struct list_head *old, struct list_head *new);
                list_replace(&(Pperson->list), &(new->list));
            }
        }
        
        // 再次遍历链表
        printf("==== 3rd iterator d-link ====
    ");
        list_for_each(pos, &person_head.list) {
            Pperson = list_entry(pos, struct person, list); 
            printf("name:%-2s, age:%d
    ", Pperson->name, Pperson->age);
        }
        
        
        // 释放资源
        list_for_each_safe(pos, next, &person_head.list) {
            Pperson = list_entry(pos, struct person, list);
            list_del_init(pos);
            free(Pperson);
        }
        
        return 0;
    }

    2.3  运行

    3. 鸣谢

    感谢下面两位博主的分享,祝二位工作开心,期待更多分享~ Thanks again.
    http://www.cnblogs.com/skywang12345/p/3562146.html https://blog.csdn.net/viewsky11/article/details/53189372

    4. 后记

      

    后天八卦

    后天八卦又称文王八卦,是周文王在改造先天八卦而创制的。文王在研究先天

    八卦的过程中发现它与实际有不符的地方,于是改变了方位,使其符合自然万

    物的变化规律,他在卦中增加了数字九,同时多出了中土的位置。后人在实际

    应用中,大多以先天八卦为“体”,后天八卦图为“用”,天干、地支、五行生克等

    要素都以后天八卦为依据。

    先天八卦之数:

    乾为一,兑为二,离为三,震为四,巽为五,坎为六,艮为七,坤为八

    后天八卦之数:

    坎为一,坤为二,震为三,巽为四,中为五,乾为六,兑为七,艮为八,离为九。

  • 相关阅读:
    SpringBoot
    mysql 8版本使用注意
    RocketMQ服务搭建_1
    otter使用
    greenplum
    一、Linux概述 二、Linux的安装 三、Linux的常用命令(重点)
    一、DAO设计模式 二、DAO设计模式的优化 三、JDBC中的事务,连接池的使用
    一、JDBC的概述 二、通过JDBC实现对数据的CRUD操作 三、封装JDBC访问数据的工具类 四、通过JDBC实现登陆和注册 五、防止SQL注入
    一、MySQL中的索引 二、MySQL中的函数 三、MySQL数据库的备份和恢复 四、数据库设计和优化(重点)
    一、TCL事务控制语言 二、MySQL中的约束 三、多表查询(重点) 四、用户的创建和授权 五、MySQL中的索引
  • 原文地址:https://www.cnblogs.com/zhaoosheLBJ/p/9391926.html
Copyright © 2011-2022 走看看