在内核编程中哈希链表hlist使用非常多,比方在openvswitch中流表的存储中就使用了(见[1])。hlist的表头仅有一个指向首节点的指针。而没有指向尾节点的指针,这样在有非常多个buckets的HASH表中存储的表头就能降低一半的空间消耗。
     和hlist相关的数据结构例如以下,桶中存储的 hlist_head 是具有同样hash值的entry构成的链表。每一个entry包括一个 hlist_node 成员,通过它链入到这个哈希链表中。
struct hlist_head {
      struct hlist_node
* first;
};

//next指向下一个节点
// pprev指向前一个节点的next域
struct hlist_node {
      struct hlist_node
* next, ** pprev;
};

结构图为:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdm9uemhvdWZ6/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">


因为头结点和其它节点的类型不一致。这样就不能使用普通的prev指针指向前一个节点(否则处理的时候还要讨论是否是第一个节点,没有通用性),这里设计者的巧妙之处就是pprev指针指向前一个节点的next,统一了兴许全部的节点。

一些实用的宏:
//头结点初始化
#define HLIST_HEAD_INIT { .first
= NULL }
//构造一个名为name的头结点
#define HLIST_HEAD(name) struct hlist_head
name = {  .first = NULL }

//初始化头指针,链表指针
#define INIT_HLIST_HEAD(ptr) ((ptr)->first
= NULL)
#define INIT_HLIST_NODE(ptr) ((ptr)->next
NULL, (ptr)->pprev = NULL)

1.删除节点
next得到当前节点的下一个节点。pprev是前一个节点的next字段的地址,那么*pprev就指向的是当前这个节点,那么 *pprev=next
就把当前节点更新为下一个节点了。假设n不是最后一个节点,还要设置next->pprev.
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;
}

2.插入节点
(1)头插入:让插入的节点成为链表的第一个节点,依次更新对应的指针。示意图例如以下。
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;
}
(2)在已知节点next之前/之后插入,通过自己绘图。非常easy理解清楚。
/* 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;
     *(n-> pprev)
= n;
}

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;
}


3.通过看一个节点h的pprev是否为空。推断其是否在哈希链表中。

static inline int hlist_unhashed (const struct hlist_node
*h)
{
      return !h-> pprev;
}

4.哈希链表的遍历(iterate)相关代码
//通过一个字段member的地址 ptr。得到包括它的容器的地址
#define hlist_entry(ptr, type, member)
container_of(ptr,type,member)

//用 pos作为游标来遍历这个链表, prefetch是数据预取
#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)

//通用的哈希链表遍历,当中 pos指向当前节点。 tpos指向的包括hlist_node的当前结构体的指针
#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 existing point
 * @ tpos: the type * to use as a loop counter.
 * @ pos:  the &struct hlist_node
to use as a loop counter.
 * @member:   the name of the hlist_node within the struct.
 */
#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
existing point
 * @ tpos: the type * to use as a loop counter.
 * @ pos:  the &struct hlist_node
to use as a loop counter.
 * @member:   the name of the hlist_node within the struct.
 */
#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 counter.
 * @ pos:  the &struct hlist_node
to use as a loop counter.
 * @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)




