Linux内核常用数据结构之哈希表源码阅读

本文以linux内核4.9.229为例

一、哈希桶(bucket)相关定义

Linux内核的哈希表比较特殊,只要包含struct hlist_head的数据结构都可以做为bucket每一个bucket都是一个链表(链表节点数据结构只要包含struct hlist_node就可以)

相关定义在include/linux/type.hinclude/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表,ehashbhash

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Rookie Linux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值