深入理解FreeBSD中的TAILQ ------good

转载 2016年08月29日 12:03:44

from  http://verynix.com/freebsd-linux-unix-tailq.html

    工作的主要内容是tcp/ip,平台是FreeBSD,而且在内核态开发,所以很多情况下会涉及内核的一些数据结构和宏,比如说mbuf和TAILQ等。
    TAILQ是FreeBSD/linux内核对双向队列操作的一种抽象,抽象程度不亚于C++,能实现操作队列需要的各种操作:插入元素,删除元素,遍历队列等。这个队列的优点是插入元素很快
这里先回顾一下队列的特点(来自维基百科 http://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97):

队列,又稱為佇列(英文queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。

FreeBSD中的TAILQ把整个队列头抽象为一个单独的数据结构,我们先看看FreeBSD中的TAILQ相关宏,然后再举例子理解这些宏。
这里用最简单的一个结构体来理解TAILQ,这个结构体中有一个int型整数,还有两个分别指向前方和后方的指针。

1.描述前一个和下一个元素的结构体

458 #define _ENTRY(type)                       \
459 struct {                                \
460     struct type *tqe_next;  /* next element */          \
461     struct type **tqe_prev; /* address of previous next element */  \
462     TRACEBUF                            \
463 }

    这是TAILQ对两个指向前后两个元素指针的抽象,抽象为TAILQ_ENTRY结构体:tqe_next是指向下一个元素的指针,tqe_prev是一个二级指针,指针变量的地址,是前一个元素的tqe_next指针变量的地址解引用(*tqe_prev)之后就是本元素的内存地址;TRACEBUF是一个调试相关的宏,我们先不管它。


  举例:
  我们声明一个结构体,这个结构体只有一个int型整数,还有前驱和后继指针

struct int_node{
	int num;
	TAILQ_ENTRY(int_node);
};


宏展开之后,就变成:

struct int_node{
	int num;
	struct int_node *tqe_next;  /* next element */ 
	sturct int_node **tqe_prev; /* address of previous next element */
};

例如:

2.队列头

TAILQ把整个队列头单独抽象为一个结构体TAILQ_HEAD,如下:

445 /*
446  * Tail queue declarations.
447  */
448 #define TAILQ_HEAD(name, type)                      \
449 struct name {                               \
450     struct type *tqh_first; /* first element */         \
451     struct type **tqh_last; /* addr of last next element */     \
452     TRACEBUF                            \
453 }

这个宏实际上使用的时候,会展开成为一个结构体,tqh_first是一个一级指针,指向队列中的第一个元素;tqh_last是一个二级指针,它指向最后一个元素中的tqe_next(请参考上面的TAILQ_ENTRY),也就是最后一个元素的tqe_next的地址,指针的地址就是二级指针;TRACEBUF是一个用来调试的宏,不用管它。举例:

声明一个叫做queue_head的队列头:

TAILQ_HEAD(my_int_struct, my_int) queue_head;

宏展开之后就会变成(不管TRACEBUF宏):

struct int_head {
	struct int_node *tqh_first; /* first element */
	struct int_node **tqh_last; /* addr of last next element */
} queue_head;

如图:

用下面的宏初始化这个队列头:

534 #define TAILQ_INIT(head) do {                       \
535     TAILQ_FIRST((head)) = NULL;                 \
536     (head)->tqh_last = &TAILQ_FIRST((head));            \
537     QMD_TRACE_HEAD(head);                       \
538 } while (0)

变成:

3.插入元素

插入元素用TAILQ_INSERT_TAIL宏,由于TAILQ中有一个tqh_last的二级指针,
所以插入元素直接插到队尾,仅用O(1)时间。

578 #define TAILQ_INSERT_TAIL(head, elm, field) do {            \
579     QMD_TAILQ_CHECK_TAIL(head, field);              \
580     TAILQ_NEXT((elm), field) = NULL;                \
581     (elm)->field.tqe_prev = (head)->tqh_last;           \
582     *(head)->tqh_last = (elm);                  \
583     (head)->tqh_last = &TAILQ_NEXT((elm), field);           \

584     QMD_TRACE_HEAD(head);                       \
585     QMD_TRACE_ELEM(&(elm)->field);                  \
586 }  while (0)

QMD_TAILQ_CHECK_TAIL,QMD_TRACE_HEAD,QMD_TRACE_ELEM这三个宏和调试信息相关和做一些必要的检查,我们可以先不管;这个宏就是在调整相关的指针指向。我们向一个空队列插入两个元素2来理解这个宏:
3.1 580行让新元素的tqe_next指向空,执行完第580行:

