1. 程式人生 > >Libevent原始碼分析-----event_io_map雜湊表

Libevent原始碼分析-----event_io_map雜湊表

  上一篇部落格說到了TAILQ_QUEUE佇列,它可以把多個event結構體連在一起。是一種歸類方式。本文也將講解一種將event歸類、連在一起的結構:雜湊結構。

雜湊結構體:

        雜湊結構由下面幾個結構體一起配合工作:


struct event_list

{

struct event *tqh_first;

struct event **tqh_last;

};


struct evmap_io {

//TAILQ_HEAD (event_list, event);

struct event_list events;

ev_uint16_t nread;

ev_uint16_t nwrite;

};


struct event_map_entry {

HT_ENTRY(event_map_entry) map_node; //next指標

evutil_socket_t fd;

union { /* This is a union in case we need to make more things that can

be in the hashtable. */

struct evmap_io evmap_io;

} ent;

};


struct event_io_map

{

//雜湊表

struct event_map_entry **hth_table;

//雜湊表的長度

unsigned hth_table_length;

//雜湊的元素個數

unsigned hth_n_entries;

//resize 之前可以存多少個元素

//在event_io_map_HT_GROW函式中可以看到其值為hth_table_length的

//一半。但hth_n_entries>=hth_load_limit時,就會發生增長雜湊表的長度

unsigned hth_load_limit;

//後面素數表中的下標值。主要是指明用到了哪個素數

int hth_prime_idx;

};

        結構體event_io_map指明瞭雜湊表的儲存位置、雜湊表的長度、元素個數等資料。該雜湊表是使用鏈地址法解決衝突問題的,這一點可以從hth_talbe成員變數看到。它是一個二級指標,因為雜湊表的元素是event_map_entry指標。

        除了怎麼解決雜湊衝突外,雜湊表還有一個問題要解決,那就是雜湊函式。這裡的雜湊函式就是模(%)。用event_map_entry結構體中的fd成員值模 event_io_map結構體中的hth_table_length。

        由上面那些結構體配合得到的雜湊表結構如下圖所示:

        

        從上圖可以看到,兩個發生了衝突的雜湊表元素event_map_entry用一個next指向連在一起了(鏈地址解決衝突)。

        另外,從圖或者從前面關於event_map_entry結構體的定義可以看到,它有一個evmap_io結構體。而這個evmap_io結構體又有一個struct event_list 型別的成員,而struct event_list型別就是一個TAILQ_HEAD。這正是前一篇部落格說到的TAILQ_QUEUE佇列的佇列頭。從這一點可以看到,這個雜湊結構還是比較複雜的。

        為什麼在雜湊表的元素裡面,還會有一個TAILQ_QUEUE佇列呢?這得由Libevent的一個特徵說起。Libevent允許用同一個檔案描述符fd或者訊號值,呼叫event_new、event_add多次。所以,同一個fd或者訊號值就可以對應多個event結構體了。所以這個TAILQ_QUEUE佇列就是將這些具有相同fd或者訊號值的多個event結構體連一起。

什麼情況會使用雜湊表:

        有一點需要說明,那就是Libevent中的雜湊表只會用於Windows系統,像遵循POSIX標準的OS是不會用到雜湊表的。從下面的定義可以看到這一點。

//event-internal.h檔案

#ifdef WIN32

/* If we're on win32, then file descriptors are not nice low densely packed

integers. Instead, they are pointer-like windows handles, and we want to

use a hashtable instead of an array to map fds to events.

*/

#define EVMAP_USE_HT

#endif



#ifdef EVMAP_USE_HT

#include "ht-internal.h"

struct event_map_entry;

HT_HEAD(event_io_map, event_map_entry);

#else

#define event_io_map event_signal_map

