1. 程式人生 > >Linux 內核鏈表實現和使用(一陰一陽即為道~)

Linux 內核鏈表實現和使用(一陰一陽即為道~)

tails 靜態 -- 作用 sdn 分享 htm oid signed

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 內核鏈表實現和使用(一陰一陽即為道~)