关于libevent与FreeBSD内核中TAILQ队列的理解

时间:2022-10-01 12:59:28

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