#endif

        可以看到,如果是非Windows系統,那麼event_io_map就會被定義成event_signal_map(這是一個很簡單的結構)。而在Windows系統,那麼就由HT_HEAD這個巨集定義event_io_map。最後得到的event_io_map就是本文最前面所示的那樣。

        為什麼只有在Windows系統才會使用這個雜湊表呢?程式碼裡面的註釋給出了一些解釋。因為在Windows系統裡面,檔案描述符是一個比較大的值,不適合放到event_signal_map結構中。而通過雜湊(模上一個小的值),就可以變得比較小,這樣就可以放到雜湊表的陣列中了。而遵循POSIX標準的檔案描述符是從0開始遞增的,一般都不會太大,適用於event_signal_map。

雜湊函式:

        前面說到雜湊函式是 用檔案描述符fd 模 雜湊表的長度。實際上,並不是直接用fd,而是用一個叫hashsocket的函式將這個fd進行一些處理後,才去模 雜湊表的長度。下面是hashsocket函式的實現:

//evmap.c檔案

/* Helper used by the event_io_map hashtable code; tries to return a good hash

* of the fd in e->fd. */

static inline unsigned

hashsocket(struct event_map_entry *e)

{

/* On win32, in practice, the low 2-3 bits of a SOCKET seem not to

* matter. Our hashtable implementation really likes low-order bits,

* though, so let's do the rotate-and-add trick. */

unsigned h = (unsigned) e->fd;

h += (h >> 2) | (h << 30);

return h;

}

        前面的event_map_entry結構體中,還有一個HT_ENTRY的巨集。從名字來看,它是一個雜湊表的表項。它是一個條件巨集,定義如下:

//ht-internal.h檔案

#ifdef HT_CACHE_HASH_VALUES

#define HT_ENTRY(type) \

struct { \

struct type *hte_next; \

unsigned hte_hash; \

}

#else

#define HT_ENTRY(type) \

struct { \

struct type *hte_next; \

}

#endif

        可以看到,如果定義了HT_CACHE_HASH_VALUES巨集,那麼就會多一個hte_hash變數。從巨集的名字來看,這是一個cache。不錯,變數hte_hash就是用來儲存前面的hashsocket的返回值。當第一次計算得到後,就存放到hte_hash變數中。以後需要用到(會經常用到),就直接向這個變數要即可,無需再次計算hashsocket函式。如果沒有這個變數,那麼需要用到這個值,都要呼叫hashsocket函式計算一次。這一點從後面的程式碼可以看到。

雜湊表操作函式:

        ht-internal.h檔案裡面定義了一系列的雜湊函式的操作函式。下來就列出這些函式。如果開啟ht-internal.h檔案,就發現,它是巨集的天下。該檔案的函式都是由巨集定義生成的。下面就貼出巨集定義展開後的函式。同前一篇博文那樣,是用gcc的-E選項展開的。下面的程式碼比較長,要有心理準備。

struct event_list

{

struct event *tqh_first;

struct event **tqh_last;

};


struct evmap_io

{

struct event_list events;

ev_uint16_t nread;

ev_uint16_t nwrite;

};



struct event_map_entry

{

struct

{

struct event_map_entry *hte_next;

#ifdef HT_CACHE_HASH_VALUES

unsigned hte_hash;

#endif

}map_node;


evutil_socket_t fd;

union

{

struct evmap_io evmap_io;

}ent;

};


struct event_io_map

{

//雜湊表

struct event_map_entry **hth_table;

//雜湊表的長度

unsigned hth_table_length;

//雜湊的元素個數

unsigned hth_n_entries;

//resize 之前可以存多少個元素

//在event_io_map_HT_GROW函式中可以看到其值為hth_table_length的

//一半。但hth_n_entries>=hth_load_limit時,就會發生增長雜湊表的長度

unsigned hth_load_limit;

//後面素數表中的下標值。主要是指明用到了哪個素數

int hth_prime_idx;

};




int event_io_map_HT_GROW(struct event_io_map *ht, unsigned min_capacity);

void event_io_map_HT_CLEAR(struct event_io_map *ht);


int _event_io_map_HT_REP_IS_BAD(const struct event_io_map *ht);


