• 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)))

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

  • 相关阅读:
    宁泽涛世锦赛夺冠
    iOS手势识别的详细使用(拖动,缩放,旋转,点击,手势依赖,自定义手势)
    在xcode里面如何利用instrument
    Xcode 中搜索任何条件文本
    十大算法
    Object c中的alloc和init问题
    My97DaePicker 用js实现文本框日期相减求天数
    js 取消listbox选中的项
    C# OR/Mapping 数据处理模式学习
    ASP.NET实现列表页连接查询 拼接sql语句 绑定grivdView
  • 原文地址:https://www.cnblogs.com/fuzidage/p/14482501.html
Copyright © 2020-2023  润新知