linux内核之哈希链表解析

本文只是对linux内核中的链表进行分析。内核版本是linux-2.6.32.63。文件在:linux内核/linux-2.6.32.63/include/linux/list.h。本文对list.h文件进行简要分析,有必要的地方还会以图进行说明。

linux内核中的哈希链表和其他链表不一样。他是头指针用单个的链表,只有next指针,但其他链表节点是有next和prev两个指针的双链表。

由上图可以知道hash链表中存在两种结构体,一种是hash表头,一种是hash节点。

hash表头:struct hlist_head{ struct hlist_node *first;};表头里面只存放一个hlist_node的指针,指向链表

hash节点:struct hlist_node{struct hlist_node *next; struct hlist_node **pprev;};有两个指针,所以链表是双链表。但和一般的双链表又有点不一样,next自然是指向链表的下一个节点,但pprev则不是指向当前节点的前一个节点,而是指向当前节点的前一个节点的next指针。所以ppre是二级指针。为什么要设计成这样呢?因为为了统一操作,如果设计的和我们平时使用的双链表的话(prev指向的是前一个节点),那头节点和链表节点之间的操作就要重新定义一套(因为头结点结构体和链表节点结构体是不一样的)。所以干脆直接指向前一个节点的next指针,next的类型是hlist_node*,first的类型也是hlist_node*。这样就统一了链表操作函数了。

还有个问题:为什么头结点要设计的和链表节点不一样呢?官方解释是为了节约空间,因为一般来说哈希表中有非常多的表项,即可能有上千个表项,也即是有上千个hlist_head。如果头结点不用pprev则可以节约非常大的空间。我个人认为还有种解释是头结点的pprev(如果有这个指针)用处不大。因为所有的操作都是要通过哈希函数来算出值在哈希表中的位置,然后再有链表中查找。哈希链表本来就是个处理碰撞现象的,说明链表中的关键字通过哈希函数后能得到一样的值。所以你不知道在链表中的哪个位置,那头结点有pprev的话你也没必要从后面开始查找(也许从前面查找开些,也许是从后面)。也就是说对于头节点来说这个指针可有可无。

代码分析:

/*
 * Double linked lists with a single pointer list head.
 * Mostly useful for hash tables where the two pointer list head is
 * too wasteful.
 * You lose the ability to access the tail in O(1).
 */
// 哈希节点,这是个表头,注意只有一个first指针。这是为了有多个哈希链表时,可以减少空间上的浪费
struct hlist_head {
struct hlist_node *first;
};
// 这个哈希链表和其他链表不一样,单指针表头双循环链表。所以头节点和其他节点不一样。
// 下面是链表节点有两个指针,next是指向下一个节点,pprev则是指向前一个节点的next,表头则是first。
// 所以pprev是二级指针
struct hlist_node {
struct hlist_node *next, **pprev;
};
// 初始化哈希链表头节点
#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 void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
// 这是个设计的非常巧妙的函数,因为pprev是指向前一个节点的next,
// 所以如果h节点在链表上则*pprev是指向自己的。如果不在则指向空的。
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}

// 判空函数,h是头节点,判断first是否为空来判断hash链表是否存在。
static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}

// 这是个删除n节点的函数。n节点的前后节点指针指向改变了,
// 但n节点的指针指向没有改变。
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;
}
// 在上面函数的基础上,改变了被删除的n节点指向。
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}

// 先判断将被删除的n节点是否在链表中,在则删除,然后初始化该节点。
static inline void hlist_del_init(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
INIT_HLIST_NODE(n);
}
}
// 这个函数是在头节点后面,增加一个节点。
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;
h->first = n;
n->pprev = &h->first;
}

/* next must be != NULL */
// 把n节点增加到next节点前面
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;
*(n->pprev) = n;
}

// 把n节点增加到next节点后面
static inline void hlist_add_after(struct hlist_node *n,
struct hlist_node *next)
{
next->next = n->next;
n->next = next;
next->pprev = &n->next;


if(next->next)
next->next->pprev  = &next->next;
}

/*
 * Move a list from one list head to another. Fixup the pprev
 * reference of the first entry if it exists.
 */
// 这是个替换函数,把new头节点替换掉old头结点,然后让old节点为NULL
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 && ({ prefetch(pos->next); 1; }); \
    pos = pos->next)
 
// 这个是带有存储临时变量的遍历函数
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
    pos = n)

/**
 * hlist_for_each_entry - iterate over list of given type
 * @tpos: the type * to use as a loop cursor.
 * @pos: the &struct hlist_node to use as a loop cursor.
 * @head: the head for your list.
 * @member: the name of the hlist_node within the struct.
 */
 // 这是个加了tpos(数据类型),且是从头节点开始的遍历
#define hlist_for_each_entry(tpos, pos, head, member)\
for (pos = (head)->first;\
    pos && ({ prefetch(pos->next); 1;}) &&\
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
    pos = pos->next)

/**
 * hlist_for_each_entry_continue - iterate over a hlist continuing after current point
 * @tpos: the type * to use as a loop cursor.
 * @pos: the &struct hlist_node to use as a loop cursor.
 * @member: the name of the hlist_node within the struct.
 */
 // 这个和上面的函数功能差不多,只是是从pos的下一个节点开始遍历,而非头节点
#define hlist_for_each_entry_continue(tpos, pos, member)\
for (pos = (pos)->next;\
    pos && ({ prefetch(pos->next); 1;}) &&\
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
    pos = pos->next)

/**
 * hlist_for_each_entry_from - iterate over a hlist continuing from current point
 * @tpos: the type * to use as a loop cursor.
 * @pos: the &struct hlist_node to use as a loop cursor.
 * @member: the name of the hlist_node within the struct.
 */
 // 这是从pos节点开始的遍历。
#define hlist_for_each_entry_from(tpos, pos, member)\
for (; pos && ({ prefetch(pos->next); 1;}) &&\
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
    pos = pos->next)


/**
 * 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.
 */
 // 这是数据项的遍历,且用临时变量n保存了pos数据。防止pos被删除时遍历出错。
#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)

#endif


阅读更多
换一批

没有更多推荐了,返回首页