linux kernel中HList和Hashtable
文章目录
HList
先来直观的比较下普通链表和哈希链表(HList):
1.1 普通链表
普通链表的表头和节点相同
1 | struct list_head { |
详情请参考内核数据结构之链表。
1.2 哈希链表
HList的资料结构定义在 include/linux/types.h 之中:
哈希链表头
1 | struct hlist_head { |
哈希链表节点
1 | struct hlist_node { |
hlist_head 和 hlist_node 用于 hash table 中 bucket 的实际操作,具有相同 hash value 的节点会放在同一条 hlist 中。 为了节省空间,hlist_head 只使用一个 first 指针指向 hlist_node,没有指向尾节点的指针。
hlist_node 有两个指针,但是需要注意的是 pprev 是指向指针的指针,它指向的是前一个节点的 next指针。
因为这时候表头(hlist_head)和节点(hlist_node)的数据结构不同。如果使用struct hlist_node *prev,只适用于前一个为节点的情况,而不适用于前一个为表头的情况。如果每次操作都要考虑指针类型转换,会是一件麻烦的事情。所以,我们需要一种统一的操作,而不用考虑前一个元素是节点还是表头。pprev指向前一个元素的next指针,不用管前一个元素是节点还是表头。当我们需要操作前一个元素(节点或表头),可以统一使用*(node->pprev)来访问和修改前一元素的next(或first)指针。
原理图如下:
1.3 相关操作
HList 的操作与 List 一样定义在: include/linux/list.h ,以 hlist_ 开头
Hashtable
2.1 定义
在 Linux kernel 3.7 之后采用由Sasha Levin设计的通用型 hash table (LWN: A generic hash table),使用 DEFINE_HASHTABLE(name, bits)的macro来宣告 hash table:
- name: the name of the hash table
- bits: the number of bits of hash values
第二的参数 bits 比较特别,它代表的是 hash value 的有效位元数。若 bits = 3,hash value 的范围会是 0~7,若 bits = 10,hash value 的范围会是 0 ~ 1023。前者需要准备 8 个 buckets,后者则需要 1024 个 buckets,bits 参数会决定 hash table 的 buckets 的数量 (=2^bits)。hashtable实际上会以 hlist_head array 的方式来配置。
举例来说, DEFINE_HASHTABLE(htable, 3)会展开成:struct hlist_head htable[8] = { [0 ... 7] = HLIST_HEAD_INIT };
这是一个有 8 个 buckets 的 hash table。
经由 hash function 将值映射到这 8 个 buckets 中,当冲突发生时,直接加到 hlist_head 后的串列中。
2.2 相关操作
hash table 相关的操作定义在 include/linux/hashtable.h
hash_init- initialize a hash tablehash_empty- check whether a hashtable is emptyhash_add- add an object to a hashtablehash_del- remove an object from a hashtablehash_for_each- iterate over a hashtablehash_for_each_possible- iterate over all possible objects hashing to the same bucket
2.3 相关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链表
2.4 hash function
hashtable 预设的 hash function 定义在 include/linux/hash.h ,有 32 和 64 两个版本,如下:
1 | /* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ |
1 | /* 2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */ |
2.5 Hashtable example
下面用例子来说明 hashtable 的使用:
1 | struct object |
1 | void hashtable_example() |
经过上面的操作之后,hash table 呈现如下:
obj1 和 obj9 的 hash value 冲突,放入同一个 bucket 的串列中
以 hash_for_each_possible()寻访 bucket 内所有的节点(hlist_node),因为 hash value 可能会有冲突的关系,同一个 bucket 内可能会有不同 key value 的节点,所以需要检查是不是要查找的 key value。
1 | int key = 1; |
函数的详细使用方法可以参考hashtable_example.c
1 |
|
参考资料: