1. 程式人生 > >linux核心分析之rbtree的使用

linux核心分析之rbtree的使用

一、理論基礎

      紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色為紅色或黑色。在二叉查詢樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求:

性質1. 節點是紅色或黑色。

性質2. 根是黑色。

性質3. 所有葉子都是黑色(葉子是NIL節點)。

性質4. 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)

性質5. 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點。

rbtree with the following properties.
1.Every node has a value.
2.The value of any node is greater than the value of its left child and less than the value of its right child.
3.Every node is colored either red or black.
4.Every red node that is not a leaf has only black children.
5.Every path from the root to a leaf contains the same number of black nodes.
6.The root node is black.

 紅黑樹的每個節點上的屬性除了有一個key3個指標:parentleftright以外,還多了一個屬性:color,如果相應的指標域沒有,則設為NIL。

我們真正關心的是key域,之所以要構建rbtree,就是為了儘量減少查詢key域的時間,保證為O(logn)。

當然犧牲了些插入,刪除的時間,為了保證rbtree的五大特性,每次插入或者刪除一個node都必須

保證五大性質依然存在,因此要多出一些額外的操作。如圖:

圖中key域是數字,大小當然好比較,任意節點的左子樹上key一定小於右子樹上key。

二、rbtree在linux中的實現

程式碼位置

kernel/lib/rbtree.c

kernel/include/linux/rbtree.h

kernel/Documentation/rbtree.tx    //描述如何使用紅黑樹的說明文件

typedef struct st_rb_node {

       /**

        * 本結構體四位元組對齊,因而其地址中低位兩個bit永遠是0。//為什麼?

        * Linux核心開發人員非常愛惜記憶體,他們用其中一個空閒的位來儲存顏色資訊。

        * parent_color成員實際上包含了父節點指標和自己的顏色資訊。

        */

       unsigned long parent_color;//

儲存父節點的指標值(父節點的地址)同時儲存節點的color  

#define RB_RED   0

#define RB_BLACK 1

       structst_rb_node *left, *right;

}__attribute__((aligned(sizeof(long))))rb_node;

但在linux的實現中沒有key域,這已經是linux資料結構的一大特色,就是結構不包括資料,而是由資料和基本結構被包括在同一個struct中。(就像list_head中沒有data域,需要用連結串列的struct中要包含list_head域一樣)由結構體獲取資料資訊是通過CONTAINER_OF這個巨集來實現的,它利用了一些編譯器的特性,有興趣的可以參考Linux的連結串列原始碼。

rb_node結構體,被一個”__attribute__((aligned(sizeof(long))))”所包裝(非常重要,技巧就在這!),

”__attribute__((aligned(sizeof(long))))“的意思是把結構體的地址按“sizeof(long)”對齊,

對於32位機,sizeof(long)為4 (即結構體內的資料型別的地址為4的倍數)。對於64位機,sizeof(long)為8(結構體內的資料型別的地址為8的倍數).這個地方需要補一下位元組對齊的知識:

<<深入理解計算機系統>>3.10節

Linux沿 用的對齊策略是,2位元組資料型別(例如short)的地址必須是2的倍數,而較大的資料型別(例如int,int*,float和double)的地址必 須是4的倍數,而這兩個要求就意味著,一個short型別物件的地址最低位必須等於0(最低為1肯定是單數地址),任何int型別的物件或 指標的地址的最低兩位必須是0(只有最低兩位為0才能整除4)。

 

所以以4(或8)為倍數的地址以二進位制表示的特點是:以4為倍數(位元組對齊)的地址(32位機器)最後兩位肯定為零(看好了是儲存變數的地址,而不是變數),對於64位機器是後三位肯定為零。

對於rb-tree 中每一個節點,都需要標記一個顏色(只有兩種選擇: vs ),而這裡的技巧就在“__attribute__((aligned(sizeof(long))))”,因為紅黑樹的每個節點都用rb_node結構來表示,利用位元組對齊技巧,任何rb_node結構體的地址的低兩位肯定都是零,與其空著不用,還不如用它們表示顏 色,反正顏色就兩種,其實一位就已經夠了。unsigned long rb_parent_color變數有兩個作用(見名知義):

1.儲存父節點的地址(注意IA32父節點的地址為4的倍數,地址後面的2位沒有用上 ,IA64則父節點的地址為8的倍數,地址後面的3位沒有用上)。

2.用後2位,標識此節點的color(: vs  )


