隊列,又稱爲隊列(英文queue),是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中通常用鏈表或者數組來實現。FreeBSD中的TAILQ把整個隊列頭抽象爲一個單獨的數據結構,我們先看看FreeBSD中的TAILQ相關宏,然後再舉例子理解這些宏。 這裏用最簡單的一個結構體來理解TAILQ,這個結構體中有一個int型整數,還有兩個分別指向前方和後方的指針。
1.描述前一個和下一個元素的結構體
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,如下:
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的隊列頭:
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))這個宏就比較簡單了,用臨時變量var來遍歷鏈表中的每一個元素。 這些宏就是幾個操作TAILQ經常使用的宏,還有一些諸如TAILQ_INSERT_HEAD等宏,類似,請自行看代碼,這裏就不一一敘述了。這篇文章主要是理解相關的宏和插入刪除過程,完整的應用例子請看下面的參考文獻。
參考資料:
- 1.FreeBSD官方網站http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/anoncvs.html 這裏的代碼示例是FreeBSD 7.0版本,上面這個鏈接教你如何獲得FreeBSD的代碼。
http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/cvs-tags.html 上面這個鏈接是FreeBSD的tag信息。 - 2.graphviz畫圖軟件http://www.graphviz.org/
這裏優美的圖是用AT&T強大的開源畫圖工具graphviz生成的。爲什麼不用visio,dia,diagram designer等拖拉拽的畫圖工具?因爲很難畫成心中想要的那種圖形,雖然還需要點學習時間,但是掌握之後畫拓撲圖就是傳說中的所想即所得。關於如何用graphviz畫出圖示中的雙向隊列,請看我的另一篇博文:http://verynix.com/graphviz-tailq.html - 3.其他TAILQ參考資料3.1 來自iteye的一篇很好的文章,舉了一個應用TAILQ的例子 http://bachmozart.iteye.com/blog/292836/ ,不過個人感覺裏面的那個圖對二級指針的描述還是不夠好。
3.2 這裏也提供了一個TAILQ應用的例子 http://blog.linuxphp.org/archives/1485/
3.3 來自FreeBSDchina論壇的講解 https://www.freebsdchina.org/forum/viewtopic.php?t=37913