freeBSD TAILQ队列的理解

转载 2016年08月29日 12:02:28

from  http://www.cnblogs.com/UnGeek/archive/2013/03/29/2989325.html

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


相关文章推荐

深入理解TAILQ队列

工作的主要内容是tcp/ip,平台是FreeBSD,而且在内核态开发,所以很多情况下会涉及内核的一些数据结构和宏,比如说mbuf和TAILQ等。 TAILQ是FreeBSD/linux内核对双向队列操...

C语言之尾队列tailq

queue和list的结构定义和操作都在'sys/queue.h'中完成, 主要定义了下面四种数据结构: 单向列表(single-linked lists)单向尾队列(single-linked...

libevent源码分析-- queue.h中TAILQ_QUEUE的理解

libevent中的例子中使用的是FreeBSD下的queue.h,在linux的/usr/include/sys/queue.h也有该头文件,但是是一个缩减版本,而且没有看到queue 的acces...

TailQueue详解

TailQueue详解   本文详细解释Libevent中TailQueue数据结构及使用方法。 采用的基础数据结构如下所示:该数据结构包含2个业务字段,设备编号devId和设备名称name。 ...

Libevent源码分析-----TAILQ_QUEUE队列

[+] 原: http://blog.csdn.net/luotuo44/article/details/38374009  Libevent源码中有一个queue...
  • ylo523
  • ylo523
  • 2015年01月29日 18:33
  • 470

Linux queue.h之TAILQ队列分析!

这两天想看看memcached的实现,所以先学习了libevent,使用起来还是比较简单的,其实是对select/poll/kqueue等的封装,学习libevent过程中又遇到了linux下队列的使...

queue.h之tailq.h尾队列理解使用

一、连接、组织方式 如图: 每个entry有两个关键元素:tqe_next(简称为next)、tqe_prev(简称为prev)。next指向下个entry的地址,prev指向上个entry的...

Libevent源码剖析基础篇——双向链表队列TAILQ

工作的主要内容是tcp/ip,平台是FreeBSD,而且在内核态开发,所以很多情况下会涉及内核的一些数据结构和宏,比如说mbuf和TAILQ等。 TAILQ是FreeBSD/linux内核对双向队列操...

C语言之尾队列tailq

转载链接: http://blog.csdn.net/gujing001/article/details/18667485 queue和list的结构定义和操作都在'sys/queu...

C语言之尾队列tailq

queue和list的结构定义和操作都在'sys/queue.h'中完成, 主要定义了下面四种数据结构: 单向列表(single-linked lists)单向尾队列(single-linked...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:freeBSD TAILQ队列的理解
举报原因:
原因补充:

(最多只允许输入30个字)