本文以linux内核4.9.229为例
一、哈希桶(bucket)相关定义
Linux内核的哈希表比较特殊,只要包含struct hlist_head的数据结构都可以做为bucket,每一个bucket都是一个链表(链表节点数据结构只要包含struct hlist_node就可以)
相关定义在include/linux/type.h和include/linux/list_nulls.h文件中
//include/linux/type.h
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;//pprev指向当前节点的前驱节点的next指针
};
//include/linux/list_nulls.h
struct hlist_nulls_head {
struct hlist_nulls_node *first;
};
struct hlist_nulls_node {
struct hlist_nulls_node *next, **pprev;
};
二、哈希表在内核中的应用
例如:在include/net/inet_hashtables.h中定义了网络相关的hash表,ehash与bhash等
struct inet_ehash_bucket {
struct hlist_nulls_head chain;
};
struct inet_bind_hashbucket {
spinlock_t lock;
struct hlist_head chain;
};
//哈希桶
struct inet_listen_hashbucket {
spinlock_t lock;
union {
struct hlist_head head;
struct hlist_nulls_head nulls_head;
};
};
/* This is for listening sockets, thus all sockets which possess wildcards. */
#define INET_LHTABLE_SIZE 32 /* Yes, really, this is all you need. */
struct inet_hashinfo {
/* This is for sockets with full identity only. Sockets here will
* always be without wildcards and will have the following invariant:
*
* TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE
*
*/
struct inet_ehash_bucket *ehash;
spinlock_t *ehash_locks;
unsigned int ehash_mask;
unsigned int ehash_locks_mask;
/* Ok, let's try this, I give up, we do need a local binding
* TCP hash as well as the others for fast bind/connect.
*/
struct inet_bind_hashbucket *bhash;
unsigned int bhash_size;
/* 4 bytes hole on 64 bit */
struct kmem_cache *bind_bucket_cachep;
/* All the above members are written once at bootup and
* never written again _or_ are predominantly read-access.
*
* Now align to a new cache line as all the following members
* might be often dirty.
*/
/* All sockets in TCP_LISTEN state will be in here. This is the only
* table where wildcard'd TCP sockets can exist. Hash function here
* is just local port number.
*/
struct inet_listen_hashbucket listening_hash[INET_LHTABLE_SIZE]
____cacheline_aligned_in_smp; //监听哈希表
};
三、哈希表初始化及非空判断
在include/linux/list.h文件中定义了相关初始化以及非空判断
//初始化
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
//非空判断
static inline int hlist_empty(const struct hlist_head *h)
{
return !READ_ONCE(h->first);//宏定义用于原子读
}
初始化用法
struct hlist_head my_hash_table[10];
int i;
for (i = 0; i < 10; i++) {
INIT_HLIST_HEAD(&my_hash_table[i]);
}
四、哈希表插入节点
//在hlist的开头插入一个新项,其中第一个struct hlist_node的pprev
//指向struct hlist_head的first
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
if (first)
first->pprev = &n->next;
WRITE_ONCE(h->first, n);//宏定义用于原子写
n->pprev = &h->first;
}
//在指定节点之前添加一个新节点
/* next must be != NULL */
static inline void hlist_add_before(struct hlist_node *n,
struct hlist_node *next)
{
n->pprev = next->pprev;
n->next = next;
next->pprev = &n->next;
WRITE_ONCE(*(n->pprev), n);
}
//在指定节点之后添加一个新节点
static inline void hlist_add_behind(struct hlist_node *n,
struct hlist_node *prev)
{
n->next = prev->next;
WRITE_ONCE(prev->next, n);
n->pprev = &prev->next;
if (n->next)
n->next->pprev = &n->next;
}
插入节点用法
struct my_struct {
int data;
struct hlist_node node;
};
struct my_struct entry;
entry.data = 42;
hlist_add_head(&entry.node, &my_hash_table[0]);
五、哈希表删除节点
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
WRITE_ONCE(*pprev, next);//n的前一个节点的next指针指向n的下一个节点
if (next)
next->pprev = pprev;
}
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}
六、移动哈希表
/*
* Move a list from one list head to another. Fixup the pprev
* reference of the first entry if it exists.
*/
static inline void hlist_move_list(struct hlist_head *old,
struct hlist_head *new)
{
new->first = old->first;
if (new->first)
new->first->pprev = &new->first;
old->first = NULL;
}
七、哈希表遍历
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos ; pos = pos->next)
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
pos = n)
#define hlist_entry_safe(ptr, type, member) \
({ typeof(ptr) ____ptr = (ptr); \
____ptr ? hlist_entry(____ptr, type, member) : NULL; \
})
/**
* hlist_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry(pos, head, member) \
for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\
pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
关于container_of宏定义的用法可以参考我另一篇博文 :linux内核常用数据结构之链表源码阅读_linux 链表源码-CSDN博客
遍历用法
struct my_struct *entry;
hlist_for_each_entry(entry, &my_hash_table[0], node) {
printk(KERN_INFO "Entry data: %d\n", entry->data);
}
八、哈希表实践
代码完整链接https://github.com/RookieLinux/CodeLanguage/tree/master/C_Pro/test_kernel_struct
#include <stdio.h>
#include "test_kernel_list.h"
typedef struct worker {
struct list_head list;
int id;
}worker;
int main() {
printf("test kernel list!\n");
//---------------------------------初始化---------------------------------
worker per1;
INIT_LIST_HEAD(&per1.list);
per1.id = 1;
//--------------------------------- 插入 ---------------------------------
worker per2;
per2.id = 2;
list_add(&per2.list, &per1.list);
worker per3;
per3.id = 3;
list_add(&per3.list, &per1.list);
worker per4;
per4.id = 4;
list_add(&per4.list, &per1.list);
//--------------------------------- 遍历 ---------------------------------
printf("遍历\n");
worker * pos;
list_for_each_entry(pos, &(per1.list), list)
{
printf("pos->id is:%d\n",pos->id);
}
printf("**************************\n");
list_for_each_entry_reverse(pos, &(per1.list), list)
{
printf("pos->id is:%d\n",pos->id);
}
//--------------------------------- 删除 ---------------------------------
printf("删除\n");
list_del_init(&per4.list);
list_for_each_entry(pos, &(per1.list), list)
{
printf("pos->id is:%d\n",pos->id);
}
return 0;
}
九、其他相关定义
include/linux/compiler.h文件中的相关定义
#define __READ_ONCE_SIZE \
({ \
switch (size) { \
case 1: *(__u8 *)res = *(volatile __u8 *)p; break; \
case 2: *(__u16 *)res = *(volatile __u16 *)p; break; \
case 4: *(__u32 *)res = *(volatile __u32 *)p; break; \
case 8: *(__u64 *)res = *(volatile __u64 *)p; break; \
default: \
barrier(); \
__builtin_memcpy((void *)res, (const void *)p, size); \
barrier(); \
} \
})
static __always_inline
void __read_once_size(const volatile void *p, void *res, int size)
{
__READ_ONCE_SIZE;
}
#ifdef CONFIG_KASAN
/*
* We can't declare function 'inline' because __no_sanitize_address confilcts
* with inlining. Attempt to inline it may cause a build failure.
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67368
* '__maybe_unused' allows us to avoid defined-but-not-used warnings.
*/
# define __no_kasan_or_inline __no_sanitize_address __maybe_unused
#else
# define __no_kasan_or_inline __always_inline
#endif
static __no_kasan_or_inline
void __read_once_size_nocheck(const volatile void *p, void *res, int size)
{
__READ_ONCE_SIZE;
}
static __always_inline void __write_once_size(volatile void *p, void *res, int size)
{
switch (size) {
case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
default:
barrier();
__builtin_memcpy((void *)p, (const void *)res, size);
barrier();
}
}
#define WRITE_ONCE(x, val) \
({ \
union { typeof(x) __val; char __c[1]; } __u = \
{ .__val = (__force typeof(x)) (val) }; \
__write_once_size(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})
#define __READ_ONCE(x, check) \
({ \
union { typeof(x) __val; char __c[1]; } __u; \
if (check) \
__read_once_size(&(x), __u.__c, sizeof(x)); \
else \
__read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})
#define READ_ONCE(x) __READ_ONCE(x, 1)
include/linux/poison.h文件中的相关定义
/********** include/linux/list.h **********/
/*
* Architectures might want to move the poison pointer offset
* into some well-recognized area such as 0xdead000000000000,
* that is also not mappable by user-space exploits:
*/
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
#define LIST_POISON1 ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2 ((void *) 0x200 + POISON_POINTER_DELTA)