码迷,mamicode.com
首页 > 系统相关 > 详细

Linux 内核链表实现和使用(一阴一阳即为道~)

时间:2018-08-04 23:17:24      阅读:259      评论:0      收藏:0      [点我收藏+]

标签:静态   ring   链表实现   entry   data   成员变量   对齐   字节   地方   

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\n", gender_offset);  // 0
    printf("id_offset = %d\n", id_offset);          // 4
    printf("age_offset = %d\n", age_offset);        // 8
    printf("name_offset = %d\n", name_offset);      // 12
    
    struct student zhao;
    printf("sizeof zhao = [%d]\n", 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\n", pstu->gender);
    printf("age = %d\n", pstu->age);
    printf("name = %s\n", 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 ====\n");
    // 判断链表是否为空
    // 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\n", Pperson->name, Pperson->age);
        }
    }
    
    // 删除节点为10的节点
    printf("==== delete node(age:10) ====\n");
    // 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 ====\n");
    list_for_each(pos, &person_head.list) {
        Pperson = list_entry(pos, struct person, list); 
        printf("name:%-2s, age:%d\n", Pperson->name, Pperson->age);
    }
    
    // 替换节点
    printf("==== replace node(age:20) ====\n");
    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 ====\n");
    list_for_each(pos, &person_head.list) {
        Pperson = list_entry(pos, struct person, list); 
        printf("name:%-2s, age:%d\n", 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. 后记

  

后天八卦

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

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

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

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

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

 

先天八卦之数:

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

后天八卦之数:

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

 

技术分享图片

Linux 内核链表实现和使用(一阴一阳即为道~)

标签:静态   ring   链表实现   entry   data   成员变量   对齐   字节   地方   

原文地址:https://www.cnblogs.com/zhaoosheLBJ/p/9391926.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!