(轉)深刻理解TAILQ隊列

又開始看libevent了,發現本身已經忘了它使用的基本數據結構,當初看的時候沒有作筆記,結果就是如今又要重看一遍。node

發現一個不錯的文章。這個實現主要的不一樣是使用了二級指針。linux

先給出一個二級指針的例子。下面這個是個很差的反例,argc須要傳遞tv和time_event進去,因此須要使用結構體。數組

咱們使用了全局變量和一個二維指針,保存了time_event的地址。若是使用結構體,那麼保存的地址須要先初始化。數據結構

struct timeval tv;
void time_cb(int fd, short event, void *argc)
{
    printf("timer wakeup/n");
    event_add((*(struct event**)(argc)), &tv); // reschedule timer
}

int main(int argc, char **argv)
{
    struct event_base *base = event_base_new();
    struct evconnlistener *listener;
    struct event *signal_event;
    //void* arg=nullptr;
    //{
        tv.tv_sec = 10; // 10s period
        tv.tv_usec = 0;
        struct event *time_event = evtimer_new(base, time_cb, &time_event);
        evtimer_add(time_event, &tv);
    //}
    ....
}

 

 

工做的主要內容是tcp/ip,平臺是FreeBSD,並且在內核態開發,因此不少狀況下會涉及內核的一些數據結構和宏,好比說mbuf和TAILQ等。 TAILQ是FreeBSD/linux內核對雙向隊列操做的一種抽象,抽象程度不亞於C++,能實現操做隊列須要的各類操做:插入元素,刪除元素,遍歷隊列等。這個隊列的優勢是插入元素很快。 這裏先回顧一下隊列的特色(來自維基百科 http://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97):tcp

隊列,又稱為佇列(英文queue),是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中一般用鏈表或者數組來實現。

FreeBSD中的TAILQ把整個隊列頭抽象爲一個單獨的數據結構,咱們先看看FreeBSD中的TAILQ相關宏,而後再舉例子理解這些宏。 這裏用最簡單的一個結構體來理解TAILQ,這個結構體中有一個int型整數,還有兩個分別指向前方和後方的指針。

1.描述前一個和下一個元素的結構體spa

458 #define TAILQ_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 {
                struct int_node *tqe_next;  /* next element */ 
	        sturct int_node **tqe_prev; /* address of previous next element */
        };
 };

例如:
2.隊列頭
TAILQ把整個隊列頭單獨抽象爲一個結構體TAILQ_HEAD,以下:code

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的隊列頭:blog

TAILQ_HEAD(int_head, int_node) 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))
相關文章
相關標籤/搜索