Linux kernel Hash list

在看桥接、路由代码时,经常会有hash表相关定的结构,为了能够更好的理解桥接、路由的代码,所以需要好好的理解hash链表
一、相关数据结构
数据结构:

struct hlist_head {
    structhlist_node *first;
};

struct hlist_node {
    structhlist_node *next, **pprev;
};

二、相关疑问
1、与一般的链表相比,hash list的表头与表节点的数据结构是不同的,其表头只有一个first指针,而没有prev指针,这是为什么呢?

首先我们需要知道hash的概念,对于hash表,其目的是为了方便快速的查找,如果hash表的长度较小的话(即hash数组的大小较小),则冲突的可能性则比较大,这样也就失去了散列表的意义。因此尽可能的维护一直大的hash表,但又要尽可能小的使用内存空间,这就需要改造hash表的数据结构了。普通链表元素都包含2个指针next、prev,而每一个指针则占用4个字节,如果我们能够将链表相关的数据结构所包含的指针减少到一个的话,则hash表的大小就会增加1倍。比如我们使用普通的链表结构list_head定义一个hash表
struct list_head hash[255];该hash表的大小为255,占用内存为255*8bytes。如果使用hlist_head,则相同内存大小,我们可以定义的hash表的大小为500。由此可见,相同内存下,与普通list_head相比,表头仅包含一个指针可以使hash表增倍

2、hlist_node中,为什么pprev是指针的指针,而不是一级指针呢?
这样做就解决了数据结构不一致的问题,hlist_node巧妙的将pprev指向上一个节点的next指针的地址,由于hlist_head和hlist_node指向的下一个节点的指针类型相同,就解决了通用性

三、相关函数介绍:

创建并初始化一个hash表头

#define HLIST_HEAD(name) struct hlist_head name ={  .first = NULL }
/*初始化hash表头,将其first指针置为NULL*/
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)

初始化一个hash表节点

/*初始化hash表节点,将其next、pprev指针置为NULL*/
    static inline void INIT_HLIST_NODE(struct hlist_node*h)
    {
    h->next =NULL;
    h->pprev =NULL;
}

判断一个hash节点是否在hash表中

/*判断hash表节点是否在hash表中,对于一个hash节点,若其pprev值为NULL,
则说明该hash节点所指向的前一个hash节点的next值为NULL,则说明该节点在hash表中;
若其pprev指针不为NULL,则说明该节点在hash表中*/
static inline int hlist_unhashed(const structhlist_node *h)
{
    return!h->pprev;
}


/*判断一个hash表是否为空,若其first指针为空,则是空hash表*/
static inline int hlist_empty(const struct hlist_head*h)
{
    return!h->first;
}

Hash节点删除相关的函数

/*从hash表中删除一个hash节点
1、获取要删除节点的next指针
2、获取要删除节点的pprev指针, 这样*pprev即为当前节点的前一个节点所指向的next节点的地址。
3、通过*pprev指针,将n的前一个节点与n->next节点关联
4、如果n->next不等于NULL,则设置n->next->pprev的值。
*/
static inline void __hlist_del(struct hlist_node *n)
{
    structhlist_node *next = n->next;
    structhlist_node **pprev = n->pprev;
    *pprev =next;
    if (next)
        next->pprev= pprev;
}
/*
1、调用__hlist_del,从hash表中删除一个hash节点
2、将删除节点的next、pprev值分别设置为LIST_POISON1、IST_POISON2
当调用__hlist_del从hash表中删除一个节点后,该节点的next与pprev值并不为空,为防止
代码通过节点n访问到hash表,需要将该节点的next与pprev设置为LIST_POISON1、IST_POISON2
*/
static inline void hlist_del(struct hlist_node *n)
{
    __hlist_del(n);
    n->next =LIST_POISON1;
    n->pprev =LIST_POISON2;
}
/*
1、调用__hlist_del,从hash表中删除一个hash节点
2、调用INIT_HLIST_NODE初始化节点n的next、pprev指针
*/
static inline void hlist_del_init(struct hlist_node*n)
{
    if(!hlist_unhashed(n)) {
        __hlist_del(n);
        INIT_HLIST_NODE(n);
    }
}

增加hash节点相关的函数

/*
在hash表头添加一个节点n
1、获取hash表头h的first指针,即所指向的下一个节点
2、将n->next与h->first关联
3、如果first不为NULL,则h->first->pprev 等于 &n->next
4、h->first 等于n,n->pprev=&h->first
*/
static inline void hlist_add_head(struct hlist_node*n, struct hlist_head *h)
{
    structhlist_node *first = h->first;
    n->next =first;
    if (first)
        first->pprev= &n->next;
    h->first =n;
    n->pprev =&h->first;
}
/*
1、首先将next->pprev的值赋给n->pprev
2、然后n->next = next
3、重新设置next->pprev的值,将其指向n->next
4、重新设置*(n->pprev)的值为n,即将n的前一个节点指向的next指针设置为n
*/
/* next must be != NULL */
static inline void hlist_add_before(struct hlist_node*n,
                    structhlist_node *next)
{
    n->pprev =next->pprev;
    n->next =next;
    next->pprev= &n->next;
    *(n->pprev)= n;
}
/*
将节点next插在节点n之后
*/
static inline void hlist_add_after(struct hlist_node*n,
                    structhlist_node *next)
{
    next->next= n->next;
    n->next =next;
    next->pprev= &n->next;

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

Hash表头转移函数

/*
将一个hash表中的节点都移植到一个新的hash表头中
1、将旧hash表的first值赋给新hash表的hash值
2、若hash表的first指针不为空,则分别更新这两个hash表的first指针
*/
/*
 * Move a listfrom one list head to another. Fixup the pprev
 * reference ofthe 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)

遍历hash表相关的函数

/*遍历hash表的每一个节点,不能进行删除节点操作*/
#define hlist_for_each(pos, head) \
    for (pos =(head)->first; pos && ({ prefetch(pos->next); 1; }); \
         pos = pos->next)
/*遍历hash表的每一个节点,可以进行删除节点操作*/
#define hlist_for_each_safe(pos, n, head) \
    for (pos =(head)->first; pos && ({ n = pos->next; 1; }); \
         pos = n)

用链表外的结构体地址来进行遍历,而不用哈希链表的地址进行遍历
通过hash表的first节点进行遍历:

#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)
#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)

从当前hash节点开始遍历

#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)
从当前hash节点的下一个节点开始遍历
#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)

转自:http://blog.csdn.net/lickylin/article/details/20004109

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值