• 关于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这个结构体是直接定义在你用户定义的结构体中,无法获取他的类型来进行转换

  • 相关阅读:
    34、JS/AJAX
    33、mybatis(二)
    32、mybatis
    31、springmvc(注解)
    30、springmvc
    29、Oralce(五)
    Spring学习之路-SpringBoot简单入门
    Spring学习之路-从放弃到入门
    心情日记
    Spring学习之路-从入门到放弃
  • 原文地址:https://www.cnblogs.com/UnGeek/p/2989325.html
Copyright © 2020-2023  润新知