linux kernel 中的链表(二)
hlist 的定义
前文提到的 linux/list.h
,这个文件中实际包含了一个双向链表和一个哈希头,哈希链表的定义如下:
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
hlist_head 是哈希头结点,里面只有一个指向链表结构的指针,表示哈希头下的第一个元素。
hlist_node 中有两个指针,next 指针比较好理解,就是指向下一个链表元素,但是 pprev 则比较难理解一点。
pprev 在使用中,是指向上一个结点的 next 指针(即指向指针的指针变量)。这样做的好处主要是为了保证结构体的通用性。如下所示:
hlist_head1 是一个哈希表数组,为了解决冲突,在数组中都会有一个 first 的指针,从它开始挂载各个冲突到同一个哈希值的元素。那么为了定义这些挂载的哈希元素,首先需要 next 指针做串联,然后需要一个 prev 指向他的前驱节点。那么问题来了,第一个挂载的元素,他的前驱节点是 hlist_head 和其他的节点的头不一致。为了解决这个问题,所有的 hlist_node 的前驱节点修改为 hlist_node **pprev
,这样第一个节点也能赋值 pprev = &head->first 。
hlist 的基础操作
类似于普通的链表,hlist 也提供诸如新增,删除等操作,具体如下:
/*
* 判断类型函数
*/
static inline int hlist_unhashed(const struct hlist_node *h) // 判断一个元素是否未在哈希表里
{
return !h->pprev;
}
static inline int hlist_empty(const struct hlist_head *h) // 判断一个哈希桶下是否为空
{
return !h->first;
}
/*
* 基础操作
*/
static inline void hlist_del_init(struct hlist_node *n); // 从hlist中删除一个节点,并初始化它
static inline void hlist_del(struct hlist_node *n);
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h);
static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next);
static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *next);
static inline void hlist_move_list(struct hlist_head *old, struct hlist_head *new);
// 以上是一些顾名思义的操作了,包括删除节点,从头新增,在某个节点前新增,在某个节点后新增,移动一个节点
以其中删除节点为例子进行分析:
/*
* 这里的*pprev = next;体现出了 struct 中的设计的优越性
* 一句话屏蔽了对于前驱节点的判断,因为 head 和 node 的下一个节点的指针类型都是 hlist_node
*/
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next; //前驱节点的下一个节点修改为后继节点
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;
}
这里有两个宏定义:
poison.h:#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
poison.h:#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
只是一个访问之后会引起页错误的特殊地址,以下为.h里的原文:
/*
* These are non-NULL pointers that will result in page faults
* under normal circumstances, used to verify that nobody uses
* non-initialized list entries.
*/
常用宏
hlist 中的宏和 list 基本是一样的,也是包括初始化和枚举:
初始化:
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
枚举宏:
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
/**
* hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @tpos: the type * to use as a loop cursor.
* @pos: the &struct hlist_node to use as a loop cursor.
* @n: another &struct hlist_node to use as temporary storage
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
for (pos = (head)->first; \
pos && ({ n = pos->next; 1; }) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
pos = n)
这里的 tpos 是指自己的外包 struct 指针,pos 是用来循环的当前 hlist_node 指针,n为下一个链表元素(其实是个临时变量),head 就是 hlist_head 类型的指针了,member 为自己的结构体中对于链表的命名。
下一篇来写一些demo和使用心得,还有把里面比较细节的宏和函数做一些学习和总结吧(container_of 真是个牛逼东西)。