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