//初始化event_io_map

static inline void event_io_map_HT_INIT(struct event_io_map *head)

{

head->hth_table_length = 0;

head->hth_table = NULL;

head->hth_n_entries = 0;

head->hth_load_limit = 0;

head->hth_prime_idx = -1;

}



//在event_io_map這個雜湊表中,找個表項elm

//在下面還有一個相似的函式,函式名少了最後的_P。那個函式的返回值

//是event_map_entry *。從查詢來說,後面那個函式更適合。之所以

//有這個函式,是因為雜湊表還有replace、remove這些操作。對於

//A->B->C這樣的連結串列。此時,要replace或者remove節點B。

//如果只有後面那個查詢函式,那麼只能查詢並返回一個指向B的指標。

//此時將無法修改A的next指標了。所以本函式有存在的必要。

//在本檔案中,很多函式都使用了event_map_entry **。

//因為event_map_entry **型別變數,既可以修改本元素的hte_next變數

//也能指向下一個元素。


//該函式返回的是查詢節點的前驅節點的hte_next成員變數的地址。

//所以返回值肯定不會為NULL,但是對返回值取*就可能為NULL

static inline struct event_map_entry **

_event_io_map_HT_FIND_P(struct event_io_map *head,

struct event_map_entry *elm)

{

struct event_map_entry **p;

if (!head->hth_table)

return NULL;


#ifdef HT_CACHE_HASH_VALUES

p = &((head)->hth_table[((elm)->map_node.hte_hash)

% head->hth_table_length]);

#else

p = &((head)->hth_table[(hashsocket(*elm))%head->hth_table_length]);

#endif


//這裡的雜湊表是用鏈地址法解決雜湊衝突的。

//上面的 % 只是找到了衝突鏈的頭。現在是在衝突鏈中查詢。

while (*p)

{

//判斷是否相等。在實現上,只是簡單地根據fd來判斷是否相等

if (eqsocket(*p, elm))

return p;


//p存放的是hte_next成員變數的地址

p = &(*p)->map_node.hte_next;

}


return p;

}


/* Return a pointer to the element in the table 'head' matching 'elm',

* or NULL if no such element exists */

//在event_io_map這個雜湊表中,找個表項elm

static inline struct event_map_entry *

event_io_map_HT_FIND(const struct event_io_map *head,

struct event_map_entry *elm)

{

struct event_map_entry **p;

struct event_io_map *h = (struct event_io_map *) head;


#ifdef HT_CACHE_HASH_VALUES

do

{ //計算雜湊值

(elm)->map_node.hte_hash = hashsocket(elm);

} while(0);

#endif


p = _event_io_map_HT_FIND_P(h, elm);

return p ? *p : NULL;

}



/* Insert the element 'elm' into the table 'head'. Do not call this

* function if the table might already contain a matching element. */

static inline void

event_io_map_HT_INSERT(struct event_io_map *head,

struct event_map_entry *elm)

{

struct event_map_entry **p;

if (!head->hth_table || head->hth_n_entries >= head->hth_load_limit)

event_io_map_HT_GROW(head, head->hth_n_entries+1);


++head->hth_n_entries;


#ifdef HT_CACHE_HASH_VALUES

do

{ //計算雜湊值.此雜湊不同於用%計算的簡單雜湊。

//存放到hte_hash變數中,作為cache

(elm)->map_node.hte_hash = hashsocket(elm);

} while (0);


p = &((head)->hth_table[((elm)->map_node.hte_hash)

% head->hth_table_length]);

#else

p = &((head)->hth_table[(hashsocket(*elm))%head->hth_table_length]);

#endif



//使用頭插法,即後面才插入的連結串列,反而會在連結串列頭。

elm->map_node.hte_next = *p;

*p = elm;

}



/* Insert the element 'elm' into the table 'head'. If there already

* a matching element in the table, replace that element and return

* it. */

static inline struct event_map_entry *

event_io_map_HT_REPLACE(struct event_io_map *head,

struct event_map_entry *elm)

