深入理解TAILQ队列以及源码分析

        TAILQ是linux内核对双向队列操作的一种抽象,能实现操作队列需要的各种操作:插入元素,删除元素,遍历队列等,其封装是对应的宏定义,下面详细说明tailq的操作,从定义,初始化,插入,删除和遍历这几个API来介绍。

宏名称操作
TAILQ_INIT队列初始化
TAILQ_FIRST获取队列中的第一个元素
TAILQ_INSERT_TAIL在队列末尾插入元素
TAILQ_INSERT_BEFORE在指定元素前插入元素
TAILQ_INSERT_AFTER在指定元素后插入元素
TAILQ_FOREACH对队列进行遍历
TAILQ_EMPTY检测队列是否为空
TAILQ_REMOVE从队列中移除该元素
TAILQ_NEXT获取当前元素的下一个元素

        在介绍上面的接口之前,先了解两个结构体。TAILQ_ENTRY结构体和TAILQ_HEAD结构体基本一致,但是表示的含义不一样。TAILQ_ENTRY结构体用来表示队列中节点的指针域(类似于平时编程中的链表指针域,单向链表中一般含有next指针,而双向链表含有pre,next指针)。TAILQ_HEAD结构是用来表示队列的头节点和尾节点的指针。TRACEBUF是一个调试相关的宏,我们先不管它。

#define	TAILQ_ENTRY(type)						\
struct {								\
	struct type *tqe_next;	/* next element */			\
	struct type **tqe_prev;	/* address of previous next element */	\
	TRACEBUF							\
}

#define	TAILQ_HEAD(name, type)						\
struct name {								\
	struct type *tqh_first;	/* first element */			\
	struct type **tqh_last;	/* addr of last next element */		\
	TRACEBUF							\
}

        可能我们现在还不是很理解,下面通过图来直观的感受下。

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

 

为什么是二级指针?
next是指向的是下一个元素的地址(由于下一个元素的类型是type,所以是一级指针type*)
prev是指向上一个元素next成员的地址(由于next成员类型是type*,所以需要使用指针type**)
跟我们平常实现的链表有什么不同?
我们平常的双向链表大概是这样子的

struct item_t {
	int value;
	item_t *pre, *next;
};
// 简单的3个节点插入过程
item_t head,node1,node2;
head.value=1,node1.value=2,node2.value=3;
head.pre=NULL;head.next=&node1;
node1.pre=&head;node1.next=&node2;
node2.pre=&node1;node2.next=NULL;

指针域都是一级指针,因为只需要存放下一个元素或者是上一个元素的地址;而TAILQ的prev指针存放的是上一个元素某一个成员的地址,而该成员的类型刚好是type*。

 

1. 队列初始化

        通过TAILQ_INIT对队列进行初始化,源码如下:

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

        通过代码可以看出,队列头中的first指针指向了NULL,last指针指向了first指针域。我们还是画图,直观感受下。

 

2. 获取队列中的第一个元素

        通过TAILQ_FIRST获取队列中的第一个元素,源码如下:

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

3. 获取当前元素的下一个元素

         通过TAILQ_NEXT可以获取当前元素的下一个元素,源码如下:

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

4. 在队列末尾插入元素

        通过TAILQ_INSERT_TAIL在队列末尾插入一个元素,源码如下:

#define	TAILQ_INSERT_TAIL(head, elm, field) do {			\
	QMD_TAILQ_CHECK_TAIL(head, field);				\
	TAILQ_NEXT((elm), field) = NULL;				\
	(elm)->field.tqe_prev = (head)->tqh_last;			\
	*(head)->tqh_last = (elm);					\
	(head)->tqh_last = &TAILQ_NEXT((elm), field);			\
	QMD_TRACE_HEAD(head);						\
	QMD_TRACE_ELEM(&(elm)->field);					\
} while (0)

        QMD_TAILQ_CHECK_TAIL,QMD_TRACE_HEAD,QMD_TRACE_ELEM这三个宏和调试信息相关会做一些必要的检查,我们可以先不管,这个宏就是在调整相关的指针指向。

       TAILQ_NEXT((elm), field) = NULL; 即将插入元素的next指针指向NULL

        (elm)->field.tqe_prev = (head)->tqh_last; 将插入元素的prev指针指向队列中的末尾元素的next指针

        *(head)->tqh_last = (elm); last指针指向了插入元素elm,解引用(*tqe_prev)之后就是本元素的内存地址

        (head)->tqh_last = &TAILQ_NEXT((elm), field);即队列头last指针指向了elm元素的next指针

 

 

 

 

 

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值