Libevent源码分析-----TAILQ_QUEUE队列
? ? ? ??Libevent源碼中有一個(gè)queue.h文件,位于compat/sys目錄下。該文件里面定義了5個(gè)數(shù)據(jù)結(jié)構(gòu),其中TAILQ_QUEUE是使得最廣泛的。本文就說(shuō)一下這個(gè)數(shù)據(jù)結(jié)構(gòu)。
隊(duì)列結(jié)構(gòu)體:
? ? ? ??TAILQ_QUEUE由下面兩個(gè)結(jié)構(gòu)體一起配合工作。
#define TAILQ_HEAD(name, type) \ struct name { \struct type *tqh_first; /* first element */ \struct type **tqh_last; /* addr of last next element */ \ }//和前面的TAILQ_HEAD不同,這里的結(jié)構(gòu)體并沒(méi)有name.即沒(méi)有結(jié)構(gòu)體名。 //所以該結(jié)構(gòu)體只能作為一個(gè)匿名結(jié)構(gòu)體。所以,它一般都是另外一個(gè)結(jié)構(gòu)體 //或者共用體的成員 #define TAILQ_ENTRY(type) \ struct { \struct type *tqe_next; /* next element */ \struct type **tqe_prev; /* address of previous next element */ \ } ? ? ? ??由這兩個(gè)結(jié)構(gòu)體配合構(gòu)造出來(lái)的隊(duì)列一般如下圖所示:? ? ? ??
? ? ? ? 圖中,一級(jí)指針指向的是queue_entry_t這個(gè)結(jié)構(gòu)體,即存儲(chǔ)queue_entry_t這個(gè)結(jié)構(gòu)體的地址值。二級(jí)指針存儲(chǔ)的是一級(jí)地址變量的地址值。所以二級(jí)指針指向的是圖中的一級(jí)指針,而非結(jié)構(gòu)體。圖中的1,2, 3為隊(duì)列元素保存的一些值。
隊(duì)列操作宏函數(shù)以及使用例子:
? ? ? ??除了這兩個(gè)結(jié)構(gòu)體,在queue.h文件中,還為TAILQ_QUEUE定義了一系列的訪問(wèn)和操作函數(shù)。很不幸,它們是一些宏定義。這里就簡(jiǎn)單貼幾個(gè)函數(shù)(準(zhǔn)確來(lái)說(shuō),不是函數(shù))的代碼。#define TAILQ_FIRST(head) ((head)->tqh_first)#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)#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)#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)
? ? ? ??這些宏是很難看的,也沒(méi)必要直接去看這些宏。下面來(lái)看一個(gè)使用例子。有例子更容易理解。
//隊(duì)列中的元素結(jié)構(gòu)體。它有一個(gè)值,并且有前向指針和后向指針 //通過(guò)前后像指針,把隊(duì)列中的節(jié)點(diǎn)(元素)連接起來(lái) struct queue_entry_t {int value;//從TAILQ_ENTRY的定義可知,它只能是結(jié)構(gòu)體或者共用體的成員變量TAILQ_ENTRY(queue_entry_t)entry; };//定義一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體名為queue_head_t,成員變量類型為queue_entry_t //就像有頭節(jié)點(diǎn)的鏈表那樣,這個(gè)是隊(duì)列頭。它有兩個(gè)指針,分別指向隊(duì)列的頭和尾 TAILQ_HEAD(queue_head_t, queue_entry_t);int main(int argc, char **argv) {struct queue_head_t queue_head;struct queue_entry_t *q, *p, *s, *new_item;int i;TAILQ_INIT(&queue_head);for(i = 0; i < 3; ++i){p = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));p->value = i;//第三個(gè)參數(shù)entry的寫法很怪,居然是一個(gè)成員變量名(field)TAILQ_INSERT_TAIL(&queue_head, p, entry);//在隊(duì)尾插入數(shù)據(jù)}q = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));q->value = 10;TAILQ_INSERT_HEAD(&queue_head, q, entry);//在隊(duì)頭插入數(shù)據(jù)//現(xiàn)在q指向隊(duì)頭元素、p指向隊(duì)尾元素s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));s->value = 20;//在隊(duì)頭元素q的后面插入元素TAILQ_INSERT_AFTER(&queue_head, q, s, entry);s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));s->value = 30;//在隊(duì)尾元素p的前面插入元素TAILQ_INSERT_BEFORE(p, s, entry);//現(xiàn)在進(jìn)行輸出//獲取第一個(gè)元素s = TAILQ_FIRST(&queue_head);printf("the first entry is %d\n", s->value);//獲取下一個(gè)元素s = TAILQ_NEXT(s, entry);printf("the second entry is %d\n\n", s->value);//刪除第二個(gè)元素, 但并沒(méi)有釋放s指向元素的內(nèi)存TAILQ_REMOVE(&queue_head, s, entry);free(s);new_item = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));new_item->value = 100;s = TAILQ_FIRST(&queue_head);//用new_iten替換第一個(gè)元素TAILQ_REPLACE(&queue_head, s, new_item, entry);printf("now, print again\n");i = 0;TAILQ_FOREACH(p, &queue_head, entry)//用foreach遍歷所有元素{printf("the %dth entry is %d\n", i, p->value);}p = TAILQ_LAST(&queue_head, queue_head_t);printf("last is %d\n", p->value);p = TAILQ_PREV(p, queue_head_t, entry);printf("the entry before last is %d\n", p->value); }? ? ? ??例子并不難看懂。這里就不多講了。
展開(kāi)宏函數(shù):
? ? ? ??下面把這些宏翻譯一下(即展開(kāi)),顯示出它們的本來(lái)面貌。這當(dāng)然不是用人工方式去翻譯。而是用gcc 的-E選項(xiàng)。
? ? ? ??閱讀代碼時(shí)要注意,tqe_prev和tqh_last都是二級(jí)指針,行為會(huì)有點(diǎn)難理解。平常我們接觸到的雙向鏈表,next和prev成員都是一級(jí)指針。對(duì)于像鏈表A->B->C(把它們想象成雙向鏈表),通常B的prev指向A這個(gè)結(jié)構(gòu)體本身。此時(shí),B->prev->next指向了本身。但隊(duì)列Libevent的TAILQ_QUEUE,B的prev是一個(gè)二級(jí)指向,它指向的是A結(jié)構(gòu)體的next成員。此時(shí),*B->prev就指向了本身。當(dāng)然,這并不能說(shuō)用二級(jí)指針就方便。我覺(jué)得用二級(jí)指針理解起來(lái)更難,編寫代碼更容易出錯(cuò)。
//隊(duì)列中的元素結(jié)構(gòu)體。它有一個(gè)值,并且有前向指針和后向指針 //通過(guò)前后像指針,把隊(duì)列中的節(jié)點(diǎn)連接起來(lái) struct queue_entry_t {int value;struct{struct queue_entry_t *tqe_next;struct queue_entry_t **tqe_prev;}entry; };//就像有頭節(jié)點(diǎn)的鏈表那樣,這個(gè)是隊(duì)列頭。它有兩個(gè)指針,分別指向隊(duì)列的頭和尾 struct queue_head_t {struct queue_entry_t *tqh_first;struct queue_entry_t **tqh_last; };int main(int argc, char **argv) {struct queue_head_t queue_head;struct queue_entry_t *q, *p, *s, *new_item;int i;//TAILQ_INIT(&queue_head);do{(&queue_head)->tqh_first = 0;//tqh_last是二級(jí)指針,這里指向一級(jí)指針(&queue_head)->tqh_last = &(&queue_head)->tqh_first;}while(0);for(i = 0; i < 3; ++i){p = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));p->value = i;//TAILQ_INSERT_TAIL(&queue_head, p, entry);在隊(duì)尾插入數(shù)據(jù)do{(p)->entry.tqe_next = 0;//tqh_last存儲(chǔ)的是最后一個(gè)元素(隊(duì)列節(jié)點(diǎn))tqe_next成員//的地址。所以,tqe_prev指向了tqe_next。(p)->entry.tqe_prev = (&queue_head)->tqh_last;//tqh_last存儲(chǔ)的是最后一個(gè)元素(隊(duì)列節(jié)點(diǎn))tqe_next成員//的地址,所以*(&queue_head)->tqh_last修改的是最后一個(gè)//元素的tqe_next成員的值,使得tqe_next指向*p(新的隊(duì)列//節(jié)點(diǎn))。*(&queue_head)->tqh_last = (p);//隊(duì)頭結(jié)構(gòu)體(queue_head)的tqh_last成員保存新隊(duì)列節(jié)點(diǎn)的//tqe_next成員的地址值。即讓tqh_last指向tqe_next。(&queue_head)->tqh_last = &(p)->entry.tqe_next;}while(0);}q = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));q->value = 10;//TAILQ_INSERT_HEAD(&queue_head, q, entry);在隊(duì)頭插入數(shù)據(jù)do {//queue_head隊(duì)列中已經(jīng)有節(jié)點(diǎn)(元素了)。要對(duì)第一個(gè)元素進(jìn)行修改if(((q)->entry.tqe_next = (&queue_head)->tqh_first) != 0)(&queue_head)->tqh_first->entry.tqe_prev = &(q)->entry.tqe_next;else//queue_head隊(duì)列目前是空的,還沒(méi)有任何節(jié)點(diǎn)(元素)。修改queue_head即可(&queue_head)->tqh_last = &(q)->entry.tqe_next;//queue_head的first指針指向要插入的節(jié)點(diǎn)*q(&queue_head)->tqh_first = (q);(q)->entry.tqe_prev = &(&queue_head)->tqh_first;}while(0);//現(xiàn)在q指向隊(duì)頭元素、p指向隊(duì)尾元素s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));s->value = 20;//TAILQ_INSERT_AFTER(&queue_head, q, s, entry);在隊(duì)頭元素q的后面插入元素do{//q不是最后隊(duì)列中最后一個(gè)節(jié)點(diǎn)。要對(duì)q后面的元素進(jìn)行修改if (((s)->entry.tqe_next = (q)->entry.tqe_next) != 0)(s)->entry.tqe_next->entry.tqe_prev = &(s)->entry.tqe_next;else//q是最后一個(gè)元素。對(duì)queue_head修改即可(&queue_head)->tqh_last = &(s)->entry.tqe_next;(q)->entry.tqe_next = (s);(s)->entry.tqe_prev = &(q)->entry.tqe_next;}while(0);s = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));s->value = 30;//TAILQ_INSERT_BEFORE(p, s, entry); 在隊(duì)尾元素p的前面插入元素do{//無(wú)需判斷節(jié)點(diǎn)p前面是否還有元素。因?yàn)榧词箾](méi)有元素,queue_head的兩個(gè)//指針從功能上也相當(dāng)于一個(gè)元素。這點(diǎn)是采用二級(jí)指針的一大好處。(s)->entry.tqe_prev = (p)->entry.tqe_prev;(s)->entry.tqe_next = (p);*(p)->entry.tqe_prev = (s);(p)->entry.tqe_prev = &(s)->entry.tqe_next;}while(0);//現(xiàn)在進(jìn)行輸出// s = TAILQ_FIRST(&queue_head);s = ((&queue_head)->tqh_first);printf("the first entry is %d\n", s->value);// s = TAILQ_NEXT(s, entry);s = ((s)->entry.tqe_next);printf("the second entry is %d\n\n", s->value);//刪除第二個(gè)元素, 但并沒(méi)有釋放s指向元素的內(nèi)存//TAILQ_REMOVE(&queue_head, s, entry);do{if (((s)->entry.tqe_next) != 0)(s)->entry.tqe_next->entry.tqe_prev = (s)->entry.tqe_prev;else (&queue_head)->tqh_last = (s)->entry.tqe_prev;*(s)->entry.tqe_prev = (s)->entry.tqe_next;}while(0);free(s);new_item = (struct queue_entry_t*)malloc(sizeof(struct queue_entry_t));new_item->value = 100;//s = TAILQ_FIRST(&queue_head);s = ((&queue_head)->tqh_first);//用new_iten替換第一個(gè)元素//TAILQ_REPLACE(&queue_head, s, new_item, entry);do{if (((new_item)->entry.tqe_next = (s)->entry.tqe_next) != 0)(new_item)->entry.tqe_next->entry.tqe_prev = &(new_item)->entry.tqe_next;else(&queue_head)->tqh_last = &(new_item)->entry.tqe_next;(new_item)->entry.tqe_prev = (s)->entry.tqe_prev;*(new_item)->entry.tqe_prev = (new_item);}while(0);printf("now, print again\n");i = 0;//TAILQ_FOREACH(p, &queue_head, entry)//用foreach遍歷所有元素for((p) = ((&queue_head)->tqh_first); (p) != 0;(p) = ((p)->entry.tqe_next)){printf("the %dth entry is %d\n", i, p->value);}//p = TAILQ_LAST(&queue_head, queue_head_t);p = (*(((struct queue_head_t *)((&queue_head)->tqh_last))->tqh_last));printf("last is %d\n", p->value);//p = TAILQ_PREV(p, queue_head_t, entry);p = (*(((struct queue_head_t *)((p)->entry.tqe_prev))->tqh_last));printf("the entry before last is %d\n", p->value); }? ? ? ??代碼中有一些注釋,不懂的可以看看。其實(shí)對(duì)于鏈表操作,別人用文字說(shuō)再多都對(duì)自己理解幫助不大。只有自己動(dòng)手一步步把鏈表操作都畫出來(lái),這樣才能完全理解。
?
特殊指針操作:
? ? ? ??最后那兩個(gè)操作宏函數(shù)有點(diǎn)難理解,現(xiàn)在來(lái)講一下。在講之前,先看一個(gè)關(guān)于C語(yǔ)言指針的例子。#include<stdio.h>struct item_t {int a;int b;int c; };struct entry_t {int a;int b; };int main() {struct item_t item = { 1, 2, 3};entry_t *p = (entry_t*)(&item.b);printf("a = %d, b = %d\n", p->a, p->b);return 0; } ? ? ? ??代碼輸出的結(jié)果是:a = 2, b = 3
? ? ? ??對(duì)于entry_t *p, 指針p指向的內(nèi)存地址為&item.b。此時(shí)對(duì)于編譯器來(lái)說(shuō),它認(rèn)為從&item.b這個(gè)地址開(kāi)始,是一個(gè)entry_t結(jié)構(gòu)體的內(nèi)存區(qū)域。并且把前4個(gè)字節(jié)當(dāng)作entry_t成員變量a的值,后4個(gè)字節(jié)當(dāng)作entry_t成員變量b的值。所以就有了a = 2, b = 3這個(gè)輸出。
? ? ? ??好了,現(xiàn)在開(kāi)始講解那兩個(gè)難看懂的宏。先看一張圖。
? ? ? ??
? ? ? ??雖然本文最前面的圖布局更好看一點(diǎn),但這張圖才更能反映文中這兩個(gè)結(jié)構(gòu)體的內(nèi)存布局。不錯(cuò),tqe_next是在tqe_prev的前面。這使得tqe_next、tqe_prev于tqh_first、tqh_last的內(nèi)存布局一樣。一級(jí)指針在前,二級(jí)指針在后。
? ? ? ??現(xiàn)在來(lái)解析代碼中最后兩個(gè)宏函數(shù)。
隊(duì)尾節(jié)點(diǎn):
//p = TAILQ_LAST(&queue_head, queue_head_t); p = (*(((struct queue_head_t *)((&queue_head)->tqh_last))->tqh_last));? ? ? ??首先是(&queue_head)->tqh_last,它的值是最后一個(gè)元素的tqe_next這個(gè)成員變量的地址。然后把這個(gè)值強(qiáng)制轉(zhuǎn)換成struct queue_head_t *指針。此時(shí),相當(dāng)于有一個(gè)匿名的struct queue_head_t類型指針q。它指向的地址為隊(duì)列的最后一個(gè)節(jié)點(diǎn)的tqe_next成員變量的地址。無(wú)論一級(jí)還是二級(jí)指針,其都是指向另外一個(gè)地址。只是二級(jí)指針只能指向一個(gè)一級(jí)指針的地址。
? ? ? ??此時(shí),在編譯器看來(lái),從tqe_next這個(gè)變量的地址開(kāi)始,是一個(gè)struct queue_head_t結(jié)構(gòu)體的內(nèi)存區(qū)域。并且可以將代碼簡(jiǎn)寫成:p = (*(q->tqh_last));
? ? ? ??
? ? ? ??回想一下剛才的那個(gè)例子。q->tqh_last的值就是上圖中最后一個(gè)節(jié)點(diǎn)的tqe_prev成員變量的值。所以*(q->tqh_last))就相當(dāng)于*tqe_prev。注意,變量tqe_prev是一個(gè)二級(jí)指針,它指向倒數(shù)第二個(gè)節(jié)點(diǎn)的tqe_next成員。所以*tqe_prev獲取了倒數(shù)第二個(gè)節(jié)點(diǎn)的tqe_next成員的值。它的值就是最后一個(gè)節(jié)點(diǎn)的地址。最后,將這個(gè)地址賦值給p,此時(shí)p指向最后一個(gè)節(jié)點(diǎn)。完成了任務(wù)。好復(fù)雜的過(guò)程。
前一個(gè)節(jié)點(diǎn):
? ? ? ??現(xiàn)在來(lái)看一下最后那個(gè)宏函數(shù),代碼如下:
//p = TAILQ_PREV(p, queue_head_t, entry); p = (*(((struct queue_head_t *)((p)->entry.tqe_prev))->tqh_last)); ? ? ? ??注意,右邊的p此時(shí)是指向最后一個(gè)節(jié)點(diǎn)(元素)的。所以(p)->entry.tqe_prev就是倒數(shù)第二個(gè)節(jié)點(diǎn)tqe_next成員的地址。然后又強(qiáng)制轉(zhuǎn)換成struct queue_head_t指針。同樣,假設(shè)一個(gè)匿名的struct queue_head_t *q;此時(shí),宏函數(shù)可以轉(zhuǎn)換成: p = (*((q)->tqh_last));? ? ? ??同樣,在編譯器看來(lái),從倒數(shù)第二個(gè)參數(shù)節(jié)點(diǎn)tqe_next的地址開(kāi)始,是一個(gè)structqueue_head_t結(jié)構(gòu)體的內(nèi)存區(qū)域。所以tqh_last實(shí)際值是tqe_prev變量上的值,即tqe_prev指向的地址。*((q)->tqh_last)就是*tqe_prev,即獲取tqe_prev指向的倒數(shù)第三個(gè)節(jié)點(diǎn)的tqe_next的值。而該值正是倒數(shù)第二個(gè)節(jié)點(diǎn)的地址。將這個(gè)地址賦值給p,此時(shí),p就指向了倒數(shù)第二個(gè)節(jié)點(diǎn)。完成了TAILQ_PREV函數(shù)名的功能。
?
? ? ? ??這個(gè)過(guò)程確實(shí)有點(diǎn)復(fù)雜。而且還涉及到強(qiáng)制類型轉(zhuǎn)換。
? ? ? ??其實(shí),在TAILQ_LAST(&queue_head, queue_head_t);中,既然都可以獲取最后一個(gè)節(jié)點(diǎn)的tqe_next的地址值,那么直接將該值 + 4就可以得到tqe_precv的地址值了(假設(shè)為pp)。有了該地址值pp,那么直接**pp就可以得到最后一個(gè)節(jié)點(diǎn)的地址了。代碼如下:struct queue_entry_t **pp = (&queue_head)->tqh_last; pp += 1; //加1個(gè)指針的偏移量,在32位的系統(tǒng)中,就等于+4//因?yàn)檫@里得到的是二級(jí)指針的地址值,所以按理來(lái)說(shuō),得到的是一個(gè) //三級(jí)指針。故要用強(qiáng)制轉(zhuǎn)換成三級(jí)指針。 struct queue_entry_t ***ppp = (struct queue_entry_t ***)pp;s = **ppp; printf("the last is %d\n", s->value);
? ? ? ??該代碼雖然能得到正確的結(jié)果,但總感覺(jué)直接加上一個(gè)偏移量的方式太粗暴了。
? ? ? ??有一點(diǎn)要提出,+1那里并不會(huì)因?yàn)樵?4位的系統(tǒng)就不能運(yùn)行,一樣能正確運(yùn)行的。因?yàn)?不是表示一個(gè)字節(jié),而是一個(gè)指針的偏移量。在64位的系統(tǒng)上一個(gè)指針的偏移量為8字節(jié)。這種”指針 + 數(shù)值”,實(shí)際其增加的值為:數(shù)值 + sizeof(*指針)。不信的話,可以試一下char指針、int指針、結(jié)構(gòu)體指針(結(jié)構(gòu)體要有多個(gè)成員)。
?
?
? ? ? ??好了,還是回到最開(kāi)始的問(wèn)題上吧。這個(gè)TAILQ_QUEUE隊(duì)列是由兩部分組成:隊(duì)列頭和隊(duì)列節(jié)點(diǎn)。在Libevent中,隊(duì)列頭一般是event_base結(jié)構(gòu)體的一個(gè)成員變量,而隊(duì)列節(jié)點(diǎn)則是event結(jié)構(gòu)體。比如event_base結(jié)構(gòu)體里面有一個(gè)struct event_list eventqueue;其中,結(jié)構(gòu)體struct event_list如下定義://event_struct.h TAILQ_HEAD (event_list, event);//所以event_list的定義展開(kāi)后如下: struct event_list {struct event *tqh_first;struct event **tqh_last; }; ? ? ? ??在event結(jié)構(gòu)體中,則有幾個(gè)TAILQ_ENTRY(event)類型的成員變量。這是因?yàn)楦鶕?jù)不同的條件,采用不同的隊(duì)列把這些event結(jié)構(gòu)體連在一起,放到一條隊(duì)列中。
總結(jié)
以上是生活随笔為你收集整理的Libevent源码分析-----TAILQ_QUEUE队列的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: js操作DOM对象(节点的增删改)
- 下一篇: AI 趋势