Linux內核設計與實現 讀書筆記(6)內核數據結構

主要內容:

  • 鏈表
  • 隊列
  • 映射
  • 紅黑樹

 

1. 鏈表

鏈表是linux內核中最簡單,同時也是應用最廣泛的數據結構。

內核中定義的是雙向鏈表。

 

1.1 頭文件簡介

內核中關於鏈表定義的代碼位於: include/linux/list.h

list.h文件中對每個函數都有註釋,這裏就不詳細說了。

其實剛開始只要先了解一個常用的鏈表操作(追加,刪除,遍歷)的實現方法,

其他方法基本都是基於這些常用操作的。

 

1.2 鏈表代碼的注意點

在閱讀list.h文件之前,有一點必須注意:linux內核中的鏈表使用方法和一般數據結構中定義的鏈表是有所不同的。

一般的雙向鏈表一般是如下的結構,

  • 有個單獨的頭結點(head)
  • 每個節點(node)除了包含必要的數據之外,還有2個指針(pre,next)
  • pre指針指向前一個節點(node),next指針指向後一個節點(node)
  • 頭結點(head)的pre指針指向鏈表的最後一個節點
  • 最後一個節點的next指針指向頭結點(head)

具體見下圖:

list1

 

傳統的鏈表有個最大的缺點就是不好共通化,因爲每個node中的data1,data2等等都是不確定的(無論是個數還是類型)。

linux中的鏈表巧妙的解決了這個問題,linux的鏈表不是將用戶數據保存在鏈表節點中,而是將鏈表節點保存在用戶數據中。

linux的鏈表節點只有2個指針(pre和next),這樣的話,鏈表的節點將獨立於用戶數據之外,便於實現鏈表的共同操作。

 

具體見下圖:

list2

 

linux鏈表中的最大問題是怎樣通過鏈表的節點來取得用戶數據?

和傳統的鏈表不同,linux的鏈表節點(node)中沒有包含用戶的用戶data1,data2等。

 

整個list.h文件中,我覺得最複雜的代碼就是獲取用戶數據的宏定義

#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一般是個結構體,也就是包含用戶數據和鏈表節點的結構體。

ptr是指向type中鏈表節點的指針

member則是type中定義鏈表節點是用的名字

比如:

struct student
{
    int id;
    char* name;
    struct list_head list;
};
  • type是struct student
  • ptr是指向stuct list的指針,也就是指向member類型的指針
  • member就是 list

下面分析一下container_of宏:

複製代碼
// 步驟1:將數字0強制轉型爲type*,然後取得其中的member元素
((type *)0)->member  // 相當於((struct student *)0)->list

// 步驟2:定義一個臨時變量__mptr,並將其也指向ptr所指向的鏈表節點
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爲例進行說明步驟3:

首先需要知道 ((TYPE *)0) 表示將地址0轉換爲 TYPE 類型的地址

由於TYPE的地址是0,所以((TYPE *)0)->MEMBER 也就是 MEMBER的地址和TYPE地址的差,如下圖所示:

step3

 

1.3 使用示例

構造了一個內核模塊來實際使用一下內核中的鏈表,代碼在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 head
    LIST_HEAD(stu_head);

    // init four list nodes
    stu1 = 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 head
    list_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 1
    list_for_each_entry(stu, &stu_head, list)
    {
        print_student(stu);
    }
    // print each student from 1 to 4
    list_for_each_entry_reverse(stu, &stu_head, list)
    {
        print_student(stu);
    }

    // delete a entry stu2
    list_del(&stu2->list);
    list_for_each_entry(stu, &stu_head, list)
    {
        print_student(stu);
    }

    // replace stu3 with stu2
    list_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) modules
    rm -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
複製代碼

安裝,卸載內核模塊以及查看內核模塊的運行結果:

insmod testlist.ko
rmmod testlist
dmesg | tail -100

 

2. 隊列

內核中的隊列是以字節形式保存數據的,所以獲取數據的時候,需要知道數據的大小。

如果從隊列中取得數據時指定的大小不對的話,取得數據會不完整或過大。

 

2.1 頭文件簡介

內核中關於隊列定義的頭文件位於:<linux/kfifo.h> include/linux/kfifo.h

頭文件中定義的函數的實現位於:kernel/kfifo.c

 

2.2 隊列代碼的注意點

內核隊列編程需要注意的是:

  • 隊列的size在初始化時,始終設定爲2的n次方
  • 使用隊列之前將隊列結構體中的鎖(spinlock)釋放

 

2.3 使用示例

構造了一個內核模塊來實際使用一下內核中的隊列,代碼在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 lock
    spinlock_t sl = SPIN_LOCK_UNLOCKED;

    // init kfifo
    fifo = 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對應的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) modules
    rm -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
複製代碼

安裝,卸載內核模塊以及查看內核模塊的運行結果:

