日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

《Linux内核设计与实现》读书笔记(六)- 内核数据结构

發(fā)布時間:2024/4/18 linux 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Linux内核设计与实现》读书笔记(六)- 内核数据结构 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

內(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個信息:

  • 父節(jié)點的地址
  • 本節(jié)點的顏色
  • 這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ù)。

    /* 刪除節(jié)點時,如果被刪除的節(jié)點左子樹==NULL或右子樹==NULL或左右子樹都==NULL* 那么只要把被刪除節(jié)點的左子樹或右子樹直接關(guān)聯(lián)到被刪節(jié)點的父節(jié)點上即可,剩下的就是調(diào)整各節(jié)點顏色。* 只有被刪節(jié)點是黑色才需要調(diào)整顏色,因為刪除紅色節(jié)點不影響紅黑樹的特性。** 被刪節(jié)點左右子樹都存在的情況下,其實就是用中序遍歷中被刪節(jié)點的下一個節(jié)點來替代被刪節(jié)點。* 代碼中的操作只是將各個指針指向新的位置而已。*/ void rb_erase(struct rb_node *node, struct rb_root *root) {struct rb_node *child, *parent;int color;if (!node->rb_left)child = node->rb_right;else if (!node->rb_right)child = node->rb_left;else{struct rb_node *old = node, *left;/* 尋找中序遍歷中被刪節(jié)點的下一個節(jié)點 */node = node->rb_right;while ((left = node->rb_left) != NULL)node = left;/* 替換要刪除的節(jié)點old */if (rb_parent(old)) {if (rb_parent(old)->rb_left == old)rb_parent(old)->rb_left = node;elserb_parent(old)->rb_right = node;} elseroot->rb_node = node;child = node->rb_right;parent = rb_parent(node);color = rb_color(node);if (parent == old) {parent = node;} else {if (child)rb_set_parent(child, parent);parent->rb_left = child;node->rb_right = old->rb_right;rb_set_parent(old->rb_right, node);}node->rb_parent_color = old->rb_parent_color;node->rb_left = old->rb_left;rb_set_parent(old->rb_left, node);goto color;}parent = rb_parent(node);color = rb_color(node);if (child)rb_set_parent(child, parent);if (parent){if (parent->rb_left == node)parent->rb_left = child;elseparent->rb_right = child;}elseroot->rb_node = child;color:if (color == RB_BLACK)__rb_erase_color(child, parent, root); }

    函數(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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。