《Linux内核设计与实现》读书笔记(六)- 内核数据结构
內(nèi)核數(shù)據(jù)結(jié)構(gòu)貫穿于整個內(nèi)核代碼中,這里介紹4個基本的內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
利用這4個基本的數(shù)據(jù)結(jié)構(gòu),可以在編寫內(nèi)核代碼時節(jié)約大量時間。
主要內(nèi)容:
- 鏈表
- 隊列
- 映射
- 紅黑樹
?
1. 鏈表
鏈表是linux內(nèi)核中最簡單,同時也是應(yīng)用最廣泛的數(shù)據(jù)結(jié)構(gòu)。
內(nèi)核中定義的是雙向鏈表。
?
1.1 頭文件簡介
內(nèi)核中關(guān)于鏈表定義的代碼位于: include/linux/list.h
list.h文件中對每個函數(shù)都有注釋,這里就不詳細(xì)說了。
其實剛開始只要先了解一個常用的鏈表操作(追加,刪除,遍歷)的實現(xiàn)方法,
其他方法基本都是基于這些常用操作的。
?
1.2 鏈表代碼的注意點
在閱讀list.h文件之前,有一點必須注意:linux內(nèi)核中的鏈表使用方法和一般數(shù)據(jù)結(jié)構(gòu)中定義的鏈表是有所不同的。
一般的雙向鏈表一般是如下的結(jié)構(gòu),
- 有個單獨的頭結(jié)點(head)
- 每個節(jié)點(node)除了包含必要的數(shù)據(jù)之外,還有2個指針(pre,next)
- pre指針指向前一個節(jié)點(node),next指針指向后一個節(jié)點(node)
- 頭結(jié)點(head)的pre指針指向鏈表的最后一個節(jié)點
- 最后一個節(jié)點的next指針指向頭結(jié)點(head)
具體見下圖:
?
傳統(tǒng)的鏈表有個最大的缺點就是不好共通化,因為每個node中的data1,data2等等都是不確定的(無論是個數(shù)還是類型)。
linux中的鏈表巧妙的解決了這個問題,linux的鏈表不是將用戶數(shù)據(jù)保存在鏈表節(jié)點中,而是將鏈表節(jié)點保存在用戶數(shù)據(jù)中。
linux的鏈表節(jié)點只有2個指針(pre和next),這樣的話,鏈表的節(jié)點將獨立于用戶數(shù)據(jù)之外,便于實現(xiàn)鏈表的共同操作。
?
具體見下圖:
?
linux鏈表中的最大問題是怎樣通過鏈表的節(jié)點來取得用戶數(shù)據(jù)?
和傳統(tǒng)的鏈表不同,linux的鏈表節(jié)點(node)中沒有包含用戶的用戶data1,data2等。
?
整個list.h文件中,我覺得最復(fù)雜的代碼就是獲取用戶數(shù)據(jù)的宏定義
#define list_entry(ptr, type, member) \container_of(ptr, type, member)這個宏沒什么特別的,主要是container_of這個宏
#define container_of(ptr, type, member) ({ \const typeof(((type *)0)->member)*__mptr = (ptr); \(type *)((char *)__mptr - offsetof(type, member)); })這里面的type一般是個結(jié)構(gòu)體,也就是包含用戶數(shù)據(jù)和鏈表節(jié)點的結(jié)構(gòu)體。
ptr是指向type中鏈表節(jié)點的指針
member則是type中定義鏈表節(jié)點是用的名字
比如:
struct student {int id;char* name;struct list_head list; };- type是struct student
- ptr是指向stuct list的指針,也就是指向member類型的指針
- member就是 list
下面分析一下container_of宏:
// 步驟1:將數(shù)字0強制轉(zhuǎn)型為type*,然后取得其中的member元素 ((type *)0)->member // 相當(dāng)于((struct student *)0)->list// 步驟2:定義一個臨時變量__mptr,并將其也指向ptr所指向的鏈表節(jié)點 const typeof(((type *)0)->member)*__mptr = (ptr);// 步驟3:計算member字段距離type中第一個字段的距離,也就是type地址和member地址之間的差 // offset(type, member)也是一個宏,定義如下: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)// 步驟4:將__mptr的地址 - type地址和member地址之間的差 // 其實也就是獲取type的地址步驟1,2,4比較容易理解,下面的圖以sturct student為例進(jìn)行說明步驟3:
首先需要知道 ((TYPE *)0) 表示將地址0轉(zhuǎn)換為 TYPE 類型的地址
由于TYPE的地址是0,所以((TYPE *)0)->MEMBER 也就是 MEMBER的地址和TYPE地址的差,如下圖所示:
?
1.3 使用示例
構(gòu)造了一個內(nèi)核模塊來實際使用一下內(nèi)核中的鏈表,代碼在CentOS6.3 x64上運行通過。
C代碼:
#include<linux/init.h> #include<linux/slab.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/list.h>MODULE_LICENSE("Dual BSD/GPL"); struct student {int id;char* name;struct list_head list; };void print_student(struct student*);static int testlist_init(void) {struct student *stu1, *stu2, *stu3, *stu4;struct student *stu;// init a list headLIST_HEAD(stu_head);// init four list nodesstu1 = kmalloc(sizeof(*stu1), GFP_KERNEL);stu1->id = 1;stu1->name = "wyb";INIT_LIST_HEAD(&stu1->list);stu2 = kmalloc(sizeof(*stu2), GFP_KERNEL);stu2->id = 2;stu2->name = "wyb2";INIT_LIST_HEAD(&stu2->list);stu3 = kmalloc(sizeof(*stu3), GFP_KERNEL);stu3->id = 3;stu3->name = "wyb3";INIT_LIST_HEAD(&stu3->list);stu4 = kmalloc(sizeof(*stu4), GFP_KERNEL);stu4->id = 4;stu4->name = "wyb4";INIT_LIST_HEAD(&stu4->list);// add the four nodes to headlist_add (&stu1->list, &stu_head);list_add (&stu2->list, &stu_head);list_add (&stu3->list, &stu_head);list_add (&stu4->list, &stu_head);// print each student from 4 to 1list_for_each_entry(stu, &stu_head, list){print_student(stu);}// print each student from 1 to 4list_for_each_entry_reverse(stu, &stu_head, list){print_student(stu);}// delete a entry stu2list_del(&stu2->list);list_for_each_entry(stu, &stu_head, list){print_student(stu);}// replace stu3 with stu2list_replace(&stu3->list, &stu2->list);list_for_each_entry(stu, &stu_head, list){print_student(stu);}return 0; }static void testlist_exit(void) {printk(KERN_ALERT "*************************\n");printk(KERN_ALERT "testlist is exited!\n");printk(KERN_ALERT "*************************\n"); }void print_student(struct student *stu) {printk (KERN_ALERT "======================\n");printk (KERN_ALERT "id =%d\n", stu->id);printk (KERN_ALERT "name=%s\n", stu->name);printk (KERN_ALERT "======================\n"); }module_init(testlist_init); module_exit(testlist_exit);Makefile:
obj-m += testlist.o#generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modulesrm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean:rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned安裝,卸載內(nèi)核模塊以及查看內(nèi)核模塊的運行結(jié)果:
insmod testlist.ko rmmod testlist dmesg | tail -100?
2. 隊列
內(nèi)核中的隊列是以字節(jié)形式保存數(shù)據(jù)的,所以獲取數(shù)據(jù)的時候,需要知道數(shù)據(jù)的大小。
如果從隊列中取得數(shù)據(jù)時指定的大小不對的話,取得數(shù)據(jù)會不完整或過大。
?
2.1 頭文件簡介
內(nèi)核中關(guān)于隊列定義的頭文件位于:<linux/kfifo.h> include/linux/kfifo.h
頭文件中定義的函數(shù)的實現(xiàn)位于:kernel/kfifo.c
?
2.2 隊列代碼的注意點
內(nèi)核隊列編程需要注意的是:
- 隊列的size在初始化時,始終設(shè)定為2的n次方
- 使用隊列之前將隊列結(jié)構(gòu)體中的鎖(spinlock)釋放
?
2.3 使用示例
構(gòu)造了一個內(nèi)核模塊來實際使用一下內(nèi)核中的隊列,代碼在CentOS6.3 x64上運行通過。
C代碼:
#include "kn_common.h"MODULE_LICENSE("Dual BSD/GPL"); struct student {int id;char* name; };static void print_student(struct student*);static int testkfifo_init(void) {struct kfifo *fifo;struct student *stu1, *stu2, *stu3, *stu4;struct student *stu_tmp;char* c_tmp;int i;// !!importent init a unlocked lockspinlock_t sl = SPIN_LOCK_UNLOCKED;// init kfifofifo = kfifo_alloc(4*sizeof(struct student), GFP_KERNEL, &sl);stu1 = kmalloc(sizeof(struct student), GFP_KERNEL);stu1->id = 1;stu1->name = "wyb1";kfifo_put(fifo, (char *)stu1, sizeof(struct student));stu2 = kmalloc(sizeof(struct student), GFP_KERNEL);stu2->id = 1;stu2->name = "wyb2";kfifo_put(fifo, (char *)stu2, sizeof(struct student));stu3 = kmalloc(sizeof(struct student), GFP_KERNEL);stu3->id = 1;stu3->name = "wyb3";kfifo_put(fifo, (char *)stu3, sizeof(struct student));stu4 = kmalloc(sizeof(struct student), GFP_KERNEL);stu4->id = 1;stu4->name = "wyb4";kfifo_put(fifo, (char *)stu4, sizeof(struct student));c_tmp = kmalloc(sizeof(struct student), GFP_KERNEL);printk(KERN_ALERT "current fifo length is : %d\n", kfifo_len(fifo));for (i=0; i < 4; i++) {kfifo_get(fifo, c_tmp, sizeof(struct student));stu_tmp = (struct student *)c_tmp;print_student(stu_tmp);printk(KERN_ALERT "current fifo length is : %d\n", kfifo_len(fifo));}printk(KERN_ALERT "current fifo length is : %d\n", kfifo_len(fifo));kfifo_free(fifo);kfree(c_tmp);return 0; }static void print_student(struct student *stu) {printk(KERN_ALERT "=========================\n");print_current_time(1);printk(KERN_ALERT "id = %d\n", stu->id);printk(KERN_ALERT "name = %s\n", stu->name);printk(KERN_ALERT "=========================\n"); }static void testkfifo_exit(void) {printk(KERN_ALERT "*************************\n");printk(KERN_ALERT "testkfifo is exited!\n");printk(KERN_ALERT "*************************\n"); }module_init(testkfifo_init); module_exit(testkfifo_exit);其中引用的kn_common.h文件:
#include<linux/init.h> #include<linux/slab.h> #include<linux/module.h> #include<linux/kernel.h> #include<linux/kfifo.h> #include<linux/time.h>void print_current_time(int);kn_common.h對應(yīng)的kn_common.c:
#include "kn_common.h"void print_current_time(int is_new_line) {struct timeval *tv;struct tm *t;tv = kmalloc(sizeof(struct timeval), GFP_KERNEL);t = kmalloc(sizeof(struct tm), GFP_KERNEL);do_gettimeofday(tv);time_to_tm(tv->tv_sec, 0, t);printk(KERN_ALERT "%ld-%d-%d %d:%d:%d",t->tm_year + 1900,t->tm_mon + 1,t->tm_mday,(t->tm_hour + 8) % 24,t->tm_min,t->tm_sec);if (is_new_line == 1)printk(KERN_ALERT "\n");kfree(tv);kfree(t); }Makefile:
obj-m += fifo.o fifo-objs := testkfifo.o kn_common.o#generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modulesrm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean:rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned安裝,卸載內(nèi)核模塊以及查看內(nèi)核模塊的運行結(jié)果:
insmod fifo.ko rmmod fifo dmesg | tail -40?
3. 映射
映射的有點想其他語言(C#或者python)中的字典類型,每個唯一的id對應(yīng)一個自定義的數(shù)據(jù)結(jié)構(gòu)。
?
3.1 頭文件簡介
內(nèi)核中關(guān)于映射定義的頭文件位于:<linux/idr.h> include/linux/idr.h
頭文件中定義的函數(shù)的實現(xiàn)位于:lib/idr.c
?
3.2 映射代碼的注意點
映射的使用需要注意的是,給自定義的數(shù)據(jù)結(jié)構(gòu)申請一個id的時候,不能直接申請id,先要分配id(函數(shù)idr_pre_get),分配成功后,在獲取一個id(函數(shù)idr_get_new)。
idr的結(jié)構(gòu)比較復(fù)雜,我也沒有很好的理解,但是csdn上有篇介紹linux idr結(jié)構(gòu)的博客寫的挺好,圖文并茂:http://blog.csdn.net/paomadi/article/details/8539794
3.3 使用示例
構(gòu)造了一個內(nèi)核模塊來實際使用一下內(nèi)核中的映射,代碼在CentOS6.3 x64上運行通過。
C代碼:
#include<linux/idr.h> #include "kn_common.h"MODULE_LICENSE("Dual BSD/GPL"); struct student {int id;char* name; };static int print_student(int, void*, void*);static int testidr_init(void) {DEFINE_IDR(idp);struct student *stu[4];// struct student *stu_tmp;int id, ret, i;// init 4 struct studentfor (i=0; i<4; i++) {stu[i] = kmalloc(sizeof(struct student), GFP_KERNEL);stu[i]->id = i;stu[i]->name = "wyb";}// add 4 student to idrprint_current_time(0);for (i=0; i < 4; i++) {do {if (!idr_pre_get(&idp, GFP_KERNEL))return -ENOSPC;ret = idr_get_new(&idp, stu[i], &id);printk(KERN_ALERT "id=%d\n", id);} while(ret == -EAGAIN);}// display all student in idridr_for_each(&idp, print_student, NULL);idr_destroy(&idp);kfree(stu[0]);kfree(stu[1]);kfree(stu[2]);kfree(stu[3]);return 0; }static int print_student(int id, void *p, void *data) {struct student* stu = p;printk(KERN_ALERT "=========================\n");print_current_time(0);printk(KERN_ALERT "id = %d\n", stu->id);printk(KERN_ALERT "name = %s\n", stu->name);printk(KERN_ALERT "=========================\n");return 0; }static void testidr_exit(void) {printk(KERN_ALERT "*************************\n");print_current_time(0);printk(KERN_ALERT "testidr is exited!\n");printk(KERN_ALERT "*************************\n"); }module_init(testidr_init); module_exit(testidr_exit);注:其中用到的kn_common.h和kn_common.c文件與隊列的示例中一樣。
Makefile:
obj-m += idr.o idr-objs := testidr.o kn_common.o#generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modulesrm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean:rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned安裝,卸載內(nèi)核模塊以及查看內(nèi)核模塊的運行結(jié)果:
insmod idr.ko rmmod idr dmesg | tail -30?
4. 紅黑樹
紅黑樹由于節(jié)點顏色的特性,保證其是一種自平衡的二叉搜索樹。
紅黑樹的一系列規(guī)則雖然實現(xiàn)起來比較復(fù)雜,但是遵循起來卻比較簡單,而且紅黑樹的插入,刪除性能也還不錯。
所以紅黑樹在內(nèi)核中的應(yīng)用非常廣泛,掌握好紅黑樹,即有利于閱讀內(nèi)核源碼,也可以在自己的代碼中借鑒這種數(shù)據(jù)結(jié)構(gòu)。
紅黑樹必須滿足的規(guī)則:
- 所有節(jié)點都有顏色,要么紅色,要么黑色
- 根節(jié)點是黑色,所有葉子節(jié)點也是黑色
- 葉子節(jié)點中不包含數(shù)據(jù)
- 非葉子節(jié)點都有2個子節(jié)點
- 如果一個節(jié)點是紅色,那么它的父節(jié)點和子節(jié)點都是黑色的
- 從任何一個節(jié)點開始,到其下葉子節(jié)點的路徑中都包含相同數(shù)目的黑節(jié)點
紅黑樹中最長的路徑就是紅黑交替的路徑,最短的路徑是全黑節(jié)點的路徑,再加上根節(jié)點和葉子節(jié)點都是黑色,
從而可以保證紅黑樹中最長路徑的長度不會超過最短路徑的2倍。
?
4.1 頭文件簡介
內(nèi)核中關(guān)于紅黑樹定義的頭文件位于:<linux/rbtree.h> include/linux/rbtree.h
頭文件中定義的函數(shù)的實現(xiàn)位于:lib/rbtree.c
?
4.2 紅黑樹代碼的注意點
內(nèi)核中紅黑樹的使用和鏈表(list)有些類似,是將紅黑樹的節(jié)點放入自定義的數(shù)據(jù)結(jié)構(gòu)中來使用的。
首先需要注意的一點是紅黑樹節(jié)點的定義:
struct rb_node {unsigned long rb_parent_color; #define RB_RED 0 #define RB_BLACK 1struct rb_node *rb_right;struct rb_node *rb_left; } __attribute__((aligned(sizeof(long))));剛開始看到這個定義的時候,我覺得很奇怪,等到看懂了之后,才知道原來作者巧妙的利用內(nèi)存對齊來將2個內(nèi)容存入到一個字段中(不服不行啊^_^!)。
字段 rb_parent_color 中保存了2個信息:
這2個信息是如何存入一個字段的呢?主要在于 __attribute__((aligned(sizeof(long))));
這行代碼的意思就是 struct rb_node 在內(nèi)存中的地址需要按照4 bytes或者8 bytes對齊。
注:sizeof(long) 在32bit系統(tǒng)中是4 bytes,在64bit系統(tǒng)中是8 bytes
?
struct rb_node的地址按4 bytes對齊,意味著分配的地址都是4的倍數(shù)。
4 的二進(jìn)制為 100 ,所以申請分配的 struct rb_node 的地址的最后2位始終是零,
struct rb_node 的字段 rb_parent_color 就是利用最后一位來保存節(jié)點的顏色信息的。
?
明白了這點之后,rb_tree.h 中很多宏的定義也就很好懂了。
/* rb_parent_color 保存了父節(jié)點的地址和本節(jié)點的顏色 *//* 將 rb_parent_color 的最后2位置成0,即將顏色信息去掉,剩下的就是parent節(jié)點的地址 */ #define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3))/* 取得 rb_parent_color 二進(jìn)制表示的最后一位,即用于保存顏色信息的那一位 */ #define rb_color(r) ((r)->rb_parent_color & 1)/* 將 rb_parent_color 二進(jìn)制表示的最后一位置為0,即置為紅色 */ #define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0)/* 將 rb_parent_color 二進(jìn)制表示的最后一位置為1,即置為黑色 */ #define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0)還有需要重點看的就是rb_tree.c中的5個函數(shù),下面對這5個函數(shù)進(jìn)行一些注釋:
函數(shù)1:左旋操作,當(dāng)右子樹的長度過大導(dǎo)致樹不平衡時,進(jìn)行左旋操作
/** 左旋操作其實就3個動作:見圖left* 1. node的右子樹關(guān)聯(lián)到right的左子樹* 2. right的左子樹關(guān)聯(lián)到node* 3. right取代node的位置* 其他帶代碼都是一些相應(yīng)的parent指針的變化*/ static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) {/* 初始化相對于node節(jié)點的父節(jié)點(圖中的P)和右節(jié)點(圖中的R) */struct rb_node *right = node->rb_right;struct rb_node *parent = rb_parent(node);/* 步驟1 */if ((node->rb_right = right->rb_left))rb_set_parent(right->rb_left, node);/* 步驟2 */right->rb_left = node;rb_set_parent(right, parent);/* node的parent NOT NULL 時,right取代原先的node的位置 */if (parent){if (node == parent->rb_left)parent->rb_left = right;elseparent->rb_right = right;}/* node的parent NULL 時,說明node原先時root節(jié)點,將新的root指向root即可 */elseroot->rb_node = right;rb_set_parent(node, right); }左旋操作圖解:
?
函數(shù)2:右旋操作,和左旋操作類似。
?
函數(shù)3:追加節(jié)點后,設(shè)置此節(jié)點的顏色。
/** 本函數(shù)沒有插入節(jié)點的功能,只是在插入新節(jié)點后,設(shè)置新節(jié)點的顏色,從而保證紅黑樹的平衡性。* 新插入的節(jié)點默認(rèn)都是紅色的。* * 下面的代碼看著復(fù)雜,其實只要時時記住紅黑樹的幾個重要特性,就會發(fā)現(xiàn)下面的都是在盡量保持住紅黑樹的這些特性。* 1. 無論從哪個節(jié)點開始,到其葉子節(jié)點的路徑中包含的黑色節(jié)點個數(shù)時一樣的* 2. 不能有連續(xù)的2個紅色節(jié)點,即父節(jié)點和子節(jié)點不能同時為紅色* 所以最簡單的情況就是:插入節(jié)點的父節(jié)點是黑色的。那么插入一個紅節(jié)點后不會有任何影響。* 3. 左旋操作有減少右子樹高度的作用* 4. 同理,右旋操作有減少左子樹高度的作用*/ void rb_insert_color(struct rb_node *node, struct rb_root *root) {struct rb_node *parent, *gparent;while ((parent = rb_parent(node)) && rb_is_red(parent)){gparent = rb_parent(parent);/* parent 是 gparent的左子樹時 */if (parent == gparent->rb_left){{/* gparent的左右子樹的黑色節(jié)點都增加一個,仍然平衡 */register struct rb_node *uncle = gparent->rb_right;if (uncle && rb_is_red(uncle)){rb_set_black(uncle);rb_set_black(parent);rb_set_red(gparent);node = gparent;continue;}}/* node為parent右子樹時 */if (parent->rb_right == node){register struct rb_node *tmp;/* 左旋后,parent的位置被node取代,然后再交換parent和node的位置,* 相當(dāng)于node是parent的左子樹* 由于node和parent都是紅色(否則到不了這一步),parent左右子樹的黑色節(jié)點數(shù)仍然是相等的*/__rb_rotate_left(parent, root);tmp = parent;parent = node;node = tmp;}/* parent 紅->黑,gparent左子樹比右子樹多一個黑色節(jié)點* 右旋后,gparent左子樹高度減一,減少的節(jié)點即parent,減少了一個黑色節(jié)點,parent變?yōu)樾碌膅parent。* 所以右旋后,新的gparent的左右子樹的黑色節(jié)點數(shù)再次平衡了*/rb_set_black(parent);rb_set_red(gparent);__rb_rotate_right(gparent, root);/* parent 是 gparent的右子樹時,和上面的過程類似 */} else {{register struct rb_node *uncle = gparent->rb_left;if (uncle && rb_is_red(uncle)){rb_set_black(uncle);rb_set_black(parent);rb_set_red(gparent);node = gparent;continue;}}if (parent->rb_left == node){register struct rb_node *tmp;__rb_rotate_right(parent, root);tmp = parent;parent = node;node = tmp;}rb_set_black(parent);rb_set_red(gparent);__rb_rotate_left(gparent, root);}}rb_set_black(root->rb_node); }
函數(shù)4:刪除一個節(jié)點,并且調(diào)整刪除后各節(jié)點的顏色。其中調(diào)整節(jié)點顏色其實是另一個單獨的函數(shù)。
函數(shù)5:刪除一個黑色節(jié)點后,重新調(diào)整相關(guān)節(jié)點的顏色。
/* 這里的node就是上面函數(shù)中的child,所有node節(jié)點的左右子樹肯定都是NULL* 不滿足紅黑樹規(guī)則的就是從parent節(jié)點開始的子樹,只要給從parent開始的子樹增加一個黑色節(jié)點就行* 如果從parent節(jié)點開始的節(jié)點全是黑色,node和parent都繼續(xù)向上移動*/ static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,struct rb_root *root) {struct rb_node *other;/* (node不為NULL 且 node是黑色的) 或者 node == NULL */while ((!node || rb_is_black(node)) && node != root->rb_node){if (parent->rb_left == node){other = parent->rb_right;if (rb_is_red(other)){rb_set_black(other);rb_set_red(parent);__rb_rotate_left(parent, root);other = parent->rb_right;}/* 如果從parent節(jié)點開始的節(jié)點全是黑色,node和parent都繼續(xù)向上移動 */if ((!other->rb_left || rb_is_black(other->rb_left)) &&(!other->rb_right || rb_is_black(other->rb_right))){rb_set_red(other);node = parent;parent = rb_parent(node);}else{if (!other->rb_right || rb_is_black(other->rb_right)){rb_set_black(other->rb_left);rb_set_red(other);__rb_rotate_right(other, root);other = parent->rb_right;}rb_set_color(other, rb_color(parent));rb_set_black(parent);rb_set_black(other->rb_right);__rb_rotate_left(parent, root);node = root->rb_node;break;}}else{other = parent->rb_left;if (rb_is_red(other)){rb_set_black(other);rb_set_red(parent);__rb_rotate_right(parent, root);other = parent->rb_left;}if ((!other->rb_left || rb_is_black(other->rb_left)) &&(!other->rb_right || rb_is_black(other->rb_right))){rb_set_red(other);node = parent;parent = rb_parent(node);}else{if (!other->rb_left || rb_is_black(other->rb_left)){rb_set_black(other->rb_right);rb_set_red(other);__rb_rotate_left(other, root);other = parent->rb_left;}rb_set_color(other, rb_color(parent));rb_set_black(parent);rb_set_black(other->rb_left);__rb_rotate_right(parent, root);node = root->rb_node;break;}}}if (node)rb_set_black(node); }?
4.3 使用示例
構(gòu)造了一個內(nèi)核模塊來實際使用一下內(nèi)核中的紅黑樹,代碼在CentOS6.3 x64上運行通過。
C代碼:
#include<linux/rbtree.h> #include <linux/string.h> #include "kn_common.h"MODULE_LICENSE("Dual BSD/GPL"); struct student {int id;char* name;struct rb_node node; };static int insert_student(struct student*, struct rb_root*); static int remove_student(struct student*, struct rb_root*); static int display_student(struct rb_root*, int); static void display_student_from_small(struct rb_node*); static void display_student_from_big(struct rb_node*); static void print_student(struct student*);static int testrbtree_init(void) { #define N 10struct rb_root root = RB_ROOT;struct student *stu[N];char tmp_name[5] = {'w', 'y', 'b', '0', '\0'};int i;// init N struct studentfor (i=0; i<N; i++){stu[i] = kmalloc(sizeof(struct student), GFP_KERNEL);stu[i]->id = i;stu[i]->name = kmalloc(sizeof(char)*5, GFP_KERNEL);tmp_name[3] = (char)(i+48);strcpy(stu[i]->name, tmp_name);// stu_name[3] = (char)(i+48);stu[i]->node.rb_left = NULL;stu[i]->node.rb_right = NULL;}for (i=0; i < N; ++i){printk(KERN_ALERT "id=%d name=%s\n", stu[i]->id, stu[i]->name);}// add N student to rbtreeprint_current_time(0);for (i=0; i < N; i++)insert_student(stu[i], &root);// display all studentsprintk(KERN_ALERT "print from small to big!\n");display_student(&root, -1);printk(KERN_ALERT "print from big to small!\n");display_student(&root, 1);// delete student 8remove_student(stu[7], &root);display_student(&root, -1);// free all studentfor (i=0; i<N; ++i){kfree(stu[i]->name);kfree(stu[i]);}return 0; }static int insert_student(struct student* stu, struct rb_root* root) {struct rb_node* parent;struct rb_node* tmp_rb;struct student* tmp_stu;/* first time to insert node */if (!root->rb_node) {root->rb_node = &(stu->node);rb_set_parent(&(stu->node), NULL);rb_set_black(&(stu->node));return 0;}/* find where to insert node */tmp_rb = root->rb_node;while(tmp_rb){parent = tmp_rb;tmp_stu = rb_entry(tmp_rb, struct student, node);if (tmp_stu->id > stu->id) tmp_rb = parent->rb_left;else if (tmp_stu->id < stu->id)tmp_rb = parent->rb_right;elsebreak;}/* the student's id is already in the rbtree */if (tmp_rb){printk(KERN_ALERT "this student has been inserted!\n");return 1;}if (tmp_stu->id > stu->id)parent->rb_left = &(stu->node);elseparent->rb_right = &(stu->node);rb_set_parent(&(stu->node), parent);rb_insert_color(&(stu->node), root);return 0; }static int remove_student(struct student* stu, struct rb_root* root) {rb_erase(&(stu->node), root);return 0; }static int display_student(struct rb_root *root, int order) {if (!root->rb_node)return 1;if (order < 0)display_student_from_small(root->rb_node);elsedisplay_student_from_big(root->rb_node);return 0; }static void display_student_from_small(struct rb_node* node) {struct student *tmp_stu;if (node){display_student_from_small(node->rb_left);tmp_stu = rb_entry(node, struct student, node);print_student(tmp_stu);display_student_from_small(node->rb_right);} }static void display_student_from_big(struct rb_node* node) {struct student *tmp_stu;if (node){display_student_from_big(node->rb_right);tmp_stu = rb_entry(node, struct student, node);print_student(tmp_stu);display_student_from_big(node->rb_left);} }static void print_student(struct student* stu) {printk(KERN_ALERT "=========================\n");print_current_time(0);printk(KERN_ALERT "id=%d\tname=%s\n", stu->id, stu->name);printk(KERN_ALERT "=========================\n"); }static void testrbtree_exit(void) {printk(KERN_ALERT "*************************\n");print_current_time(0);printk(KERN_ALERT "testrbtree is exited!\n");printk(KERN_ALERT "*************************\n");}module_init(testrbtree_init); module_exit(testrbtree_exit);注:其中用到的kn_common.h和kn_common.c文件與隊列的示例中一樣。
Makefile:
obj-m += rbtree.o rbtree-objs := testrbtree.o kn_common.o#generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL) #complie object all:make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modulesrm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned #clean clean:rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned安裝,卸載內(nèi)核模塊以及查看內(nèi)核模塊的運行結(jié)果:
insmod rbtree.ko rmmod rbtree dmesg | tail -135總結(jié)
以上是生活随笔為你收集整理的《Linux内核设计与实现》读书笔记(六)- 内核数据结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Linux内核设计与实现》读书笔记(五
- 下一篇: 《Linux内核设计与实现》读书笔记(七