3.2 581行让新元素的tqe_prev赋值为tqh_last,也就是指向队列头中的tqh_first的地址,执行完第581行:

3.3 582行让二级指针tqh_last中的内容指向新元素,也就是tqh_first指向新元素,执行完第582行:

3.4 583行,队列头的tqh_last赋值为新元素的tqe_next的地址(指针的地址,二级指针),执行完第583行:
这就是插入2后的整个链表。

4.删除元素

删除元素用TAILQ_REMOVE宏

596 #define TAILQ_REMOVE(head, elm, field) do {             \
597     QMD_SAVELINK(oldnext, (elm)->field.tqe_next);           \
598     QMD_SAVELINK(oldprev, (elm)->field.tqe_prev);           \
599     QMD_TAILQ_CHECK_NEXT(elm, field);               \
600     QMD_TAILQ_CHECK_PREV(elm, field);               \
601     if ((TAILQ_NEXT((elm), field)) != NULL)             \
602         TAILQ_NEXT((elm), field)->field.tqe_prev =      \
603             (elm)->field.tqe_prev;              \
604     else {                              \
605         (head)->tqh_last = (elm)->field.tqe_prev;       \
606         QMD_TRACE_HEAD(head);                   \
607     }                               \
608     *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);      \
609     TRASHIT(*oldnext);                      \
610     TRASHIT(*oldprev);                      \
611     QMD_TRACE_ELEM(&(elm)->field);                  \
612 } while (0)

QMD_SAVELINK,QMD_TAILQ_CHECK_NEXT,QMD_TAILQ_CHECK_PREV,TRASHIT,同样先不管这几个宏。我们从队列中删除一个元素来理解这个宏:
4.1 假设经过上节插入元素2之后,我们用TAILQ_INSERT_TAIL再插入一个元素1,没有删除之前的链表如下图:

现在假设我们删除队列中的第一个元素2

4.2 602和603在调整当前元素的下一个元素的tqe_prev指针,执行完第602行和603行之后:

4.3 608调整当前元素tqe_prev中的内容,执行完第608行之后:

4.4 释放结点2的空间之后,最后的链表:

5.队列中的第一个元素

512 #define TAILQ_FIRST(head)   ((head)->tqh_first)



6.当前元素的下一个元素

591 #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)

这个宏比较简单。


7.遍历链表中的每一个元素
用宏TAILQ_FOREACH

514 #define TAILQ_FOREACH(var, head, field)                 \
515     for ((var) = TAILQ_FIRST((head));               \
516         (var);                          \
517         (var) = TAILQ_NEXT((var), field))

这个宏就比较简单了,用临时变量var,  来遍历链表中的每一个元素

这些宏就是几个操作TAILQ经常使用的宏,还有一些诸如TAILQ_INSERT_HEAD等宏,类似,请自行看代码,这里就不一一叙述了。


这篇文章主要是理解相关的宏和插入删除过程,完整的应用例子请看下面的参考文献。

参考资料:


相关文章推荐

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

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

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

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

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

 出处: http://blog.csdn.net/luotuo44/article/details/38374009         Libevent源码中有一个queue....

C语言之尾队列tailq

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

C语言之尾队列tailq

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

FreeBSD 9.1使用DDB在线调试内核

要配置您的内核使其包含 DDB, 需要在您的内核配置文件中加入 options KDB options DDB 至于这些options是干嘛用的,可以参考文件: /usr/src/syc/conf...
  • ztz0223
  • ztz0223
  • 2013年02月21日 19:28
  • 1858

用U盘安装FreeBSD

今天
  • suelog
  • suelog
  • 2014年09月01日 19:23
  • 1983

muc.lists.freebsd.current

Hi I was wondering if anyone else is having this problem building asterisk on -CURRENT.  The pro...

在FreeBSD上安装Bugzilla

Bugzilla 是一款开源的 Web 应用,是一款bug跟踪系统和测试工具,由 mozilla 开发,并采用 Mozilla 公共许可证授权(MPL),它经常被一些高科技公司如 mozilla、红帽...

freebsd的SYSINIT框架

个人觉的freebsd内核子系统的初始化过程比linux更清晰,这主要归功于SYSINIT框架。SYSINIT的基本原理就是构造一个数据结构,将该数据结构的地址放到 一个统一的数据节中,这样当内核初始...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深入理解FreeBSD中的TAILQ ------good
举报原因:
原因补充:

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