[cpp] view plain copy  print?
  1. include/linux/rbtree.h中有幾個與rb_parent_color相關的巨集  
  2. #definerb_parent(r)   ((struct rb_node*)((r)->rb_parent_color & ~3))
  3.  //取 r節點的父節點的地址,即把r節點的後兩位清零後與r節點儲存的父節點進行與操作。
  4.  //假設r->rb_parent_color 的內容為 0x20000001(表示父節點的地址為0x20000000,節點顏色為黑),那麼執行//此操作後,取得父節點的地址為 0x20000000
  5. #definerb_color(r)   ((r)->rb_parent_color& 1)
  6. // 設定r節點的顏色為黑 ,只要看最後一位即可.
  7. #definerb_is_red(r)   (!rb_color(r))
  8. // 測試r節點是否為紅
  9. #definerb_is_black(r) rb_color(r)
  10. //測試r節點是否為黑
  11. #definerb_set_red(r)  do {(r)->rb_parent_color &= ~1; } while (0)
  12. //設定r節點是為紅
  13. #definerb_set_black(r)  do {(r)->rb_parent_color |= 1; } while (0)
  14.  //設定r節點是為黑
  15.   //行內函數,設定節點的父節點
  16.  staticinline voidrb_set_parent(struct rb_node *rb, struct rb_node *p)  
  17.  {  
  18.          rb->rb_parent_color =(rb->rb_parent_color & 3) | (unsigned long)p;  
  19.  }  
  20.   //行內函數,設定節點的color
  21.  staticinline voidrb_set_color(struct rb_node *rb, int color)  
  22.  {  
  23.          rb->rb_parent_color =(rb->rb_parent_color & ~1) | color;  
  24.  }    


這樣,提取parent指標只要把rb_parent_color成員的低兩位清零即可:

#define rb_parent(r) ((struct rb_node*)((r)->rb_parent_color & ~3))

取顏色只要看最後一位即可:

#define rb_color(r) ((r)->rb_parent_color &1)

測試顏色和設定顏色也是水到渠成的事了。需要特別指出的是下面的一個行內函數:

static inline void rb_link_node(struct rb_node *node, struct rb_node * parent, struct rb_node ** rb_link);

它把parent設為node的父結點,並且讓rb_link指向node。

 兩個基本操作:左旋與右旋

__rb_rotate_left是把以root為根的樹中的node結點進行左旋,__rb_rotate_right是進行右旋。這兩個函式是為後面的插入和刪除服務,而不是為外部提供介面。

其餘的幾個介面就比較簡單了

struct rb_node *rb_first(struct rb_root *root);

在以root為根的樹中找出並返回最小的那個結點,只要從根結點一直向左走就是了。如圖中的3

struct rb_node *rb_last(struct rb_root *root);

是找出並返回最大的那個,一直向右走如圖中的47

struct rb_node *rb_next(struct rb_node *node);//注意這裡next的概念,不是根據節點的位置,而是由其key域決定的,

//rbtree本身是有序的,所以可以有前序,中序,後續遍歷方式

返回node在樹中的後繼,這個稍微複雜一點。如果node的右孩子不為空,它只要返回node的右子樹中最小的結點即可(如圖中17的next是19);如果為空,它要向上查詢,找到迭帶結點是其父親的左孩子的結點,返回父結點(如圖中16的next是17)。如果一直上述到了根結點,返回NULL。

struct rb_node *rb_prev(struct rb_node *node);

返回node的前驅,和rb_next中的操作對稱。

void rb_replace_node(struct rb_node *victim, structrb_node *new, struct rb_root *root);

用new替換以root為根的樹中的victim結點。

三、key域為字串如何構建rbtree?

當key域為字串時,其可以構建rbtree的基礎是字串也是可以比較大小的。

先從strcmp函式說起, strcmp函式是比較兩個字串的大小,返回比較的結果。一般形式是: 

         i=strcmp(字串1,字串2);

        其中,字串1、字串2均可為字串常量或變數;i   是用於存放比較結果的整型變數。比較結果是這樣規定的: 

①字串1小於字串2,strcmp函式返回一個負值;

②字串1等於字串2,strcmp函式返回零;

③字串1大於字串2,strcmp函式返回一個正值;

那麼,字元中的大小是如何比較的呢?來看一個例子。 

        實際上,字串的比較是比較字串中各對字元的ASCII碼。首先比較兩個串的第一個字元,若不相等,則停止比較並得出大於或小於的結果;如果相等就接著 比較第二個字元然後第三個字元等等。如果兩上字串前面的字元一直相等,像"disk"和"disks"   那樣,   前四個字元都一樣,   然後比較第五個字元,   前一個字串"disk"只剩下結束符'/0',後一個字串"disks"剩下's','/0'的ASCII碼小於's'的ASCII 碼,所以得出了結果。因此無論兩個字串是什麼樣,strcmp函式最多比較到其中一個字串遇到結束符'/0'為止,就能得出結果。