{

struct event_map_entry **p, *r;


if (!head->hth_table || head->hth_n_entries >= head->hth_load_limit)

event_io_map_HT_GROW(head, head->hth_n_entries+1);


#ifdef HT_CACHE_HASH_VALUES

do

{

(elm)->map_node.hte_hash = hashsocket(elm);

} while(0);

#endif


p = _event_io_map_HT_FIND_P(head, elm);


//由前面的英文註釋可知,這個函式是替換插入一起進行的。如果雜湊表

//中有和elm相同的元素(指的是event_map_entry的fd成員變數值相等)

//那麼就發生替代(其他成員變數值不同,所以不是完全相同,有替換意義)

//如果雜湊表中沒有和elm相同的元素,那麼就進行插入操作


//指標p存放的是hte_next成員變數的地址

//這裡的p存放的是被替換元素的前驅元素的hte_next變數地址

r = *p; //r指向了要替換的元素。有可能為NULL

*p = elm; //hte_next變數被賦予新值elm


//找到了要被替換的元素r(不為NULL)

//而且要插入的元素地址不等於要被替換的元素地址

if (r && (r!=elm))

{

elm->map_node.hte_next = r->map_node.hte_next;


r->map_node.hte_next = NULL;

return r; //返回被替換掉的元素

}

else //進行插入操作

{

//這裡貌似有一個bug。如果前一個判斷中,r 不為 NULL,而且r == elm

//對於同一個元素,多次呼叫本函式。就會出現這樣情況。

//此時,將會來到這個else裡面

//實際上沒有增加元素,但元素的個數卻被++了。因為r 的地址等於 elm

//所以 r = *p; *p = elm; 等於什麼也沒有做。(r == elm)

//當然,因為這些函式都是Libevent內部使用的。如果它保證不會同於同

//一個元素呼叫本函式,那麼就不會出現bug

++head->hth_n_entries;

return NULL; //插入操作返回NULL,表示沒有替換到元素

}

}



/* Remove any element matching 'elm' from the table 'head'. If such

* an element is found, return it; otherwise return NULL. */

static inline struct event_map_entry *

event_io_map_HT_REMOVE(struct event_io_map *head,

struct event_map_entry *elm)

{

struct event_map_entry **p, *r;


#ifdef HT_CACHE_HASH_VALUES

do

{

(elm)->map_node.hte_hash = hashsocket(elm);

} while (0);

#endif


p = _event_io_map_HT_FIND_P(head,elm);


if (!p || !*p)//沒有找到

return NULL;


//指標p存放的是hte_next成員變數的地址

//這裡的p存放的是被替換元素的前驅元素的hte_next變數地址

r = *p; //r現在指向要被刪除的元素

*p = r->map_node.hte_next;

r->map_node.hte_next = NULL;


--head->hth_n_entries;


return r;

}



/* Invoke the function 'fn' on every element of the table 'head',

using 'data' as its second argument. If the function returns

nonzero, remove the most recently examined element before invoking

the function again. */

static inline void

event_io_map_HT_FOREACH_FN(struct event_io_map *head,

int (*fn)(struct event_map_entry *, void *),

void *data)

{

unsigned idx;

struct event_map_entry **p, **nextp, *next;


if (!head->hth_table)

return;


for (idx=0; idx < head->hth_table_length; ++idx)

{

p = &head->hth_table[idx];


while (*p)

{

//像A->B->C連結串列。p存放了A元素中hte_next變數的地址

//*p則指向B元素。nextp存放B的hte_next變數的地址

//next指向C元素。

nextp = &(*p)->map_node.hte_next;

next = *nextp;


//對B元素進行檢查

if (fn(*p, data))

{

--head->hth_n_entries;

//p存放了A元素中hte_next變數的地址

//所以*p = next使得A元素的hte_next變數值被賦值為

//next。此時連結串列變成A->C。即使拋棄了B元素。不知道

//呼叫方是否能釋放B元素的記憶體。

*p = next;

}

else

{

p = nextp;

}

}

}

}



