nginx之ngx_hash
nginx使用了开放地址法解决哈希冲突,还有利用内存对齐使用的一些小技巧等等。
下面是关于ngx_hash.h中的一些定义:
/**
*先看nginx里面对hash每个元素的定义:
*/
typedef struct {
void *value;
u_short len;
u_char name[1];
} ngx_hash_elt_t;
这里使用了一个技巧,使用了零长数组,当申请的内存被value和len占据相应的size后,剩余的内存对于name来说可以使用。
/**
*nginx对ngx_hash_t的定义
*/
typedef struct {
ngx_hash_elt_t **buckets;
ngx_uint_t size;
} ngx_hash_t;
/**
*nginx对ngx_wildcard_t的定义
*/
typedef struct {
ngx_hash_t hash;
void *value;
} ngx_hash_wildcard_t;
/**
*nginx对ngx_hash_key_t的定义
*/
typedef struct {
ngx_str_t key;
ngx_uint_t key_hash;
void *value;
} ngx_hash_key_t;
/**
*nginx对ngx_hash_combined_t的定义
*/
typedef struct {
ngx_hash_t hash;
ngx_hash_wildcard_t *wc_head;
ngx_hash_wildcard_t *wc_tail;
} ngx_hash_combined_t;
/**
*nginx对ngx_hash_init_t的定义
*/
typedef struct {
ngx_hash_t *hash;
ngx_hash_key_pt key;
ngx_uint_t max_size;
ngx_uint_t bucket_size;
char *name;
ngx_pool_t *pool;
ngx_pool_t *temp_pool;
} ngx_hash_init_t;
/**
*nginx对ngx_hash_key_array_t的定义
*/
typedef struct {
ngx_uint_t hsize;
ngx_pool_t *pool;
ngx_pool_t *temp_pool;
ngx_array_t keys;
ngx_array_t *keys_hash;
ngx_array_t dns_wc_head;
ngx_array_t *dns_wc_head_hash;
ngx_array_t dns_wc_tail;
ngx_array_t *dns_wc_tail_hash;
} ngx_hash_keys_arrays_t;
/**
*nginx对ngx_hash_table_elt_t的定义
*/
typedef struct {
ngx_uint_t hash;
ngx_str_t key;
ngx_str_t value;
u_char *lowcase_key;
} ngx_table_elt_t;
/**
*一些nginx自定义的字段
*/
#define NGX_HASH_SMALL 1
#define NGX_HASH_LARGE 2
#define NGX_HASH_LARGE_ASIZE 16384
#define NGX_HASH_LARGE_HSIZE 10007
#define NGX_HASH_WILDCARD_KEY 1
#define NGX_HASH_READONLY_KEY 2
ngx_hash.c:
ngx_int_t ngx_hash_init()解析:
/**
*对齐只需保证末尾的bit位为0即可
*如8字节对齐保证末尾3个bit位为0,4字节对齐保证末尾2个bit为0
*不同平台下的void*大小不一样,32位长为4个字节,64位长为8个字节
*/
#define NGX_HASH_ELT_SIZE(name) \
(sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
//这是在ngx_config.h里面关于ngx_align()的定义
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
NGX_HASH_ELT_SIZE(name)是nginx在使用内存对齐的一个自定义的宏,可以利用其计算出每个hash桶里面每个elt在内存对齐后所占用的内存大小。
nginx采用的是预分配的方式来决定需要多少个bucket,通过计算算出至少需要start个桶,再通过计算每个names[n].key的hash值,并计算bucket_size是否能够容纳所有的elt,如果可以就确定了size值,如果无法容纳就增大start值。
bucket_size = hinit->bucket_size - sizeof(void *);
start = nelts / (bucket_size / (2 * sizeof(void *)));
start = start ? start : 1;
nginx按照cpu的缓存大小进行内存对齐的,来提高程序运行效率。
elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
if (elts == NULL) {
ngx_free(test);
return NGX_ERROR;
}
elts = ngx_align_ptr(elts, ngx_cacheline_size);
将names中的各个元素添加进hash桶里,而且最后nginx在hash桶里面设置哨兵节点,由value=NULL确定,且该哨兵节点elt只分配sizeof(void*),由于在访问时只访问value,因此并不会指针造成越界。
ngx_hash_wildcard_init()解析:
nginx在处理通配符的时候运用了一些处理,即在利用了末尾两个bit位进行了数据的标识。
/**
*利用末尾两个00位,由于内存对齐的缘故,末尾两个bit位必定为00
*01表示value指向的是确定的数据,10和11指向下个哈希表
*/
name->value = (void *) ((uintptr_t) wdc | (dot ? 3 : 2));
name->value = (void *) ((uintptr_t) name->value | 1);
最后用ngx_hash_init将curr_names.elt装配成通用的哈希表。
ngx_uint_t ngx_hash_key_lc(): 一般由ngx_hash_init_t里面的函数指针ngx_hash_key_pt指向该函数。
ngx_int_t ngx_hash_add_key()解析:
if (flags & NGX_HASH_WILDCARD_KEY){
...
//如果有多个*或者两个.连续,直接返回错误
if (key->data[i] == '*'){...}
if (key->data[i] == '.' && key->data[i + 1] == '.'){...}
//.example.com的情况
if (key->len > 1 && key->data[0] == '.'){...}
//*.example.com的情况
if (key->data[0] == '*' && key->data[1] == '.'){...}
//www.example.*的情况
if (key->data[i - 2] == '.' && key->data[i - 1] == '*'){...}
...
}
wildcard是处理通配符放入相应的动态数组里面
例如:
原始字段: .example.com\0
dns_wc_head: com.example\0
dns_wc_head_hash: example.com\0
原始字段: *.example.com\0
dns_wc_head: com.example.\0
dns_wc_head_hash: example.com\0
原始字段: www.example.*\0
dns_wc_tail: www.example\0
dns_wc_tail_hash: www.example.\0