轉載自:http://blog.csdn.net/joker0910/article/details/8250085php
基數(radix)樹 node
Linux基數樹(radix tree)是將指針與long整數鍵值相關聯的機制,它存儲有效率,而且可快速查詢,用於指針與整數值的映射(如:IDR機制)、內存管理等。
IDR(ID Radix)機制是將對象的身份鑑別號整數值ID與對象指針創建關聯表,完成從ID與指針之間的相互轉換。IDR機制使用radix樹狀結構做爲由id進行索引獲取指針的稀疏數組,經過使用位圖能夠快速分配新的ID,IDR機制避免了使用固定尺寸的數組存放指針。IDR機制的API函數在lib/idr.c中實現,這裏不加分析。
Linux radix樹最普遍的用途是用於內存管理,結構address_space經過radix樹跟蹤綁定到地址映射上的核心頁,該radix樹容許內存管理代碼快速查找標識爲dirty或writeback的頁。Linux radix樹的API函數在lib/radix-tree.c中實現。linux
(1)radix樹概述數據庫
radix樹是通用的字典類型數據結構,radix樹又稱爲PAT位樹(Patricia Trie or crit bit tree)。Linux內核使用了數據類型unsigned long的固定長度輸入的版本。每級表明了輸入空間固定位數。
radix tree是一種多叉搜索樹,樹的葉子結點是實際的數據條目。每一個結點有一個固定的、2^n指針指向子結點(每一個指針稱爲槽slot),並有一個指針指向父結點。數組
Linux內核利用radix樹在文件內偏移快速定位文件緩存頁,圖4是一個radix樹樣例,該radix樹的分叉爲4(22),樹高爲4,樹的每一個葉子結點用來快速定位8位文件內偏移,能夠定位4x4x4x4=256頁,如:圖中虛線對應的兩個葉子結點的路徑組成值0x00000010和0x11111010,指向文件內相應偏移所對應的緩存頁。
圖4 一個四叉radix樹緩存
Linux radix樹每一個結點有64個slot,與數據類型long的位數相同,圖1顯示了一個有3級結點的radix樹,每一個數據條目(item)可用3個6位的鍵值(key)進行索引,鍵值從左到右分別表明第1~3層結點位置。沒有孩子的結點在圖中不出現。所以,radix樹爲稀疏樹提供了有效的存儲,代替固定尺寸數組提供了鍵值到指針的快速查找。
圖1 一個3級結點的radix樹及其鍵值表示安全
(2)radix樹slot數數據結構
Linux內核根用戶配置將樹的slot數定義爲4或6,即每一個結點有16或64個slot,如圖2所示,當樹高爲1時,64個slot對應64個頁,當樹高爲2時,對應64*64個頁。
圖2 高爲1和二、slot數爲64的radix樹併發
Linux內核radix樹的slot數定義以下(在lib/radix-tree.c中):app
#ifdef __KERNEL__
/*值爲6時,表示每一個結點有2^6=64個slot,值爲4時,表示有2^4=16個slot*/
#define RADIX_TREE_MAP_SHIFT (CONFIG_BASE_SMALL ? 4 : 6)
#else
#define RADIX_TREE_MAP_SHIFT 3 /* 用於更有強度的測試 */
#endif
/*表示1個葉子結點可映射的頁數,如:1<<6=64,表示可映射64個slot映射64頁*/
#define RADIX_TREE_MAP_SIZE (1UL << RADIX_TREE_MAP_SHIFT)
#define RADIX_TREE_MAP_MASK (RADIX_TREE_MAP_SIZE-1)
/*定義slot數佔用的long類型長度個數,每一個slot用位圖1位進行標記,如:64個slot時,值爲2*/
#define RADIX_TREE_TAG_LONGS \
((RADIX_TREE_MAP_SIZE + BITS_PER_LONG - 1) / BITS_PER_LONG)
(3)結點結構
樹的根結點結構radix_tree_root列出以下(在include/linux/radix-tree.h中):
#define RADIX_TREE_MAX_TAGS 2 /*每一個slot須要的最大標籤位數*/
/*根結點的標籤存放在gfp_mask中,經過__GFP_BITS_SHIFT移位獲得 */
struct radix_tree_root {
unsigned int height; /* 從葉子向上計算的樹高度 */
gfp_t gfp_mask;
/*間接指針指向結點而非數據條目,經過設置root->rnode的低位表示是不是間指針*/
struct radix_tree_node *rnode;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
struct radix_tree_context *context;
struct task_struct *owner;
#endif
};
結構radix_tree_context列出以下:
struct radix_tree_context {
struct radix_tree_root *root;
#ifdef CONFIG_RADIX_TREE_CONCURRENT
spinlock_t *locked;
#endif
}
樹的結點結構radix_tree_node列出以下(在lib/radix-tree.c中):
struct radix_tree_node {
unsigned int height; /* 從葉子向上計算的樹高度 */
unsigned int count; /*非葉子結點含有一個count域,表示出如今該結點的孩子的數量*/
struct rcu_head rcu_head;
void *slots[RADIX_TREE_MAP_SIZE]; /*結點的slot指針*/
/* 結點標籤數組=每一個slot須要的最大標籤位數*slot數所需的long類型變量數 */
unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};
Linux內核radix樹高度是可變的,它依賴於存儲的索引鍵值。radix樹查詢必須知道樹高度,以便知道結點的slot是指向葉子結點的指針仍是結點的其餘層。Linux在結點結構中有成員height存入樹高度信息。
Linux radix樹的獨特特徵是標籤擴展。圖2顯示了有2個標籤,分別表示打開和關閉。這些標籤是結點結構的成員tags,該標籤用一個位圖實現,在加鎖的狀況下設置和清除。每一個標籤基本上是一個在radix樹頂層上的位圖序號。標籤與羣查詢一塊兒用來在給定的範圍內查找給定標籤集的頁。
標籤在每結點位圖中維護,在每一個給定的級別上,能夠斷定下一個級別是否至少有一個標籤集。
圖2 帶有2位圖標籤指示的8位radix樹
在結點結構radix_tree_node中,tags域是一個二維數組,每一個slot用2位標識,這是一個典型的用空間換時間的應用。tags域用於記錄該結點下面的子結點有沒有相應的標誌位。目前RADIX_TREE_MAX_TAGS爲2,表示只記錄兩個標誌,其中tags[0]爲PAGE_CACHE_DIRTY,tags[1]爲PAGE_CACHE_WRITEBACK。若是當前節點的tags[0]值爲1,那麼它的子樹節點就存在PAGE_CACHE_DIRTY節點,不然這個子樹分枝就不存在着這樣的節點,就沒必要再查找這個子樹了。好比在查找PG_dirty的頁面時,就不須要遍歷整個樹,而能夠跳過那些tags[0]爲0值的子樹,這樣就提升了查找效率。
「加標籤查詢」是返回有指定標籤集radix樹條目的查詢,能夠查詢設置了標籤的條目。加標籤查詢能夠並行執行,但它們須要經過設置或清除標籤從操做上互斥。
(4) 並行操做的優化
Linux radix樹並行操做包括並行查詢和並行修改,其中,並行修改在標準內核中未完沒有實現,須要經過打補丁得到該功能。並行操做說明以下:
RCU併發查詢
經過使用RCU,RCU Radix樹能夠進行徹底併發的查詢操做。RCU從根本上要求原子操做地移動指針從數據結構的一個版本到新的版本,保持舊版本直到系統通過靜止狀態。在靜止狀態點,舊版本數據結構已沒有用戶,所以能夠被安全釋放。
RCU radix樹的修改操做之間還須要串行化,可是查詢再也不須要與修改操做串行化。
併發修改
RCU可以使RCU radix樹查詢徹底並行化,但修改操做成了「瓶頸」。這可經過將全樹的鎖破碎成較小的鎖進行改善,再明顯的方法是對結點進行加鎖而非對整個樹加鎖。
radix樹修改操做可分爲單向和雙向操做。單向操做僅執行從根節點和葉子結點的單方向指針移動,它包括插入、更新和設置標籤操做。雙向操做較複雜,它須要在指針移到葉子後又回移,它包括刪除和清除標籤操做。
梯級加鎖(Ladder Locking)和鎖耦合(Lock-Coupling)技術經常使用於數據庫方面,容許單向遍歷結點加鎖的樹(雙向可能產生死鎖)。若是全部的修改者從樹頂到樹底進行修改,而且修改的結點持有鎖,那麼,向下遍歷時對孩子加鎖,在孩子被鎖住時再釋放該結點鎖。在這種狀況下併發操做是可能的,由於只要根結點解鎖,另外一個操做就能夠自上向下進行。若是兩操做的路徑沒有相同操做結點,後一個操做可能在前一個操做完成以前完成。最壞的狀況是流水線操做,但這仍是比串行化操做好不少。
雙向操做包括刪除和清除標籤操做,分別說明以下:
1)清除標籤
在radix樹中清除一個標籤包括向下遍歷樹、查找定位條目和清除條目標籤的操做。只要孩子結點沒有打標籤的條目,就能夠向上遍歷結點清除標籤。結束條件是:若是遍歷遇到一個結點,在清除一個標籤後,它還有一個或多個條目帶有標籤集,就能夠結束向上遍歷。爲了與向下遍歷期間有一樣的結束點,將終止條件改成:向上遍歷將在有比清除標籤數更多標籤的結點處結束。這樣,不論什麼時候遇到這樣的結點,將做爲上遍歷樹的結束點。
2)刪除元素
刪除元素在刪除無用結點時還須要刪除該條目的全部標籤。它的終止條件須要知足這兩個方面。向上回退遍歷樹時須要知足下面的條件:當遇到一個非空結點且沒有無用的標籤時應終止向上回退遍歷樹。
在向下遍歷樹時鑑別此點的條件是:當遇到有超過2個孩子的結點、而且每一個標籤來講結點有多於一個標籤條目被清除時,結束向上遍歷。該條件用來鑑別向上回退遍歷的終止點。
(5)radix樹API說明
聲明和初始化radix樹
聲明和初始化radix樹的方法列出以下:
#include <linux/radix-tree.h>
/* gfp_mask表示如何執行內存分配,若是操做(如:插入)以原子性上下文中執行,其值爲GFP_ATOMIC*/
RADIX_TREE(name, gfp_mask); /* 聲明和初始化名爲name的樹*/
struct radix_tree_root my_tree;
INIT_RADIX_TREE(my_tree, gfp_mask);
插入條目
插入條目的函數定義列出以下:
int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)
函數radix_tree_insert插入條目item到樹root中,若是插入條目中內存分配錯誤,將返回錯誤-ENOMEM。該函數不能覆蓋寫正存在的條目。若是索引鍵值index已存在於樹中,返回錯誤-EEXIST。插入操做成功是,返回0。
對於插入條目操做失敗將引發嚴重問題的場合,下面的一對函數可避免插入操做失敗:
int radix_tree_preload(gfp_t gfp_mask);
void radix_tree_preload_end(void);
函數radix_tree_preload嘗試用給定的gfp_mask分配足夠的內存,保證下一個插入操做不會失敗。在調用插入操做函數以前調用此函數,分配的結構將存放在每CPU變量中。函數radix_tree_preload操做成功後,將完畢內核搶佔。所以,在插入操做完成以後,用戶應調用函數radix_tree_preload_end打開內核搶佔。
刪除條目
刪除條目的函數定義列出以下:
void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
函數radix_tree_delete刪除與索引鍵值index相關的條目,若是刪除條目在樹中,返回該條目的指針,不然返回NULL。
查詢操做
用於查詢操做的函數定義列出以下:
/*在樹中查找指定鍵值的條目,查找成功,返回該條目的指針,不然,返回NULL*/
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
/*返回指向slot的指針,該slot含有指向查找到條目的指針*/
void **radix_tree_lookup_slot(struct radix_tree_root *root, unsigned long index);
/*查詢返回max_items條目在results中。查詢時鍵值索引從first_index開始*/
radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items);
標籤操做
與標籤操做相關的函數說明列出以下:
/*將鍵值index對應的條目設置標籤tag,返回值爲設置標籤的條目*/
void *radix_tree_tag_set(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*從鍵值index對應的條目清除標籤tag,返回值爲清除標籤的條目*/
void *radix_tree_tag_clear(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*檢查鍵值index對應的條目是否爲標籤tag,若是鍵值不存在,返回0,若是鍵值存在,但標籤未設置,返回-1;若是鍵值存在,且標籤已設置,返回1*/
int radix_tree_tag_get(struct radix_tree_root *root, unsigned long index, unsigned int tag);
/*從first_index起查詢樹root中標籤值爲tag的條目,在results中返回*/
unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items, unsigned int tag);
/*若是樹root中有任何條目使用tag標籤,返回鍵值*/
int radix_tree_tagged(struct radix_tree_root *root, unsigned int tag);
(6)並行操做使用radix樹API的方法
查詢獲取slot操做
查詢操做支持RCU無阻塞並行讀操做,所以,須要遵循RCU的用法加RCU讀鎖,還須要將rcu_dereference()用於得到的slot,在寫(或更新)操做時,須要給新的slot使用rcu_assign_pointer()。查詢操做的使用方法列出以下:
struct page **slot, *page;
rcu_read_lock();
slot = radix_tree_lookup_slot(&mapping->page_tree, index);
page = rcu_dereference(*slot);
rcu_read_unlock();
查詢修改slot操做
Linux內核的radix樹須要打補丁才支持併發修改。查詢僅有一個全局狀態:RCU靜止狀態,併發修改須要跟蹤持有什麼鎖。鎖狀態對於操做來講必須是外部的,所以,咱們須要實例化一個本地上下文跟蹤這些鎖。查詢修改slot的方法列出以下:
struct page **slot;
DEFINE_RADIX_TREE_CONTEXT(ctx,&mapping->page_tree);
radix_tree_lock(&ctx); /*鎖住了根結點*/
/* ctx.tree代替&mapping->page_tree做爲根,能夠傳遞上下文
slot = radix_tree_lookup_slot(tx.tree, index);
rcu_assign_pointer(*slot, new_page);
radix_tree_unlock(&ctx);
radix樹API函數radix_tree_lookup_slot含有鎖從樹頂向下移動機制,鎖移動的代碼部分列出以下:
void **radix_tree_lookup_slot(struct radix_tree *root, unsigned long index)
{
...
RADIX_TREE_CONTEXT(context, root); /*提供上下文和實際的root指針*、
...
do {
...
/* 從樹頂向下移動鎖*/
radix_ladder_lock(context, node);
...
} while (height > 0);
...
}
(7)radix樹API的實現
樹的操做一般包括查找、插入、刪除和樹調整,下面分別說明radix樹這些操做的實現。
查找單個條目
radix樹經過索引鍵值進行查詢,如圖1所示,它按路徑組成索引鍵值,圖中3級結點對應3段6位表示的索引值,圖中key對應的葉子slot的索引鍵值爲「2,63,1」。經過葉子slot的指針slot[1]就可獲得所存儲的數據item。所以,查詢迭代時,須要將索引鍵值「2,63,1」移去高2層「2,63」,獲得葉子slot的指針索引鍵值「1」。
圖1 一個3級結點的radix樹及其鍵值表示
函數radix_tree_lookup執行查找操做,查找方法是:從葉子到樹頂,經過數組索引鍵值值查看數組元素的方法,一層層地查找slot。其列出以下(在lib/radix-tree.c中):
void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index)
{
unsigned int height, shift;
struct radix_tree_node *node, **slot;
node = rcu_dereference(root->rnode); /*獲取根結點*/
if (node == NULL)
return NULL;
/*間接指針指向結點而非數據條目,經過設置root->rnode的低位表示是不是間指針。對於間接指針來講,樹高度值root->height大於0,可是RCU查找須要測試間接指針,由於root->height 值不可靠。這種問題僅的RCU下須要考慮*/
if (!radix_tree_is_indirect_ptr(node)) { /*非間接指針,說明只有根結點*/
if (index > 0)
return NULL;
return node;
}
/*獲取真正結點指針,由於根結點指針的第0位設置爲1表示爲間接指針。當使用結點指針時,必須將第0位設置爲0,由於地址以字對齊*/
node = radix_tree_indirect_to_ptr(node);
height = node->height;
if (index > radix_tree_maxindex(height)) /*索引鍵值不能超過最大索引值*/
return NULL;
/*每層索引偏移值爲RADIX_TREE_MAP_SHIFT,葉子索引值偏移基數爲(樹高-1)*每層索引偏移值*/
shift = (height-1) * RADIX_TREE_MAP_SHIFT;
do { /*從葉子到樹頂,經過樹路徑組成的索引查找指定索引鍵值的slot*/
slot = (struct radix_tree_node **)(node->slots + ((index>>shift) & RADIX_TREE_MAP_MASK)); /*如:slots +1*/
node = rcu_dereference(*slot);
if (node == NULL)
return NULL;
shift -= RADIX_TREE_MAP_SHIFT; /*向上移一層,再迭代查找*/
height--;
} while (height > 0);
return node;
}
查找多個條目
函數radix_tree_gang_lookup執行多個索引鍵值的查找,其列出以下:
unsigned int radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items)
{
unsigned long max_index;
struct radix_tree_node *node;
unsigned long cur_index = first_index;
unsigned int ret;
node = rcu_dereference(root->rnode);
if (!node)
return 0;
if (!radix_tree_is_indirect_ptr(node)) { /*若是爲非間接指針,表示只有根節點*/
if (first_index > 0)
return 0;
results[0] = node;
return 1;
}
node = radix_tree_indirect_to_ptr(node); /*清除用於間接指針標識的第0位*/
max_index = radix_tree_maxindex(node->height); /*獲取樹的最大索引鍵值*/
ret = 0;
while (ret < max_items) { /* max_items爲查找的最大條目數*/
unsigned int nr_found;
unsigned long next_index; /* 下一個搜索的索引鍵值*/
if (cur_index > max_index) /*已查詢完所需查詢的索引鍵值*/
break;
nr_found = __lookup(node, results + ret, cur_index,
max_items - ret, &next_index);
ret += nr_found;
if (next_index == 0)
break;
cur_index = next_index;
}
return ret;
}
static unsigned int
__lookup(struct radix_tree_node *slot, void **results, unsigned long index, unsigned int max_items, unsigned long *next_index)
{
unsigned int nr_found = 0;
unsigned int shift, height;
unsigned long i;
height = slot->height;
if (height == 0)
goto out;
/*全部葉子slot的索引鍵值基數偏移*/
shift = (height-1) * RADIX_TREE_MAP_SHIFT;
/*從底層向樹頂層,
for ( ; height > 1; height--) { /*從葉子向樹頂查找*/
i = (index >> shift) & RADIX_TREE_MAP_MASK;
for (;;) { /*遍歷每一層的各個路徑,由樹頂到當前層一條路徑組成索引鍵值*/
/*若是slot不爲空,那麼它掛有子結點,跳出本循環,進入子結點層進行本循環*/
if (slot->slots[i] != NULL)
break;
/*若是slot爲空,就跳過slot對應的全部索引鍵值*/
/*清除索引號低位.將索引號與該層slot的起始索引號對齊*/
index &= ~((1UL << shift) - 1);
/*跳過一個slot的索引鍵值數*/
index += 1UL << shift;
if (index == 0)
goto out; /* 32-bit wraparound */
i++; /*找到多個slot*/
if (i == RADIX_TREE_MAP_SIZE)
goto out;
}
shift -= RADIX_TREE_MAP_SHIFT; /*向上移一層,基數偏移減小*/
slot = rcu_dereference(slot->slots[i]);
if (slot == NULL)
goto out;
}
/* 返回找到的多個slot*/
for (i = index & RADIX_TREE_MAP_MASK; i < RADIX_TREE_MAP_SIZE; i++) {
struct radix_tree_node *node;
index++;
node = slot->slots[i];
if (node) {
results[nr_found++] = rcu_dereference(node);
if (nr_found == max_items)
goto out;
}
}
out:
*next_index = index;
return nr_found;
}
插入條目
函數radix_tree_insert找到索引鍵值對應的結點,將item加到該結點的slot指針上。其列出以下:
int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item)
{
struct radix_tree_node *node = NULL, *slot;
unsigned int height, shift;
int offset;
int error;
BUG_ON(radix_tree_is_indirect_ptr(item));
/* 若是樹的高度不夠,就擴展樹。函數radix_tree_maxindex計算樹高容納的最大索引*/
if (index > radix_tree_maxindex(root->height)) {
error = radix_tree_extend(root, index);
if (error)
return error;
}
slot = radix_tree_indirect_to_ptr(root->rnode);
height = root->height;
shift = (height-1) * RADIX_TREE_MAP_SHIFT; /*計算偏移基數*/
offset = 0;
while (height > 0) {
if (slot == NULL) { /*若是slot爲空,則須要加入孩子結點*/
/* 分配slot */
if (!(slot = radix_tree_node_alloc(root)))
return -ENOMEM;
slot->height = height;
if (node) {/*添加slot*/
rcu_assign_pointer(node->slots[offset], slot);
node->count++;
} else
rcu_assign_pointer(root->rnode,radix_tree_ptr_to_indirect(slot));
}
/* 進入上一層*/
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
node = slot;
slot = node->slots[offset];
shift -= RADIX_TREE_MAP_SHIFT;
height--;
}
/*若是index對應的slot已有映射頁面,返回-EEXIST*/
if (slot != NULL)
return -EEXIST;
if (node) {
node->count++; /*增長子結點的計數*/
rcu_assign_pointer(node->slots[offset], item);
BUG_ON(tag_get(node, 0, offset));
BUG_ON(tag_get(node, 1, offset));
} else { /*爲頂層結點*/
rcu_assign_pointer(root->rnode, item);
BUG_ON(root_tag_get(root, 0));
BUG_ON(root_tag_get(root, 1));
}
return 0;
}
擴展樹的高度
若是當前樹高度不足以存放index,就須要擴展樹,擴展方法是在舊樹頂上加新的根結點,並將原根結點的tag信息移到新根結點的第1個slot。函數radix_tree_extend 列出以下:
static int radix_tree_extend(struct radix_tree_root *root, unsigned long index)
{
struct radix_tree_node *node;
unsigned int height;
int tag;
/* 計算擴展的深度*/
height = root->height + 1;
/*若是index超過樹高所容納的最大索引值,樹高遞增*/
while (index > radix_tree_maxindex(height))
height++;
/*到這裏已計算好合適的高度height*/
if (root->rnode == NULL) { /*只有根結點,設置好樹高度就可返回*/
root->height = height;
goto out;
}
/*將當前樹擴展到高度height*/
do {
unsigned int newheight;
if (!(node = radix_tree_node_alloc(root))) /*分配一個結點*/
return -ENOMEM;
/* 增長樹高,在樹頂點之上增長一個結點*/
node->slots[0] = radix_tree_indirect_to_ptr(root->rnode);
/*傳播tag信息到新根結點*/
/*之前的根結點現成爲新頂結點的第1個插槽。 若是之前的根結點打上了tag,就將新增結點的第1個插槽對應的子節點打上相應的tag*/
for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
if (root_tag_get(root, tag))
tag_set(node, tag, 0);
}
newheight = root->height+1;
node->height = newheight;
node->count = 1;
node = radix_tree_ptr_to_indirect(node);
rcu_assign_pointer(root->rnode, node);
root->height = newheight;
} while (height > root->height);
out:
return 0;
}
刪除條目
函數radix_tree_delete刪除index對應的條目,並返回刪除條目的地址。其列出以下:
void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
{
/*數組path用於保存路徑上的結點及索引偏移值*/
struct radix_tree_path path[RADIX_TREE_MAX_PATH + 1], *pathp = path;
struct radix_tree_node *slot = NULL;
struct radix_tree_node *to_free;
unsigned int height, shift;
int tag;
int offset;
height = root->height;
if (index > radix_tree_maxindex(height)) /*index不能超過樹的最大索引值*/
goto out;
slot = root->rnode;
if (height == 0) { /*只有根結點*/
root_tag_clear_all(root);
root->rnode = NULL;
goto out;
}
slot = radix_tree_indirect_to_ptr(slot);
shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
pathp->node = NULL;
/*獲取刪除條目的路徑*/
/*將index從根到葉子的路徑所通過的結點及相應索引偏移值存放在數組pathp中*/
do {
if (slot == NULL)
goto out;
pathp++;
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
pathp->offset = offset;
pathp->node = slot;
slot = slot->slots[offset];
shift -= RADIX_TREE_MAP_SHIFT;
height--;
} while (height > 0);
if (slot == NULL)
goto out;
/*清除與刪除條目相關的全部標籤*/
for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
if (tag_get(pathp->node, tag, pathp->offset))
radix_tree_tag_clear(root, index, tag);
}
to_free = NULL;
/*釋放再也不須要的結點 */
while (pathp->node) { /*刪除
pathp->node->slots[pathp->offset] = NULL; /*清除slot*/
pathp->node->count--; /*孩子數量減1*/
/*調用RCU機制的函數call_rcu在最後一個引用結束時延遲釋放結點to_free */
if (to_free)
radix_tree_node_free(to_free);
if (pathp->node->count) { /*還有孩子存在*/
if (pathp->node == radix_tree_indirect_to_ptr(root->rnode)) /*爲根結點的孩子*/
radix_tree_shrink(root); /*樹縮小*/
goto out;
}
/*釋放有0個slot的結點 */
to_free = pathp->node;
pathp--;
}
/*運行到這裏,說明是根結點*/
root_tag_clear_all(root);
root->height = 0;
root->rnode = NULL;
if (to_free)
radix_tree_node_free(to_free);
out:
return slot;
}
縮小樹的高度
函數radix_tree_shrink縮小樹的高度到最小。其列出以下:
static inline void radix_tree_shrink(struct radix_tree_root *root)
{
/* 嘗試縮小樹的高度*/
while (root->height > 0) {
struct radix_tree_node *to_free = root->rnode;
void *newptr;
BUG_ON(!radix_tree_is_indirect_ptr(to_free));
to_free = radix_tree_indirect_to_ptr(to_free);
/*候選結點多於一個孩子或孩子不在最左邊slot,不能縮小樹的高度,跳出循環*/
if (to_free->count != 1)
break;
if (!to_free->slots[0])
break;
/*不須要調用rcu_assign_pointer(),由於僅從樹的一部分到另外一部分移動結點。若是釋放舊指針的引用to_free->slots[0]是安全的,那麼釋放新指針的引用root->rnode也是安全的*/ newptr = to_free->slots[0]; if (root->height > 1) newptr = radix_tree_ptr_to_indirect(newptr); root->rnode = newptr; root->height--; /* 僅釋放0結點*/ tag_clear(to_free, 0, 0); tag_clear(to_free, 1, 0); to_free->slots[0] = NULL; to_free->count = 0; radix_tree_node_free(to_free); } }