注意:字串是陣列型別而非簡單型別,不能用關係運算進行大小比較。 

       if("ABC">"DEF")  /*錯誤的字串比較*/

        if(strcmp("ABC","DEF")   /*正確的字串比較*/ 

  正是有了這個理論基礎,我們才可以構建key域為string型別的rbtree,其構建方法見 

./kernel/Documentation/rbtree.txt ,摘錄如下

Creating a new rbtree

---------------------

Data nodes in an rbtree tree are structurescontaining a struct rb_node member:

   struct mytype

    {

       struct rb_node node;

       char *keystring;

    };

When dealing with a pointer to the embeddedstruct rb_node, the containing data structure may be accessed with the standardcontainer_of() macro.  In addition,individual members may be accessed directly via rb_entry(node, type, member).

At the root of each rbtree is an rb_rootstructure, which is initialized to be empty via:

 struct rb_root mytree = RB_ROOT;

Searching for a value in an rbtree

----------------------------------

Writing a search function for your tree isfairly straightforward: start at the root, compare each value, and follow theleft or right branch as necessary.

Example:

   struct mytype *my_search(struct rb_root *root, char *string)

    {

       struct rb_node *node = root->rb_node;

       while (node)

       {

           struct mytype *data = container_of(node, struct mytype, node);

           int result;

           //比較字串

           result = strcmp(string,data->keystring);

           if (result < 0)

                node = node->rb_left; //比當前節點小,就一直往左找

           else if (result > 0)

                node = node->rb_right; //比當前節點大,就一直往右找

           else

                return data;

       }

       return NULL;

    }

Inserting data into an rbtree

-----------------------------

Inserting data in the tree involves firstsearching for the place to insert the new node, then inserting the node andrebalancing ("recoloring") the tree.

The search for insertion differs from theprevious search by finding the location of the pointer on which to graft thenew node. The new node also needs a link to its parent node for rebalancingpurposes.

Example:

   int my_insert(struct rb_root *root, struct mytype *data)

    {

       struct rb_node **new = &(root->rb_node), *parent = NULL;

       /* Figure out where to put new node */

       while (*new)

       {

           struct mytype *this = container_of(*new, struct mytype, node);

           int result = strcmp(data->keystring, this->keystring);

           parent = *new;

           if (result < 0)

                new =&((*new)->rb_left);

           else if (result > 0)

                new =&((*new)->rb_right);

           else

                return FALSE;

       }

       /* Add new node and rebalance tree. */

       rb_link_node(data->node, parent, new);

       rb_insert_color(data->node, root);

       return TRUE;

    }

Removing or replacing existing data in anrbtree

------------------------------------------------

To remove an existing node from a tree,call:

   void rb_erase(struct rb_node *victim, struct rb_root *tree);

Example:

   struct mytype *data = mysearch(mytree, "walrus");

    if(data)

    {

       rb_erase(data->node, mytree);

       myfree(data);

    }

To replace an existing node in a tree with anew one with the same key, call:

  voidrb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root*tree);

Replacing a node this way does not re-sortthe tree: If the new node doesn't have the same key as the old node, the rbtreewill probably become corrupted.

Iterating through the elements stored in anrbtree (in sort order)

在Linux中有很多地方用到了RD樹。anticipatory, deadline, 和CFQ I/O排程都使用的是RB樹進行請求跟蹤,還有CD/DVD驅動的包管理也是如此。高精度計時器(high-resolutiontimer)使用RB樹組織定時請求。EXT3檔案系統也使用RB樹來管理目錄。虛擬儲存管理系統也是有RB樹進行VMAs(Virtual Memory Areas)的管理。當然還有檔案描述符,密碼鑰匙,“等級令牌桶”排程的網路資料包都是用RB資料進行組織和管理的。另外C++ STL中,關聯式容器 set 和 map 的底層實現是基於  RB-tree


android中使用rbtree的地方

1)/kernel/kernel/power/userwakelock.c //管理使用者空間的wakelock

這個檔案是用rbtree的精簡版,僅僅使用幾個介面就實現了,這個也是key域為字串,其實是wake_lock的name.

用rbtree來連線了所有從使用者空間申請的wakelock。

可以通過這個例子,梳理一下rbtree的使用:

首先,為什麼要使用rbtree?

  當用戶空間申請了成百上千的wakelock時,由rbtree這一資料結構僅僅通過name就可以快速的找到此lock

其次,如何使用rbtree?

構建struct user_wake_lock

由name--------> rbtree_node -------->user_wake_lock  -------->wake_lock

可以想象各個結構在記憶體中的佈局:通過 rbtree_node聯絡起來的樹,每個節點又是另一個struct的成員

2)binder

這個還沒有看。。。