/* Return a pointer to the first element in the table 'head', under

* an arbitrary order. This order is stable under remove operations,

* but not under others. If the table is empty, return NULL. */

//獲取第一條衝突鏈的第一個元素

static inline struct event_map_entry **

event_io_map_HT_START(struct event_io_map *head)

{

unsigned b = 0;


while (b < head->hth_table_length)

{

//返回雜湊表中,第一個不為NULL的節點

//即有event_map_entry元素的節點。

//找到鏈。因為是雜湊。所以不一定雜湊表中的每一個節點都存有元素

if (head->hth_table[b])

return &head->hth_table[b];


++b;

}


return NULL;

}




/* Return the next element in 'head' after 'elm', under the arbitrary

* order used by HT_START. If there are no more elements, return

* NULL. If 'elm' is to be removed from the table, you must call

* this function for the next value before you remove it.

*/

static inline struct event_map_entry **

event_io_map_HT_NEXT(struct event_io_map *head,

struct event_map_entry **elm)

{

//本雜湊解決衝突的方式是鏈地址

//如果引數elm所在的鏈地址中,elm還有下一個節點,就直接返回下一個節點

if ((*elm)->map_node.hte_next)

{

return &(*elm)->map_node.hte_next;

}

else //否則取雜湊表中的下一條鏈中第一個元素

{

#ifdef HT_CACHE_HASH_VALUES

unsigned b = (((*elm)->map_node.hte_hash)

% head->hth_table_length) + 1;

#else

unsigned b = ( (hashsocket(*elm)) % head->hth_table_length) + 1;

#endif


while (b < head->hth_table_length)

{

//找到鏈。因為是雜湊。所以不一定雜湊表中的每一個節點都存有元素

if (head->hth_table[b])

return &head->hth_table[b];

++b;

}


return NULL;

}

}




//功能同上一個函式。只是引數不同,另外本函式還會使得--hth_n_entries

//該函式主要是返回elm的下一個元素。並且雜湊表的總元素個數減一。

//主調函式會負責釋放*elm指向的元素。無需在這裡動手

//在evmap_io_clear函式中,會呼叫本函式。

static inline struct event_map_entry **

event_io_map_HT_NEXT_RMV(struct event_io_map *head,

struct event_map_entry **elm)

{

#ifdef HT_CACHE_HASH_VALUES

unsigned h = ((*elm)->map_node.hte_hash);

#else

unsigned h = (hashsocket(*elm));

#endif


//elm變數變成存放下一個元素的hte_next的地址

*elm = (*elm)->map_node.hte_next;


--head->hth_n_entries;


if (*elm)

{

return elm;

}

else

{

unsigned b = (h % head->hth_table_length)+1;


while (b < head->hth_table_length)

{

if (head->hth_table[b])

return &head->hth_table[b];


++b;

}


return NULL;

}

}




//素數表。之所以去素數,是因為在取模的時候,素數比合數更有優勢。

//聽說是更散,更均勻

static unsigned event_io_map_PRIMES[] =

{

//素數表的元素具有差不多2倍的關係。

//這使得擴容操作不會經常發生。每次擴容都預留比較大的空間

53, 97, 193, 389, 769, 1543, 3079,

6151, 12289, 24593, 49157, 98317,

196613, 393241, 786433, 1572869, 3145739,

6291469, 12582917, 25165843, 50331653, 100663319,

201326611, 402653189, 805306457, 1610612741

};



//素數表中,元素的個數。

static unsigned event_io_map_N_PRIMES =

(unsigned)(sizeof(event_io_map_PRIMES)

/sizeof(event_io_map_PRIMES[0]));



/* Expand the internal table of 'head' until it is large enough to

* hold 'size' elements. Return 0 on success, -1 on allocation

* failure. */

int event_io_map_HT_GROW(struct event_io_map *head, unsigned size)