insmod fifo.ko
rmmod fifo
dmesg | tail -40

 

3. 映射

映射的有點想其他語言(C#或者python)中的字典類型,每個唯一的id對應一個自定義的數據結構。

 

3.1 頭文件簡介

內核中關於映射定義的頭文件位於:<linux/idr.h> include/linux/idr.h

頭文件中定義的函數的實現位於:lib/idr.c

 

3.2 映射代碼的注意點

映射的使用需要注意的是,給自定義的數據結構申請一個id的時候,不能直接申請id,先要分配id(函數idr_pre_get),分配成功後,在獲取一個id(函數idr_get_new)。

idr的結構比較複雜,我也沒有很好的理解,但是csdn上有篇介紹linux idr結構的博客寫的挺好,圖文並茂:http://blog.csdn.net/paomadi/article/details/8539794

3.3 使用示例

構造了一個內核模塊來實際使用一下內核中的映射,代碼在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 student
    for (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 idr
    print_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 idr
    idr_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) modules
    rm -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
複製代碼

安裝,卸載內核模塊以及查看內核模塊的運行結果:

insmod idr.ko
rmmod idr
dmesg | tail -30

 

4. 紅黑樹

紅黑樹由於節點顏色的特性,保證其是一種自平衡的二叉搜索樹。

紅黑樹的一系列規則雖然實現起來比較複雜,但是遵循起來卻比較簡單,而且紅黑樹的插入,刪除性能也還不錯。

所以紅黑樹在內核中的應用非常廣泛,掌握好紅黑樹,即有利於閱讀內核源碼,也可以在自己的代碼中借鑑這種數據結構。

紅黑樹必須滿足的規則:

  • 所有節點都有顏色,要麼紅色,要麼黑色
  • 根節點是黑色,所有葉子節點也是黑色
  • 葉子節點中不包含數據
  • 非葉子節點都有2個子節點
  • 如果一個節點是紅色,那麼它的父節點和子節點都是黑色的
  • 從任何一個節點開始,到其下葉子節點的路徑中都包含相同數目的黑節點

紅黑樹中最長的路徑就是紅黑交替的路徑,最短的路徑是全黑節點的路徑,再加上根節點和葉子節點都是黑色,

從而可以保證紅黑樹中最長路徑的長度不會超過最短路徑的2倍。

 

4.1 頭文件簡介

內核中關於紅黑樹定義的頭文件位於:<linux/rbtree.h> include/linux/rbtree.h

頭文件中定義的函數的實現位於:lib/rbtree.c

 

4.2 紅黑樹代碼的注意點

內核中紅黑樹的使用和鏈表(list)有些類似,是將紅黑樹的節點放入自定義的數據結構中來使用的。

首先需要注意的一點是紅黑樹節點的定義:

複製代碼
struct rb_node
{
    unsigned long  rb_parent_color;
#define    RB_RED        0
#define    RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
複製代碼

剛開始看到這個定義的時候,我覺得很奇怪,等到看懂了之後,才知道原來作者巧妙的利用內存對齊來將2個內容存入到一個字段中(不服不行啊^_^!)。

字段 rb_parent_color 中保存了2個信息:

  1. 父節點的地址
  2. 本節點的顏色

這2個信息是如何存入一個字段的呢?主要在於 __attribute__((aligned(sizeof(long))));

這行代碼的意思就是 struct rb_node 在內存中的地址需要按照4 bytes或者8 bytes對齊。

注:sizeof(long) 在32bit系統中是4 bytes,在64bit系統中是8 bytes

 

struct rb_node的地址按4 bytes對齊,意味着分配的地址都是4的倍數。

4 的二進制爲 100 ,所以申請分配的 struct rb_node 的地址的最後2位始終是零,

struct rb_node 的字段 rb_parent_color 就是利用最後一位來保存節點的顏色信息的。

 

明白了這點之後,rb_tree.h 中很多宏的定義也就很好懂了。

複製代碼
/* rb_parent_color 保存了父節點的地址和本節點的顏色 */

/* 將 rb_parent_color 的最後2位置成0,即將顏色信息去掉,剩下的就是parent節點的地址 */
#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3))

/* 取得 rb_parent_color 二進制表示的最後一位,即用於保存顏色信息的那一位 */
#define rb_color(r)   ((r)->rb_parent_color & 1)

/* 將 rb_parent_color 二進制表示的最後一位置爲0,即置爲紅色 */
#define rb_set_red(r)  do { (r)->rb_parent_color &= ~1; } while (0)

/* 將 rb_parent_color 二進制表示的最後一位置爲1,即置爲黑色 */
#define rb_set_black(r)  do { (r)->rb_parent_color |= 1; } while (0)
複製代碼

還有需要重點看的就是rb_tree.c中的5個函數,下面對這5個函數進行一些註釋:

函數1:左旋操作,當右子樹的長度過大導致樹不平衡時,進行左旋操作

複製代碼
/*
 *  左旋操作其實就3個動作:見圖left
 *  1. node的右子樹關聯到right的左子樹
 *  2. right的左子樹關聯到node
 *  3. right取代node的位置
 *  其他帶代碼都是一些相應的parent指針的變化
 */
static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
{
    /* 初始化相對於node節點的父節點(圖中的P)和右節點(圖中的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;
        else
            parent->rb_right = right;
    }

    /* node的parent NULL 時,說明node原先時root節點,將新的root指向root即可 */
    else
        root->rb_node = right;
    rb_set_parent(node, right);
}
複製代碼

左旋操作圖解:

left

 

函數2:右旋操作,和左旋操作類似。

 

函數3:追加節點後,設置此節點的顏色。

複製代碼
/*
 *  本函數沒有插入節點的功能,只是在插入新節點後,設置新節點的顏色,從而保證紅黑樹的平衡性。
 *  新插入的節點默認都是紅色的。
 *  
 *  下面的代碼看着複雜,其實只要時時記住紅黑樹的幾個重要特性,就會發現下面的都是在儘量保持住紅黑樹的這些特性。
 *  1. 無論從哪個節點開始,到其葉子節點的路徑中包含的黑色節點個數時一樣的
 *  2. 不能有連續的2個紅色節點,即父節點和子節點不能同時爲紅色
 *  所以最簡單的情況就是:插入節點的父節點是黑色的。那麼插入一個紅節點後不會有任何影響。
 *  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的左右子樹的黑色節點都增加一個,仍然平衡 */
                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的位置,
                 * 相當於node是parent的左子樹
                 * 由於node和parent都是紅色(否則到不了這一步),parent左右子樹的黑色節點數仍然是相等的
                 */
                __rb_rotate_left(parent, root);
                tmp = parent;
                parent = node;
                node = tmp;
            }

            /* parent 紅->黑,gparent左子樹比右子樹多一個黑色節點
             * 右旋後,gparent左子樹高度減一,減少的節點即parent,減少了一個黑色節點,parent變爲新的gparent。
             * 所以右旋後,新的gparent的左右子樹的黑色節點數再次平衡了
             */
            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);
}
複製代碼


