Linux kernel rbtree
因編寫內核模塊時需要用到rbtree來記錄異步request,研究分析了一下kernel rbtree的使用方法,記錄於此。本文主要參考了內核文檔rbtree.txt
rbtree簡介
Red-black trees(rbtree)是一種自平衡的二叉搜索樹,用於存儲可分類的key/value數據對。它不同於radix trees或者hash tables。
radix trees用於有效存儲稀疏數組(使用長整型索引進行節點的插入、查詢和刪除),其索引值太大無法用數組直接存儲。
hash tables用於散列索引縮小查詢的范圍,但它沒有做排序,因此不能快速的定位。
Red-black trees和AVL trees很相似,但是提供了最壞情況下更快的實時插入和刪除性能。插入最多2次rotations、刪除最多3次rotations即可完成tree的重平衡。不過相比AVL trees,其查詢時間稍慢(O(log n))。
Linux內核大量使用rbtree,如:I/O調度算法deadline和CFQ使用rbtree來跟蹤request;高精度定時器代碼使用rbtree來組織定時任務;ext3文件系統使用rbtree來跟蹤目錄entry;等等。
rbtree使用方法
內核rbtree的實現在文件"lib/rbtree.c",使用rbtree需要包含頭文件:
#include <linux/rbtree.h>
為了提高性能,linux rbtree比傳統的tree實現了更少的中間層。rbtree的節點結構體struct rb_node直接嵌入到使用者的data structure(傳統的方法是通過指針指向了data structure)。rbtree的插入和查詢函數由使用者通過調用linux rbtree提供的基礎函數自己實現(傳統的方法是提供回調函數指針)。並且btree的鎖也由使用者自己管理。
創建rbtree
在data數據結構里定義struct rb_node:
struct mytype {
struct rb_node node;
char *keystring;
};
當處理rbtree的節點時,通過container_of()
宏定義找到data數據結構指針。keystring
為rbtree的key,可以定義為字符串或者整型,它將用於用戶自定義的排序和查找。
然后定義rbtree的root節點:
struct rb_root mytree = RB_ROOT;
查找rbtree
使用者自己實現rbtree的查找函數,通過如下方法:從root開始,比較key的值,然后根據需要查找left節點或者right節點。
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;
}
插入新節點
使用者自己實現rbtree的插入函數,先找到插入的位置(該位置為NULL),然后插入新的節點並執行rbtree的重平衡。在查找到插入位置時,需要其parent節點的link用於rbtree的重平衡。
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;
}
刪除/覆蓋節點
通過如下函數刪除和覆蓋一個節點:
void rb_erase(struct rb_node *victim, struct rb_root *tree);
void rb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root *tree);
覆蓋一個節點並不會重平衡rbtree,因此必須保證new和old的key是一樣的,否者會導致異常。
刪除一個節點代碼示例:
struct mytype *data = mysearch(&mytree, "walrus");
if (data) {
rb_erase(&data->node, &mytree);
myfree(data);
}
按順序遍歷rbtree
如下4個函數用於順序遍歷rbtree:
struct rb_node *rb_first(struct rb_root *tree);
struct rb_node *rb_last(struct rb_root *tree);
struct rb_node *rb_next(struct rb_node *node);
struct rb_node *rb_prev(struct rb_node *node);
代碼示例:
struct rb_node *node;
for (node = rb_first(&mytree); node; node = rb_next(node))
printk("key=%s\n", rb_entry(node, struct mytype, node)->keystring);