正如你所知道的,Linux內核提供了許多不一樣的庫和函數,它們實現了不一樣的數據結構和算法。在這部分,咱們將研究其中一種數據結構——基數樹 Radix tree。在 Linux 內核中,有兩個文件與基數樹的實現和API相關:linux
讓咱們先說說什麼是 基數樹
吧。基數樹是一種 壓縮的字典樹 (compressed trie)
,而字典樹是實現了關聯數組接口並容許以 鍵值對
方式存儲值的一種數據結構。這裏的鍵一般是字符串,但可使用任意數據類型。字典樹由於它的節點而與 n叉樹
不一樣。字典樹的節點不存儲鍵,而是存儲單個字符的標籤。與一個給定節點關聯的鍵能夠經過從根遍歷到該節點得到。舉個例子:git
+-----------+ | | | " " | | | +------+-----------+------+ | | | | +----v------+ +-----v-----+ | | | | | g | | c | | | | | +-----------+ +-----------+ | | | | +----v------+ +-----v-----+ | | | | | o | | a | | | | | +-----------+ +-----------+ | | +-----v-----+ | | | t | | | +-----------+
所以在這個例子中,咱們能夠看到一個有着兩個鍵 go
和 cat
的 字典樹
。壓縮的字典樹也叫作 基數樹
,它和 字典樹
的不一樣之處在於,全部只有一個子節點的中間節點都被刪除。github
Linux 內核中的基數樹是把值映射到整形鍵的一種數據結構。include/linux/radix-tree.h文件中的如下結構體描述了基數樹:算法
struct radix_tree_root { unsigned int height; gfp_t gfp_mask; struct radix_tree_node __rcu *rnode; };
這個結構體描述了一個基數樹的根,它包含了3個域成員:api
height
- 樹的高度;gfp_mask
- 告知如何執行動態內存分配;rnode
- 孩子節點指針.咱們第一個要討論的字段是 gfp_mask
:數組
底層內核的內存動態分配函數以一組標誌做爲 gfp_mask
,用於描述如何執行動態內存分配。這些控制分配進程的 GFP_
標誌擁有如下值:( GF_NOIO
標誌)意味着睡眠以及等待內存,( __GFP_HIGHMEM
標誌)意味着高端內存可以被使用,( GFP_ATOMIC
標誌)意味着分配進程擁有高優先級並不能睡眠等等。數據結構
GFP_NOIO
- 睡眠等待內存__GFP_HIGHMEM
- 高端內存可以被使用;GFP_ATOMIC
- 分配進程擁有高優先級而且不能睡眠;等等。數據結構和算法
下一個字段是rnode
:ide
struct radix_tree_node { unsigned int path; unsigned int count; union { struct { struct radix_tree_node *parent; void *private_data; }; struct rcu_head rcu_head; }; /* For tree user */ struct list_head private_list; void __rcu *slots[RADIX_TREE_MAP_SIZE]; unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS]; };
這個結構體包含的信息有父節點中的偏移以及到底端(葉節點)的高度、子節點的個數以及用於訪問和釋放節點的字段成員。這些字段成員描述以下:
path
- 父節點中的偏移和到底端(葉節點)的高度count
- 子節點的個數;parent
- 父節點指針;private_data
- 由樹的用戶使用;rcu_head
- 用於釋放節點;private_list
- 由樹的用戶使用;radix_tree_node
的最後兩個成員—— tags
和 slots
很是重要且使人關注。Linux 內核基數樹的每一個節點都包含了一組指針槽( slots ),槽裏存儲着指向數據的指針。在Linux內核基數樹的實現中,空槽存儲的是 NULL
。Linux內核中的基數樹也支持標籤( tags ),它與 radix_tree_node
結構體的 tags
字段相關聯。有了標籤,咱們就能夠對基數樹中存儲的記錄以單個比特位( bit )進行設置。
既然咱們瞭解了基數樹的結構,那麼該是時候看一下它的API了。
咱們從結構體的初始化開始。有兩種方法初始化一個新的基數樹。第一種是使用 RADIX_TREE
宏:
RADIX_TREE(name, gfp_mask);
正如你所看到的,咱們傳遞了 name
參數,因此經過 RADIX_TREE
宏,咱們可以定義和初始化基數樹爲給定的名字。RADIX_TREE
的實現很簡單:
#define RADIX_TREE(name, mask) \ struct radix_tree_root name = RADIX_TREE_INIT(mask) #define RADIX_TREE_INIT(mask) { \ .height = 0, \ .gfp_mask = (mask), \ .rnode = NULL, \ }
在 RADIX_TREE
宏的開始,咱們使用給定的名字定義 radix_tree_root
結構體實例,並使用給定的 mask 調用 RADIX_TREE_INIT
宏。 而 RADIX_TREE_INIT
宏則是使用默認值和給定的mask對 radix_tree_root
結構體進行了初始化。
第二種方法是手動定義radix_tree_root
結構體,而且將它和mask傳給 INIT_RADIX_TREE
宏:
struct radix_tree_root my_radix_tree; INIT_RADIX_TREE(my_tree, gfp_mask_for_my_radix_tree);
INIT_RADIX_TREE
宏的定義以下:
#define INIT_RADIX_TREE(root, mask) \ do { \ (root)->height = 0; \ (root)->gfp_mask = (mask); \ (root)->rnode = NULL; \ } while (0)
和RADIX_TREE_INIT
宏所作的初始化工做同樣,INIT_RADIX_TREE
宏使用默認值和給定的 mask 完成初始化工做。
接下來是用於向基數樹插入和刪除數據的兩個函數:
radix_tree_insert
;radix_tree_delete
;第一個函數 radix_tree_insert
須要3個參數:
radix_tree_delete
函數須要和 radix_tree_insert
同樣的一組參數,可是不須要傳入要刪除的數據。
基數樹的搜索以兩種方法實現:
radix_tree_lookup
;radix_tree_gang_lookup
;radix_tree_lookup_slot
.第一個函數radix_tree_lookup
須要兩個參數:
這個函數嘗試在樹中查找給定的鍵,並返回和該鍵相關聯的記錄。第二個函數 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);
它返回的是記錄的個數。 results
中的結果,按鍵排序,並從第一個索引開始。返回的記錄個數將不會超過 max_items
的值。
最後一個函數radix_tree_lookup_slot
將會返回包含數據的指針槽。
via: https://github.com/0xAX/linux-insides/blob/master/DataStructures/radix-tree.md