{

unsigned new_len, new_load_limit;

int prime_idx;


struct event_map_entry **new_table;

//已經用到了素數表中最後一個素數,不能再擴容了。

if (head->hth_prime_idx == (int)event_io_map_N_PRIMES - 1)

return 0;


//雜湊表中還夠容量,無需擴容

if (head->hth_load_limit > size)

return 0;


prime_idx = head->hth_prime_idx;


do {

new_len = event_io_map_PRIMES[++prime_idx];


//從素數表中的數值可以看到,後一個差不多是前一個的2倍。

//從0.5和後的new_load_limit <= size,可以得知此次擴容

//至少得是所需大小(size)的2倍以上。免得經常要進行擴容

new_load_limit = (unsigned)(0.5*new_len);

} while (new_load_limit <= size

&& prime_idx < (int)event_io_map_N_PRIMES);


if ((new_table = mm_malloc(new_len*sizeof(struct event_map_entry*))))

{

unsigned b;

memset(new_table, 0, new_len*sizeof(struct event_map_entry*));


for (b = 0; b < head->hth_table_length; ++b)

{

struct event_map_entry *elm, *next;

unsigned b2;


elm = head->hth_table[b];

while (elm) //該節點有衝突鏈。遍歷衝突鏈中所有的元素

{

next = elm->map_node.hte_next;


//衝突鏈中的元素,相對於前一個素數同餘(即模素數後,結果相當)

//但對於現在的新素數就不一定同餘了,即它們不一定還會衝突

//所以要對衝突鏈中的所有元素都再次雜湊,並放到它們應該在的地方

//b2存放了再次雜湊後,元素應該存放的節點下標。

#ifdef HT_CACHE_HASH_VALUES

b2 = (elm)->map_node.hte_hash % new_len;

#else

b2 = (hashsocket(*elm)) % new_len;

#endif

//用頭插法插入資料

elm->map_node.hte_next = new_table[b2];

new_table[b2] = elm;


elm = next;

}

}


if (head->hth_table)

mm_free(head->hth_table);


head->hth_table = new_table;

}

else

{

unsigned b, b2;


//剛才mm_malloc失敗,可能是記憶體不夠。現在用更省記憶體的

//mm_realloc方式。當然其代價就是更耗時(下面的程式碼可以看到)。

//前面的mm_malloc會同時有hth_table和new_table兩個陣列。

//而mm_realloc則只有一個數組,所以省記憶體,省了一個hth_table陣列

//的記憶體。此時,new_table陣列的前head->hth_table_length個

//元素存放了原來的衝突鏈的頭部。也正是這個原因導致後面程式碼更耗時。

//其實,只有在很特殊的情況下,這個函式才會比mm_malloc省記憶體.

//就是堆記憶體head->hth_table區域的後面剛好有一段可以用的記憶體。

//具體的,可以搜一下realloc這個函式。

new_table = mm_realloc(head->hth_table,

new_len*sizeof(struct event_map_entry*));


if (!new_table)

return -1;


memset(new_table + head->hth_table_length, 0,

(new_len - head->hth_table_length)*sizeof(struct event_map_entry*)

);


for (b=0; b < head->hth_table_length; ++b)

{

struct event_map_entry *e, **pE;


for (pE = &new_table[b], e = *pE; e != NULL; e = *pE)

{


#ifdef HT_CACHE_HASH_VALUES

b2 = (e)->map_node.hte_hash % new_len;

#else

b2 = (hashsocket(*elm)) % new_len;

#endif

//對於衝突鏈A->B->C.

//pE是二級指標,存放的是A元素的hte_next指標的地址值

//e指向B元素。


//對新的素數進行雜湊,剛好又在原來的位置

if (b2 == b)

{

//此時,無需修改。接著處理衝突鏈中的下一個元素即可

//pE向前移動,存放B元素的hte_next指標的地址值

pE = &e->map_node.hte_next;

}

else//這個元素會去到其他位置上。

{

//此時衝突鏈修改成A->C。

//所以pE無需修改,還是存放A元素的hte_next指標的地址值

//但A元素的hte_next指標要指向C元素。用*pE去修改即可

*pE = e->map_node.hte_next;


//將這個元素放到正確的位置上。

e->map_node.hte_next = new_table[b2];

new_table[b2] = e;

}


//這種再次雜湊的方式,很有可能會對某些元素操作兩次。

//當某個元素第一次在else中處理,那麼它就會被雜湊到正確的節點

//的衝突鏈上。隨著外迴圈的進行,處理到正確的節點時。在遍歷該節點

//的衝突鏈時,又會再次處理該元素。此時,就會在if中處理。而不會

//進入到else中。

}

}


head->hth_table = new_table;

}



//一般是當hth_n_entries >= hth_load_limit時,就會呼叫

//本函式。hth_n_entries表示的是雜湊表中節點的個數。而hth_load_limit

//是hth_table_length的一半。hth_table_length則是雜湊表中

//雜湊函式被模的數字。所以,當雜湊表中的節點個數到達雜湊表長度的一半時

//就會發生增長,本函式被呼叫。這樣的結果是:平均來說,雜湊表應該比較少發生

//衝突。即使有,衝突鏈也不會太長。這樣就能有比較快的查詢速度。

head->hth_table_length = new_len;

head->hth_prime_idx = prime_idx;

head->hth_load_limit = new_load_limit;


return 0;

}



