zoukankan      html  css  js  c++  java
  • 关于libevent与FreeBSD内核中TAILQ队列的理解

    在看libevent源码中TAILQ的时候发现了一些让我迷惑的地方,就是里面的双端队列以及链表中节点的next与prev指针,它的设计与我们一般的链表以及linux内核的list完全不一样,因为里面的prev根本不是指向前一个节点,而是指向前一个节点的next元素的地址:

    #define TAILQ_HEAD(name, type)                        \
    struct name {                                \
        struct type *tqh_first;    /* first element */            \
        struct type **tqh_last;    /* addr of last next element */        \
    }
    
    #define TAILQ_HEAD_INITIALIZER(head)                    \
        { NULL, &(head).tqh_first }
    
    #define TAILQ_ENTRY(type)                        \
    struct {                                \
        struct type *tqe_next;    /* next element */            \
        struct type **tqe_prev;    /* address of previous next element */    \
    }
    

    还有一个令人迷惑的地方是下面的两个macros:

    #define TAILQ_LAST(head, headname)                    \
        (*(((struct headname *)((head)->tqh_last))->tqh_last))
    /* XXX */
    #define TAILQ_PREV(elm, headname, field)                \
        (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
    

    首先我们定义两个用于分析的实例

    struct user{

      int  i;

      TAILQ_ENTRY(user)  entry
    };

    TAILQ_HEAD(head,user) tq

    head * ptq = & tq

    为什么要强制转换问headname*呢,先将TAILQ_LASR进行分解来说明,TAILQ_PREV类似:

    @1 (head)->tqh_last

    @2 (  (struct headname *) (@1) )

    @3   ( *((@2)->tqh_last) )

    我们要获得自己定义的tq的最后一个节点的地址应这样使用

    TAILQ_LAST(ptq, head)

    由@1 我们得到的是ptq->tqh_last,即最后一个节点TAILQ_ENTRY(type)的struct type *tqe_next;元素的地址,这时候我们只要获得struct type **tqh_last的值就能得到该节点的地址值,就是last节点(注意不是前一个),现在如何获得struct type **tqh_last是关键所在。仔细观察我们发现TAILQ_ENTRY与TAILQ_HEAD的内存布局实际上是一样的,都是对应两个元素共八个字节,现在有两种方法获得struct type **tqh_last

    1.struct type *tqe_next的地址值直接加4

    2.将struct type *tqe_next强制转换成head,也就是源代码中的方法

    现在主要分析第二中解决方案,@2是一个head* 指针,通过@3之后我们实际得到的是前一个节点的struct type *tqe_next地址的解引用,就是last节点的地址,这里有点迷惑((@2)->tqh_last)实际对应的是TAILQ_ENTRY节点的struct type **tqe_prev元素,它指向的是前一个节点的struct type *tqe_next地址,这样一来我们便能得到最好一个节点的地址了,TAILQ_PREV使用的方法也是一样的。至于为什么使用2方法不用1方法,我是这样理解的,使用1方法需要进行加法运算,会消耗CPU周期,这对于网络应用与内核是很关键的,而且通过使用方法2可以易于移植,因为当将内核移植到64位机器的时候方法1肯能就会产生错误。

    也许上面的讲解一时无法理解,但是只要记住一点的就是TAILQ_ENTRY与TAILQ_HEAD的内存布局是一样的,为什么TAILQ_LAST和TAILQ_PREV中不将(head)->tqh_last)转换为TAILQ_HEAD而不是转换为TAILQ_ENTRY是因为TAILQ_ENTRY这个结构体是直接定义在你用户定义的结构体中,无法获取他的类型来进行转换

  • 相关阅读:
    398. Random Pick Index
    382. Linked List Random Node
    645. Set Mismatch
    174. Dungeon Game
    264. Ugly Number II
    115. Distinct Subsequences
    372. Super Pow
    LeetCode 242 有效的字母异位词
    LeetCode 78 子集
    LeetCode 404 左叶子之和
  • 原文地址:https://www.cnblogs.com/UnGeek/p/2989325.html
Copyright © 2011-2022 走看看