Linux内核hlist数据结构分析的更多相关文章

  1. linux内核打印数据到串口控制台,printk数据不打印问题

    linux内核打印数据到串口控制台问题 原文来源:http://i.cnblogs.com/EditPosts.aspx?opt=1 1.查看当前控制台的打印级别 cat /proc/sys/kern ...

  2. Linux内核二层数据包接收流程

    本文主要讲解了Linux内核二层数据包接收流程,使用的内核的版本是2.6.32.27 为了方便理解,本文采用整体流程图加伪代码的方式从内核高层面上梳理了二层数据包接收的流程,希望可以对大家有所帮助.阅 ...

  3. Linux内核网络数据包处理流程

    Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...

  4. Linux内核之数据双链表

    导读 Linux 内核中自己实现了双向链表,可以在 include/linux/list.h 找到定义.我们将会首先从双向链表数据结构开始介绍内核里的数据结构.为什么?因为它在内核里使用的很广泛,你只 ...

  5. Linux 内核 hlist 详解

    在Linux内核中,hlist(哈希链表)使用非常广泛.本文将对其数据结构和核心函数进行分析. 和hlist相关的数据结构有两个:hlist_head 和 hlist_node //hash桶的头结点 ...

  6. linux 内核网络数据包接收流程

    转:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面 ...

  7. Linux内核 网络数据接收流程图

      各层主要函数以及位置功能说明:       1)sock_read:初始化msghdr{}的结构类型变量msg,并且将需要接收的数据存放的地址传给msg.msg_iov->iov_base. ...

  8. Linux 内核链表 list.h 的使用

    Linux 内核链表 list.h 的使用 C 语言本身并不自带集合(Collection)工具,当我们需要把结构体(struct)实例串联起来时,就需要在结构体内声明指向下一实例的指针,构成所谓的& ...

  9. Linux内核IP层的报文处理流程(一)

    本文主要讲解了Linux内核IP层的整体架构和对从网卡接受的报文处理流程,使用的内核的版本是2.6.32.27 为了方便理解,本文采用整体流程图加伪代码的方式对Linxu内核中IP整体实现架构和对网卡 ...

随机推荐

  1. ZOJ 3537 Cake(凸包+区间DP)

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3537 题目大意:给出一些点表示多边形顶点的位置,如果不是凸多边形 ...

  2. MySQL学习笔记:少用Null

    在实际编程中,Null容易引起很多问题,例如在Java里NullPointerException猝不及防的空指针异常,因此需要过多的if判断,甚是麻烦. 在MySQL数据库中也要少用Null,尽量保持 ...

  3. c语言循环链表的问题

    今天,老师说了一道题,大意是,有一群小朋友10个人,但是老师只有一个苹果,只能给一个小朋友,于是老师就决定让小朋友们做成一圈,从第一个小朋友开始,每隔一个小朋友就没有机会得到苹果,最后剩下的一个人可以 ...

  4. 什么是 CLR(转)

    CLR(公用语言运行时)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离..NET提供了一个运行时环境,叫做公用语言运行时(Comm ...

  5. Kylin使用笔记-1: 安装

    2016年1月14日 9:57:23 星期四 背景介绍     Apache Kylin是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最 ...

  6. mysql关于数据库表的水平拆分和垂直拆分

    最初知道水平垂直分表的时候是刚参加工作不久的时候,知道了这个概念,但是公司用户量和数据量始终没上来,所以也没用到过,知道有一天到了一家新公司后,这些才被应用到实际开发中,这里我就大概说说关于水平和垂直 ...

  7. thinkphp签到的实现代码

    thinkphp签到的实现代码 数据表 1 2 3 4 5 6 7 8 9 10 11 CREATE TABLE `members_sign` (   `id` int(11) unsigned NO ...

  8. 使用apache的ab命令进行压测

    1. 背景:互联网发达的今天,大大小小的网站如雨后春笋,不断出现,但是想要做出一个网站很简单,但是想要做好一个网站,非常非常难,首先:网站做好之后的功能怎么样这都是次要的,主要的是你的网站能承受怎么样 ...

  9. 【知了堂学习笔记】java 编写几种常见排序算法3

    排序的分类: 1.希尔排序 希尔排序是快速插入排序的改进版,希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰 ...

  10. Ubuntu16.04 下的网易云出现网络异常、无法播放,界面无响应问题的统一解决

    能够在Linux系统下体验到原生界面的网易云音乐是件不错的事情,但是它总是经常性的出现网络异常,界面无响应的问题 为了听歌的体验,进行深入探究: 首先通过终端启用网易云音乐:sudo netease- ...