• 哈希散列表hlist_head - linux内核经典实例


    hlist_headhlist_node用于散列表,分别表示列表头(数组中的一项)和列表头所在双向链表中的某项,两者结构如下:

    include/linux/types.h(line 190)

    1. struct hlist_head {
    2. struct hlist_node *first;
    3. };
    4. struct hlist_node {
    5. struct hlist_node *next, **pprev;
    6. };

    在内核中的普通双向链表基本上都是通过list_head实现的:

    include/linux/types.h(line 186)

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

    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个函数来加深理解:

    include/linux/list.h(line 669)

    1. static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
    2. {
    3. struct hlist_node *first = h->first;
    4. n->next = first; //新节点的next指针指向原头结点
    5. if (first)
    6. first->pprev = &n->next;//原头结点的pprev指向新节点的next字段
    7. WRITE_ONCE(h->first, n);//first指针指向新的节点(更换了头结点)
    8. n->pprev = &h->first;//此时n是链表的头结点,将它的pprev指向list_head的first字段
    9. }

    include/linux/list.h(line 644)

    1. static inline void __hlist_del(struct hlist_node *n)
    2. {
    3. struct hlist_node *next = n->next;
    4. struct hlist_node **pprev = n->pprev;
    5. WRITE_ONCE(*pprev, next); // pprev指向的是前一个节点的next指针,当该节点是头节点时指向 hlist_head的first,两种情况下不论该节点是一般的节点还是头结点都可以通过这个操作删除掉所需删除的节点。
    6. if (next)
    7. next->pprev = pprev;//使删除节点的后一个节点的pprev指向删除节点的前一个节点的next字段,节点成功删除。
    8. }

    相关API

    API说明
    HLIST_HEAD_INIT静态初始化hlist_head
    HLIST_HEAD静态初始化hlist_head
    INIT_HLIST_HEAD动态初始化hlist_head
    INIT_HLIST_NODE动态初始化hlist_node
    hlist_unhashed判断hlist_node是否添加到hash链表中
    hlist_empty判断hash链表是否为空
    hlist_del在hash链表中删除一个节点
    hlist_del_init在hash链表中删除一个节点
    hlist_add_head在hash链表头添加一个节点
    hlist_add_before在指定节点之前添加一个节点
    hlist_add_behind在指定节点之后添加一个节点
    hlist_add_fake
    hlist_fake
    hlist_is_singular_node判断hlist是否只有一个节点
    hlist_move_list将一个hash链表从一个hlist_head移动到另外一个hlist_head中
    hlist_entry根据hlist_node找到其外层结构体
    hlist_entry_safe同上
    hlist_for_each遍历hash链表
    hlist_for_each_safe同上
    hlist_for_each_entry遍历hash链表
    hlist_for_each_entry_safe同上
    hlist_for_each_entry_continue从当前节点之后遍历hash链表
    hlist_for_each_entry_from从当前节点开始遍历hash链表

    程序示例

    写一个测试模块,验证一下各个API

    模块代码

    1. #include <linux/module.h>
    2. #include <linux/kernel.h>
    3. #include <linux/init.h>
    4. #include <linux/list.h>
    5. struct node {
    6. int val;
    7. struct hlist_node list;
    8. };
    9. static int __init hlist_test_init(void)
    10. {
    11. struct hlist_head head;
    12. struct node a, b, c, d, e;
    13. struct node *pos;
    14. struct hlist_node *p;
    15. printk(KERN_ALERT "[Hello] hlist_test \n");
    16. INIT_HLIST_HEAD(&head); //初始化链表头
    17. a.val = 1;
    18. b.val = 2;
    19. c.val = 3;
    20. d.val = 4;
    21. e.val = 5;
    22. hlist_add_head(&a.list, &head); //添加节点
    23. hlist_add_head(&b.list, &head);
    24. hlist_add_head(&c.list, &head);
    25. printk(KERN_ALERT "-------------------------------------- \n");
    26. //遍历链表,打印结果 方法1
    27. hlist_for_each_entry(pos, &head, list) {
    28. printk(KERN_ALERT "node.val = %d\n", pos->val);
    29. } // print 3 2 1
    30. printk(KERN_ALERT "-------------------------------------- \n");
    31. // 遍历链表,打印结果 方法2
    32. hlist_for_each(p, &head) {
    33. pos = hlist_entry(p, struct node, list);
    34. printk(KERN_ALERT "node.val = %d\n", pos->val);
    35. } // print 3 2 1
    36. printk(KERN_ALERT "-------------------------------------- \n");
    37. hlist_del_init(&b.list); // 删除中间节点
    38. hlist_for_each_entry(pos, &head, list) {
    39. printk(KERN_ALERT "node.val = %d\n", pos->val);
    40. } // print 3 1
    41. printk(KERN_ALERT "-------------------------------------- \n");
    42. hlist_add_before(&d.list, &a.list); //在最后一个节点之前添加新节点
    43. hlist_for_each_entry(pos, &head, list) {
    44. printk(KERN_ALERT "node.val = %d\n", pos->val);
    45. } // print 3 4 1
    46. printk(KERN_ALERT "-------------------------------------- \n");
    47. hlist_add_behind(&e.list, &a.list);//在最后一个节点之后添加新节点
    48. hlist_for_each_entry(pos, &head, list) {
    49. printk(KERN_ALERT "node.val = %d\n", pos->val);
    50. } // print 3 4 1 5
    51. return 0;
    52. }
    53. static void __exit hlist_test_exit(void)
    54. {
    55. printk(KERN_ALERT "[Goodbye] hlist_test\n");
    56. }
    57. module_init(hlist_test_init);
    58. module_exit(hlist_test_exit);
    59. MODULE_LICENSE("GPL");

    执行结果

    1. [ 944.056943] [Hello] hlist_test
    2. [ 944.056947] --------------------------------------
    3. [ 944.056948] node.val = 3
    4. [ 944.056949] node.val = 2
    5. [ 944.056950] node.val = 1
    6. [ 944.056951] --------------------------------------
    7. [ 944.056952] node.val = 3
    8. [ 944.056953] node.val = 2
    9. [ 944.056954] node.val = 1
    10. [ 944.056955] --------------------------------------
    11. [ 944.056956] node.val = 3
    12. [ 944.056957] node.val = 1
    13. [ 944.056958] --------------------------------------
    14. [ 944.056959] node.val = 3
    15. [ 944.056960] node.val = 4
    16. [ 944.056961] node.val = 1
    17. [ 944.056962] --------------------------------------
    18. [ 944.056963] node.val = 3
    19. [ 944.056964] node.val = 4
    20. [ 944.056965] node.val = 1
    21. [ 944.056965] node.val = 5

    其他

    内核中用hlist来实现 hash table,在内核上一般有如下的hash table

    # dmesg | grep "hash table entries" 
     

    1. [ 0.000000] PV qspinlock hash table entries: 256 (order: 0, 4096 bytes)
    2. [ 0.000000] PID hash table entries: 4096 (order: 3, 32768 bytes)
    3. [ 0.294869] Dentry cache hash table entries: 524288 (order: 10, 4194304 bytes)
    4. [ 0.296328] Inode-cache hash table entries: 262144 (order: 9, 2097152 bytes)
    5. [ 0.296589] Mount-cache hash table entries: 8192 (order: 4, 65536 bytes)
    6. [ 0.296595] Mountpoint-cache hash table entries: 8192 (order: 4, 65536 bytes)
    7. [ 0.614525] TCP established hash table entries: 32768 (order: 6, 262144 bytes)
    8. [ 0.614769] TCP bind hash table entries: 32768 (order: 9, 2621440 bytes)
    9. [ 0.616607] UDP hash table entries: 2048 (order: 6, 393216 bytes)
    10. [ 0.616794] UDP-Lite hash table entries: 2048 (order: 6, 393216 bytes)
    11. [ 1.053747] futex hash table entries: 1024 (order: 5, 131072 bytes)
    12. [ 1.079062] Dquot-cache hash table entries: 512 (order 0, 4096 bytes)
  • 相关阅读:
    【K8S系列】深入解析k8s网络插件—Weave Net
    计算机网络核心概念——名词解释
    wstunnel (websocket模式ssh)
    视频教程下载:ChatGPT驱动的SEO、网络营销、生产力提升
    刷题笔记之三(统计回文+连续最大和+查找组成一个偶数最接近的两个素数+把字符串转换成整数+不要二)
    写好 Spring Starter : 控制好Bean的加载顺序与原理
    【Python】练习题附带答案
    java-php-python--关爱留守儿童志愿者管理系统-计算机毕业设计
    串的顺序存储的应用实例二
    Ubuntu升级python后ModuleNotFoundError: No module named ‘apt_pkg‘ && ‘apt_inst’ 异常
  • 原文地址:https://blog.csdn.net/u012294613/article/details/127994882