函數4:刪除一個節點,並且調整刪除後各節點的顏色。其中調整節點顏色其實是另一個單獨的函數。

複製代碼
/* 刪除節點時,如果被刪除的節點左子樹==NULL或右子樹==NULL或左右子樹都==NULL
 * 那麼只要把被刪除節點的左子樹或右子樹直接關聯到被刪節點的父節點上即可,剩下的就是調整各節點顏色。
 * 只有被刪節點是黑色才需要調整顏色,因爲刪除紅色節點不影響紅黑樹的特性。
 *
 * 被刪節點左右子樹都存在的情況下,其實就是用中序遍歷中被刪節點的下一個節點來替代被刪節點。
 * 代碼中的操作只是將各個指針指向新的位置而已。
 */
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;

        /* 尋找中序遍歷中被刪節點的下一個節點 */
        node = node->rb_right;
        while ((left = node->rb_left) != NULL)
            node = left;

        /* 替換要刪除的節點old */
        if (rb_parent(old)) {
            if (rb_parent(old)->rb_left == old)
                rb_parent(old)->rb_left = node;
            else
                rb_parent(old)->rb_right = node;
        } else
            root->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;
        else
            parent->rb_right = child;
    }
    else
        root->rb_node = child;

 color:
    if (color == RB_BLACK)
        __rb_erase_color(child, parent, root);
}
複製代碼

函數5:刪除一個黑色節點後,重新調整相關節點的顏色。

複製代碼
/* 這裏的node就是上面函數中的child,所有node節點的左右子樹肯定都是NULL
 * 不滿足紅黑樹規則的就是從parent節點開始的子樹,只要給從parent開始的子樹增加一個黑色節點就行
 * 如果從parent節點開始的節點全是黑色,node和parent都繼續向上移動
 */
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節點開始的節點全是黑色,node和parent都繼續向上移動 */
            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 使用示例

構造了一個內核模塊來實際使用一下內核中的紅黑樹,代碼在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 10
    struct rb_root root = RB_ROOT;
    struct student *stu[N];
    char tmp_name[5] = {'w', 'y', 'b', '0', '\0'};
    int i;

    // init N struct student
    for (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 rbtree
    print_current_time(0);
    for (i=0; i < N; i++)
        insert_student(stu[i], &root);

    // display all students
    printk(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 8
    remove_student(stu[7], &root);
    display_student(&root, -1);
    
    // free all student
    for (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;
        else
            break;
    }

    /* 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);
    else
        parent->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);
    else
        display_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) modules
    rm -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
複製代碼

安裝,卸載內核模塊以及查看內核模塊的運行結果:

insmod rbtree.ko
rmmod rbtree
dmesg | tail -135
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章