TAILQ队列实现原理

tailq队列实现原理

TAILQ队列是FreeBSD内核中的一种队列数据结构,主要是把队列头抽象成一个单独的结构体。它实现在Linux queue中。

queue 简介

可以include <sys/queue.h>后直接使用。queue 分为 SLIST、LIST、STAILQ、TAILQ、CIRCLEQ 。queue 的所有源码都是宏定义,因此完全包含于queue.h当中,无需编译为库文件。

可以从toolchains或者系统路径/usr/include/x86_64-linux-gnu/sys/queue.h找到实现。

 

SLIST:

SLIST 是Singly-linked List 的缩写,意为单向无尾链表。

 

STAILQ:

单向有尾链表,节点n为尾节点。

 

LIST:

双向无尾链表

 

TAILQ:

双向有尾链表

 

CIRCLEQ:

双向循环链表

 

TAILQ实现原理:

双向有尾链表,也就是有一个表头和表尾,表头指向节点1和尾节点。

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

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

/*tqe_next是指向下一个元素的指针,tqe_prev是指向前一个元素的tqe_next地址,对它解引用后

(*tqe_priv)指向当前元素的地址。*/

如:

struct tailq_entry {
  int val;
  TAILQ_ENTRY(int_node);
};

 

队列头:

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

STAILQ_HEAD(my_tailq,  tailq_entry) queue_head;

 

 我们先看TAILQ_HEAD:

tqh_first为队列第一个元素的地址;

tqh_last为最后一个元素tqe_next的地址;

tqh_last指向的指针为0;

再看TAILQ_ENTRY:

tqe_next为队列下一个元素的地址;

tqe_prev为队列上一个元素tqe_next的地址;

tqe_prev指向的指针为当前元素的地址;

初始化:

 

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

插入元素:

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

1.插入1个node时图解:

1.1 将要插入的node加入到尾部:

  (elm)->field.tqe_next = NULL;                         

  (elm)->field.tqe_prev = (head)->tqh_last;//将要插入的节点prev指向最后一个node      

 

1.2 update head node:

    *(head)->tqh_last = (elm);          

    (head)->tqh_last = &(elm)->field.tqe_next;

 

同理多个元素时尾插:

1.1 将要插入的node加入到尾部:

 

1.2 update head node:

    *(head)->tqh_last = (elm);           //尾节点指向新的尾巴

    (head)->tqh_last = &(elm)->field.tqe_next; //head的last指向新的尾巴

 

删除元素:

#define TAILQ_REMOVE(head, elm, field) do {                                 
    if (((elm)->field.tqe_next) != NULL)                                    
        (elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev;      
    else                                                                    
        (head)->tqh_last = (elm)->field.tqe_prev;                           
    *(elm)->field.tqe_prev = (elm)->field.tqe_next;                         
} while (0)

 

  1. 我们现在要把val=3的elm删除:

elm中的tqe_next不为空,表示elm不是尾节点。那么

(elm)->field.tqe_next->field.tqe_prev = (elm)->field.tqe_prev;   

*(elm)->field.tqe_prev = (elm)->field.tqe_next;

这2句执行完后,

 

  然后free掉该elm,

2. 同理再删除val=2的elm:

 

 然后free掉该elm,

 

3. 我们现在要把val=4的elm删除:

elm中的tqe_next为空,表示elm是尾节点。那么

(head)->tqh_last = (elm)->field.tqe_prev;               //让head的last指向新的尾巴        

*(elm)->field.tqe_prev = (elm)->field.tqe_next;    //让elm的前一个node的next指向该elm的后一个node

 

第一个元素:

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

最后一个元素:

#define        TAILQ_LAST(head, headname) 
(*(((struct headname *)((head)->tqh_last))->tqh_last))

这个实现看起来有点绕,我们先做一个实验,

 typedef struct _QUEUE_ITEM {
    int value;
    TAILQ_ENTRY(QUEUE_ITEM) entries;
}QUEUE_ITEM;

TAILQ_HEAD(TAIL_QUEUE, QUEUE_ITEM) queue_head;

int main(int argc, char **argv) {
    QUEUE_ITEM *item[5];
    TAILQ_INIT(&queue_head);

    int i = 0;
    for (i = 0; i < 5; i += 1) {
        item[i] = (struct QUEUE_ITEM*)malloc(sizeof(QUEUE_ITEM));
        item[i]->value = i;
        TAILQ_INSERT_TAIL(&queue_head, item[i], entries);
    }

    for(i = 0; i < 5; i += 1)
    {
            printf("item[%d]: item:%#x, next:%#x,&next:%#x, prev:%#x, *prev:%#x
",
        i, item[i], item[i]->entries.tqe_next, &(item[i]->entries.tqe_next), item[i]->entries.tqe_prev, *(item[i]->entries.tqe_prev));
    }

    printf("queue_head:%#x, first:%#x, last:%#x
", &queue_head, queue_head.tqh_first, queue_head.tqh_last);
    printf("last item:%p
", TAILQ_LAST(&queue_head, TAIL_QUEUE));
}

打印结果如下:

 

可以用图形来描述:

 

 所以把TAILQ_LAST(&queue_head, TAIL_QUEUE);这句话展开变成

(*(((struct TAIL_QUEUE*)((&queue_head)->tqh_last))->tqh_last))

((struct TAIL_QUEUE*)((&queue_head)->tqh_last))这句话,我们把地址0x601060代入进去得0x602098,即为

 

然后(((struct TAIL_QUEUE*)((&queue_head)->tqh_last))->tqh_last)得到0x602078,

认真的同学此时已经发现,此时对应倒数第二元素的next地址,

最后取(*(((struct TAIL_QUEUE*)((&queue_head)->tqh_last))->tqh_last))得到0x602090,

即为最后一个元素的地址。

总结:这里核心其实就是把最后一个元素的entries成员当成head指针来使用。因为本质上最后一个节点的TAILQ_ENTRY域

和TAILQ_HEAD是同样的结构。

下一个元素:

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

前一个元素:

#define        TAILQ_PREV(elm, headname, field) 
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))

这里和TAILQ_LAST原理一样,将0x602090代入进去得

 

 然后对*(0x602058)得0x602070,即得到了前一个node的地址。

判断是否空:

#define        TAILQ_EMPTY(head)                ((head)->tqh_first == NULL)

判断是否第一个元素:

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

遍历:

#define        TAILQ_FOREACH(var, head, field)                                        
for ((var) = ((head)->tqh_first);                                
(var);                                                        
(var) = ((var)->field.tqe_next))

倒遍历:

#define        TAILQ_FOREACH_REVERSE(var, head, headname, field)                
for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));        
(var);                                                        
(var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last)))

当看懂之前的最后一个元素原理时,倒遍历的实现是不是超级简单。

原文地址:https://www.cnblogs.com/fuzidage/p/14482501.html