正如你所知道的, Linux 核心通過許多不同庫以及函式提供各種資料結構以及演算法。這個部分我們將介紹其中一個資料結構 Radix tree。Linux 核心中有兩個檔案與 radix tree
的實現和 API 相關:
首先說明一下什麼是 radix tree
,Radix tree 是一個 壓縮 trie
, trie 是一種通過儲存關聯陣列(associative array)來提供 關鍵字-值(key-value)
儲存與查詢的資料結構。通常關鍵字是字串,不過也可以是其他資料型別。 Trie 結構的節點與 n-tree
不同,其節點中並不儲存關鍵字,取而代之的是儲存單個字元標籤。關鍵字查詢時,通過從樹的根開始遍歷關鍵字相關的所有字元標籤節點,直至到達最終的葉子節點。下面是個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
+-----------+ | | | " " | | | +------+-----------+------+ | | | | +----v------+ +-----v-----+ | | | | | g | | c | | | | | +-----------+ +-----------+ | | | | +----v------+ +-----v-----+ | | | | | o | | a | | | | | +-----------+ +-----------+ | | +-----v-----+ | | | t | | | +-----------+ |
這個例子中,我們可以看到 trie 所儲存的關鍵字資訊 go
與 cat
,壓縮 trie 或 radix tree
與 trie
所不同的是,對於只有一個孩子的中間節點將被壓縮。
Linux 核心中的 Radix 樹將值對映為整型關鍵字,Radix 的資料結構定義在 include/linux/radix-tree.h 檔案中 :
1 2 3 4 5 |
struct radix_tree_root { unsigned int height; gfp_t gfp_mask; struct radix_tree_node __rcu *rnode; }; |
上面這個是 radix 樹的 root 節點的結構體,它包括三個成員:
height
:從葉節點向上計算出的樹高度。gfp_mask
:記憶體申請的標識。rnode
:子節點指標。
這裡首先要討論的結構體成員是 gfp_mask
:
Linux 底層的記憶體申請介面需要提供一類標識(flag) – gfp_mask
,用於描述記憶體申請的行為。這個以 GFP_
字首開頭的記憶體申請控制標識主要包括,GFP_NOIO
禁止所有 IO 操作但允許睡眠等待記憶體,__GFP_HIGHMEM
允許申請核心的高階記憶體,GFP_ATOMIC
高優先順序申請記憶體且操作不允許被睡眠。
接下來說的節結構體成員是 rnode
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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
:用於節點釋放的 RCU 連結串列。private_list
– 儲存資料。
結構體 radix_tree_node
的最後兩個成員 tags
與 slots
是非常重要且需要特別注意的。每個 Radix 樹節點都可以包括一個指向儲存資料指標的 slots 集合,空閒 slots 的指標指向 NULL。 Linux 核心的 Radix 樹結構體中還包含用於記錄節點儲存狀態的標籤 tags
成員,標籤通過位設定指示 Radix 樹的資料儲存狀態。
至此,我們瞭解到 radix 樹的結構,接下來看一下 radix 樹所提供的 API。
Linux 核心 radix 樹 API
我們從資料結構的初始化開始看,radix 樹支援兩種方式初始化。
第一個是使用巨集 RADIX_TREE
:
1 |
RADIX_TREE(name, gfp_mask); |
正如你看到,只需要提供 name
引數,就能夠使用 RADIX_TREE
巨集完成 radix 的定義以及初始化,RADIX_TREE
巨集的實現非常簡單:
1 2 3 4 5 6 7 8 |
#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
巨集首先使用 name
定義了一個 radix_tree_root
例項,並用 RADIX_TREE_INIT
巨集帶引數 mask
進行初始化。巨集 RADIX_TREE_INIT
將 radix_tree_root
初始化為預設屬性,並將 gfp_mask 初始化為入參 mask
。
第二種方式是手工定義 radix_tree_root
變數,之後再使用 mask
呼叫 INIT_RADIX_TREE
巨集對變數進行初始化。
1 2 |
struct radix_tree_root my_radix_tree; INIT_RADIX_TREE(my_tree, gfp_mask_for_my_radix_tree); |
INIT_RADIX_TREE
巨集定義:
1 2 3 4 5 6 |
#define INIT_RADIX_TREE(root, mask) do { (root)->height = 0; (root)->gfp_mask = (mask); (root)->rnode = NULL; } while (0) |
巨集 INIT_RADIX_TREE
所初始化的屬性與 RADIX_TREE_INIT
一致
接下來是 radix 樹的節點插入以及刪除,這兩個函式:
radix_tree_insert
;radix_tree_delete
.
第一個函式 radix_tree_insert
需要三個入參:
- radix 樹 root 節點結構
- 索引關鍵字
- 需要插入儲存的資料
第二個函式 radix_tree_delete
除了不需要儲存資料引數外,其他與 radix_tree_insert
一致。
radix 樹的查詢實現有以下幾個函式:
radix_tree_lookup
;radix_tree_gang_lookup
;radix_tree_lookup_slot
;
第一個函式 radix_tree_lookup
需要兩個引數:
- radix 樹 root 節點結構
- 索引關鍵字
這個函式通過給定的關鍵字查詢 radix 樹,並返關鍵字所對應的結點。
第二個函式 radix_tree_gang_lookup
具有以下特徵:
1 2 3 4 |
unsigned int radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items); |
函式返回查詢到記錄的條目數,並根據關鍵字進行排序,返回的總結點數不超過入參 max_items
的大小。
最後一個函式 radix_tree_lookup_slot
返回結點 slot 中所儲存的資料。