/* Free all storage held by 'head'. Does not free 'head' itself,

* or individual elements. 並不需要釋放獨立的元素*/

//在evmap_io_clear函式會呼叫該函式。其是在刪除所有雜湊表中的元素後

//才呼叫該函式的。

void event_io_map_HT_CLEAR(struct event_io_map *head)

{

if (head->hth_table)

mm_free(head->hth_table);


head->hth_table_length = 0;


event_io_map_HT_INIT(head);

}



/* Debugging helper: return false iff the representation of 'head' is

* internally consistent. */

int _event_io_map_HT_REP_IS_BAD(const struct event_io_map *head)

{

unsigned n, i;

struct event_map_entry *elm;


if (!head->hth_table_length)

{

//剛被初始化,還沒申請任何空間

if (!head->hth_table && !head->hth_n_entries

&& !head->hth_load_limit && head->hth_prime_idx == -1

)

return 0;

else

return 1;

}


if (!head->hth_table || head->hth_prime_idx < 0

|| !head->hth_load_limit

)

return 2;


if (head->hth_n_entries > head->hth_load_limit)

return 3;


if (head->hth_table_length != event_io_map_PRIMES[head->hth_prime_idx])

return 4;


if (head->hth_load_limit != (unsigned)(0.5*head->hth_table_length))

return 5;


for (n = i = 0; i < head->hth_table_length; ++i)

{

for (elm = head->hth_table[i]; elm; elm = elm->map_node.hte_next)

{


#ifdef HT_CACHE_HASH_VALUES


if (elm->map_node.hte_hash != hashsocket(elm))

return 1000 + i;


if( (elm->map_node.hte_hash % head->hth_table_length) != i)

return 10000 + i;


#else

if ( (hashsocket(*elm)) != hashsocket(elm))

return 1000 + i;


if( ( (hashsocket(*elm)) % head->hth_table_length) != i)

return 10000 + i;

#endif

++n;

}

}


if (n != head->hth_n_entries)

return 6;


return 0;

}

        程式碼中的註釋已經對這個雜湊表的一些特徵進行了描述,這裡就不多說了。

雜湊表在Libevent的使用:

        現在來講一下event_io_map的應用。

        在event_base這個結構體中有一個event_io_map型別的成員變數io。它就是一個雜湊表。當一個監聽讀或者寫操作的event,呼叫event_add函式插入到event_base中時,就會呼叫evmap_io_add函式。evmap_io_add函式應用到這個event_io_map結構體。該函式的定義如下,其中使用到了一個巨集定義,我已經展開了。

int

evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev)

