linux內核中hlist_head和hlist_node結構解析


 

hlist_headhlist_node用於散列表,分表表示列表頭(數組中的一項)和列表頭所在雙向鏈表中的某項,兩者結構如下:

1
2
3
struct hlist_head {
struct hlist_node *first;
};
1
2
3
struct hlist_node {
struct hlist_node *next, **pprev;
};

在內核中的普通雙向鏈表基本上都是通過list_head實現的:

1
2
3
struct list_head {
struct list_head *next, *prev;
};

list_head很好理解,但是hlist_headhlist_node為何要這樣設計呢?

先看下hlist_headhlist_node的示意圖:

hash_table為散列表(數組),其中的元素類型為struct hlist_head。以hlist_head為鏈表頭的鏈表,其中的節點hash值是相同的(也叫沖突)。first指針指向鏈表中的節點①,然后節點①的pprev指針指向hlist_head中的first,節點①的next指針指向節點②。以此類推。

hash_table的列表頭僅存放一個指針,也就是first指針,指向的是對應鏈表的頭結點,沒有tail指針也就是指向鏈表尾節點的指針,這樣的考慮是為了節省空間——尤其在hash bucket(數組size)很大的情況下可以節省一半的指針空間。

為什么pprev是一個指向指針的指針呢?按照這個設計,我們如果想要得到尾節點,必須遍歷整個鏈表,可如果是一個指向節點的指針,那么頭結點現在的pprev便可以直接指向尾節點,也就是list_head的做法。

對於散列表來說,一般發生沖突的情況並不多(除非hash設計出現了問題),所以一個鏈表中的元素數量比較有限,遍歷的劣勢基本可以忽略。

在刪除鏈表頭結點的時候,pprev這個設計無需判斷刪除的節點是否為頭結點。如果是普通雙向鏈表的設計,那么刪除頭結點之后,hlist_head中的first指針需要指向新的頭結點。通過下面2個函數來加深理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//添加節點到鏈表頭
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first; //新節點的next指針指向原頭結點
if (first)
first->pprev = &n->next; //原頭結點的pprev指向新節點的next字段
h->first = n; //first指針指向新的節點(更換了頭結點)
n->pprev = &h->first; //此時n是鏈表的頭結點,將它的pprev指向list_head的first字段
}
 
//刪除節點
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next; // pprev指向的是前一個節點的next指針,當該節點是頭節點時指向 hlist_head的first,兩種情況下不論該節點是一般的節點還是頭結點都可以通過這個操作刪除掉所需刪除的節點。
if (next)
next->pprev = pprev; //使刪除節點的后一個節點的pprev指向刪除節點的前一個節點的next字段,節點成功刪除。
}
因本人水平有限,若文章內容存在問題,懇請指出。允許轉載,轉載請在正文明顯處注明原站地址以及原文地址,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM