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指针