{

const struct eventop *evsel = base->evsel;

struct event_io_map *io = &base->io;

struct evmap_io *ctx = NULL;

int nread, nwrite, retval = 0;

short res = 0, old = 0;

struct event *old_ev;


EVUTIL_ASSERT(fd == ev->ev_fd);


if (fd < 0)

return 0;


//GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,

// evsel->fdinfo_len);SLOT指的是fd

//GET_IO_SLOT_AND_CTOR巨集將展開成下面這個do{}while(0);

do

{

struct event_map_entry _key, *_ent;

_key.fd = fd;


struct event_io_map *_ptr_head = io;

struct event_map_entry **ptr;


//雜湊表擴容,減少衝突的可能性

if (!_ptr_head->hth_table

|| _ptr_head->hth_n_entries >= _ptr_head->hth_load_limit)

{

event_io_map_HT_GROW(_ptr_head,

_ptr_head->hth_n_entries + 1);

}


#ifdef HT_CACHE_HASH_VALUES

do{

(&_key)->map_node.hte_hash = hashsocket((&_key));

} while(0);

#endif


//返回值ptr,是要查詢節點的前驅節點的hte_next成員變數的地址.

//所以返回值肯定不會為NULL,而*ptr就可能為NULL。說明hte_next

//不指向任何節點。也正由於這個原因,所以即使*ptr 為NULL,但是可以

//給*ptr賦值。此時,是修改前驅節點的hte_next成員變數的值,使之

//指向另外一個節點。

//這裡呼叫_event_io_map_HT_FIND_P原因有二:1.檢視該fd是否已經

//插入過這個雜湊表中。2.得到這個fd計算雜湊位置。

ptr = _event_io_map_HT_FIND_P(_ptr_head, (&_key));


//在event_io_map這個雜湊表中查詢是否已經存在該fd的event_map_entry了

//因為同一個fd可以呼叫event_new多次,然後event_add多次的。

if (*ptr)

{

_ent = *ptr;

}

else

{

_ent = mm_calloc(1, sizeof(struct event_map_entry) + evsel->fdinfo_len);

if (EVUTIL_UNLIKELY(_ent == NULL))

return (-1);


_ent->fd = fd;

//呼叫初始化函式初始化這個evmap_io

(evmap_io_init)(&_ent->ent.evmap_io);


#ifdef HT_CACHE_HASH_VALUES

do

{

ent->map_node.hte_hash = (&_key)->map_node.hte_hash;

}while(0);

#endif

_ent->map_node.hte_next = NULL;


//把這個新建的節點插入到雜湊表中。ptr已經包含了雜湊位置

*ptr = _ent;

++(io->hth_n_entries);

}



//這裡是獲取該event_map_entry的next和prev指標。因為

//evmap_io含有next、prev變數。這樣在之後就可以把這個

//event_map_entry連起來。這個外do{}while(0)的功能是

//為這個fd分配一個event_map_entry,並且插入到現有的雜湊

//表中。同時,這個fd還是結構體event的一部分。而event必須

//插入到event佇列中。

(ctx) = &_ent->ent.evmap_io;


} while (0);



....


//ctx->events是一個TAILQ_HEAD。結合之前講到的TAILQ_QUEUE佇列,

//就可以知道:同一個fd,可能有多個event結構體。這裡就把這些結構體連

//起來。依靠的連結串列是,event結構體中的ev_io_next。ev_io_next是

//一個TAILQ_ENTRY,具有前驅和後驅指標。佇列頭部為event_map_entry

//結構體中的evmap_io成員的events成員。

TAILQ_INSERT_TAIL(&ctx->events, ev, ev_io_next);


return (retval);

}

        GET_IO_SLOT_AND_CTOR巨集的作用就是讓ctx指向struct event_map_entry結構體中的TAILQ_HEAD。這樣就可以使用TAILQ_INSERT_TAIL巨集,把ev變數插入到佇列中。如果有現成的event_map_entry就直接使用,沒有的話就新建一個。