在上一篇分析nginx内存池的基础上,回过头来看看nginx中一些常见的数据结构,如字符串、数组、队列、链表、hash表等。
字符串
nginx字符串的实现代码在core/ngx_string.{h,c},里面除了字符串定义之外,还包括很多辅助函数,有字符串拷贝、格式化、转换及编码等。以下是字符串结构的定义,实际上就是在字符指针的基础上加了个长度字段,意味着可以用来表示二进制的数据。
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
// 定义静态字符串
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
// 定义空字符串
#define ngx_null_string { 0, NULL }
数组
相关代码在core/ngx_array.{h,c}中,数组本质上来说是一段连续的内存空间,只不过分成了相同长度的若干份。如下为nginx数组的定义:
typedef struct {
void *elts; // 数组内存空间起始地址
ngx_uint_t nelts; // 已使用数组元素个数
size_t size; // 数组单个元素占用空间
ngx_uint_t nalloc; // 申请的数组元素个数
ngx_pool_t *pool; // 所在内存池
} ngx_array_t;
代码中还定义了数组的相关操作:
ngx_array_create
函数用来创建(n*size)数组
ngx_array_t *
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
{
ngx_array_t *a;
// 申请ngx_array_t结构体
a = ngx_palloc(p, sizeof(ngx_array_t));
if (a == NULL) {
return NULL;
}
// expand from ngx_array_init
// 初始化ngx_array_t结构体
a->nelts = 0;
a->size = size;
a->nalloc = n;
a->pool = pool;
a->elts = ngx_palloc(pool, n * size);
if (array->elts == NULL) {
return NULL;
}
return a;
}
ngx_array_destroy
用来“销毁”数组空间,实际上只是会归还给内存池
void
ngx_array_destroy(ngx_array_t *a)
{
ngx_pool_t *p;
p = a->pool;
// 如果数组占用空间末端地址正好是last指向地址,将last指针指向数组空间起始地址
if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
p->d.last -= a->size * a->nalloc;
}
// 如果数组结构体占用空间末端地址正好是last指向地址,将last指针指向结构体地址
if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
p->d.last = (u_char *) a;
}
}
ngx_array_push
函数用来返回一个数组元素(未被使用)的地址ngx_array_push_n
函数用来返回n个连续数组元素(未被使用)的地址
队列
相关代码在core/ngx_queue.{h,c}中。
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
#define ngx_queue_init(q) \
(q)->prev = q; (q)->next = q
#define ngx_queue_empty(h) \
(h == (h)->prev)
#define ngx_queue_insert_head(h, x) (x)->next = (h)->next; (x)->next->prev = x; (x)->prev = h; (h)->next = x
#define ngx_queue_insert_tail(h, x) \
(x)->prev = (h)->prev; (x)->prev->next = x; (x)->next = h; (h)->prev = x
#define ngx_queue_head(h) \
(h)->next
#define ngx_queue_last(h) \
(h)->prev
#define ngx_queue_sentinel(h) \
(h)
#define ngx_queue_next(q) \
(q)->next
#define ngx_queue_prev(q) \
(q)->prev
#define ngx_queue_remove(x) \
(x)->next->prev = (x)->prev; (x)->prev->next = (x)->next
#define ngx_queue_split(h, q, n) \
(n)->prev = (h)->prev; (n)->prev->next = n; (n)->next = q; (h)->prev = (q)->prev; (h)->prev->next = h; (q)->prev = n;
#define ngx_queue_add(h, n) \
(h)->prev->next = (n)->next; (n)->next->prev = (h)->prev; (h)->prev = (n)->prev; (h)->prev->next = h;
#define ngx_queue_data(q, type, link) \
(type *) ((u_char *) q - offsetof(type, link))
链表
相关代码保存在core/ngx_list.{h|c}中,下面是链表的结构体
typedef struct ngx_list_part_s ngx_list_part_t;
struct ngx_list_part_s {
void *elts; // 链表数据空间起始地址
ngx_uint_t nelts; // 已使用空间元素个数
ngx_list_part_t *next; // 链表下一个节点
};
typedef struct {
ngx_list_part_t *last; // 最后一个链表节点
ngx_list_part_t part; // 链表内容
size_t size; // 链表节点空间元素大小
ngx_uint_t nalloc; // 链表节点申请元素个数
ngx_pool_t *pool;
} ngx_list_t;
- 创建链表
// 初始化链表
static ngx_inline ngx_int_t
ngx_list_init(ngx_list_t *list, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
// 为第一个节点申请内存空间,大小为n*size
list->part.elts = ngx_palloc(pool, n * size);
if (list->part.elts == NULL) {
return NGX_ERROR;
}
list->part.nelts = 0;
list->part.next = NULL;
list->last = &list->part;
list->size = size;
list->nalloc = n;
list->pool = pool;
return NGX_OK;
}
ngx_list_t *
ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
ngx_list_t *list;
// 申请ngx_list_t结构体
list = ngx_palloc(pool, sizeof(ngx_list_t));
if (list == NULL) {
return NULL;
}
// 初始化链表
if (ngx_list_init(list, pool, n, size) != NGX_OK) {
return NULL;
}
return list;
}
- 向链表最后一个节点申请一个元素空间
void *
ngx_list_push(ngx_list_t *l)
{
void *elt;
ngx_list_part_t *last;
last = l->last;
if (last->nelts == l->nalloc) {
/* the last part is full, allocate a new list part */
last = ngx_palloc(l->pool, sizeof(ngx_list_part_t));
if (last == NULL) {
return NULL;
}
last->elts = ngx_palloc(l->pool, l->nalloc * l->size);
if (last->elts == NULL) {
return NULL;
}
last->nelts = 0;
last->next = NULL;
l->last->next = last;
l->last = last;
}
elt = (char *) last->elts + l->size * last->nelts;
last->nelts++;
return elt;
}
hash表
相关代码在core/ngx_hash.{h,c}中。先熟悉下几个重要的结构体:
// hash元素
typedef struct {
void *value; // K-V中的V
u_short len; // name长度
u_char name[1]; // K-V中的K
} ngx_hash_elt_t;
// hash表结构
typedef struct {
ngx_hash_elt_t **buckets; // hash桶
ngx_uint_t size; // hash桶个数
} ngx_hash_t;
// hash键值对
typedef struct {
ngx_str_t key; // hash键
ngx_uint_t key_hash; // hash键的hash值
void *value; // hash值
} ngx_hash_key_t;
typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len);
// hash初始化结构
typedef struct {
ngx_hash_t *hash; // hash表
ngx_hash_key_pt key; // hash函数
ngx_uint_t max_size; // bucket最大个数
ngx_uint_t bucket_size; // 每个bucket的空间
char *name; // hash表的名称
ngx_pool_t *pool; // 内存池
ngx_pool_t *temp_pool;
} ngx_hash_init_t;
hash表的实现主要有三点:hash函数、hash结构初始化以及hash表查询,下面分别从这三点出发。nginx中hash函数有ngx_hash_key
和ngx_hash_key_lc
,两者唯一的区别就是后者将源字符串中的大写英文字母转换成了小写再计算。ngx_hash_key
的计算方法比较简单理解,即字符串每个字节*31的累加值,计算出来的hash值用ngx_uint_t
来表示。
ngx_uint_t
ngx_hash_key(u_char *data, size_t len)
{
ngx_uint_t i, key;
key = 0;
for (i = 0; i < len; i++) {
key = ngx_hash(key, data[i]);
}
return key;
}
#define ngx_hash(key, c) ((ngx_uint_t) key * 31 + c)
hash结构的初始化是在ngx_hash_init
中完成的,传入参数分别为待初始化的hash结构hinit、hash键值对names和键值对个数nelts。
// 计算hash键值对保存为bucket的大小,即ngx_hash_elt_t
#define NGX_HASH_ELT_SIZE(name) \
(sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
ngx_int_t
ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)
{
u_char *elts;
size_t len;
u_short *test;
ngx_uint_t i, n, key, size, start, bucket_size;
ngx_hash_elt_t *elt, **buckets;
for (n = 0; n < nelts; n++) {
// 对每个键值对,判断bucket大小是否能够容纳该键值对占用bucket空间
if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))
{
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
"could not build the %s, you should "
"increase %s_bucket_size: %i",
hinit->name, hinit->name, hinit->bucket_size);
return NGX_ERROR;
}
}
// 申请max_size*sizeof(u_short)大小空间,即2*max_size,用来临时保存bucket大小
test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);
if (test == NULL) {
return NGX_ERROR;
}
// bucket要预留一个sizeof(void*)的空间
bucket_size = hinit->bucket_size - sizeof(void *);
// 计算start值,即起始bucket个数,暂时没想明白为什么要这样算??
start = nelts / (bucket_size / (2 * sizeof(void *)));
start = start ? start : 1;
if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {
start = hinit->max_size - 1000;
}
// 寻找一个合适的bucket个数
for (size = start; size <= hinit->max_size; size++) {
ngx_memzero(test, size * sizeof(u_short));
for (n = 0; n < nelts; n++) {
if (names[n].key.data == NULL) {
continue;
}
// hash%size表示哈希值所在的bucket index
key = names[n].key_hash % size;
// 累加计算桶占用的内存空间
test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
#if 0
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: %ui %ui \"%V\"",
size, key, test[key], &names[n].key);
#endif
// 如果桶大小超过了bucket_size,说明桶个数不合适
if (test[key] > (u_short) bucket_size) {
goto next;
}
}
goto found;
next:
continue;
}
ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0,
"could not build optimal %s, you should increase "
"either %s_max_size: %i or %s_bucket_size: %i; "
"ignoring %s_bucket_size",
hinit->name, hinit->name, hinit->max_size,
hinit->name, hinit->bucket_size, hinit->name);
found:
// 重置桶大小为sizeof(void*)
for (i = 0; i < size; i++) {
test[i] = sizeof(void *);
}
// 重新计算每个桶占用大小
for (n = 0; n < nelts; n++) {
if (names[n].key.data == NULL) {
continue;
}
key = names[n].key_hash % size;
test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
}
len = 0;
// 计算所有桶所占用空间大小
for (i = 0; i < size; i++) {
if (test[i] == sizeof(void *)) {
continue;
}
// 每个桶长度对齐
test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));
len += test[i];
}
// 申请buckets空间(size*sizeof(ngx_hash_elt_t *),即桶大小个指针空间)
// 如果hash为空则多申请一个ngx_hash_wildcard_t结构
if (hinit->hash == NULL) {
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
+ size * sizeof(ngx_hash_elt_t *));
if (hinit->hash == NULL) {
ngx_free(test);
return NGX_ERROR;
}
buckets = (ngx_hash_elt_t **)
((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));
} else {
buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));
if (buckets == NULL) {
ngx_free(test);
return NGX_ERROR;
}
}
// 申请elts空间(用来存放实际bucket数据)
elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);
if (elts == NULL) {
ngx_free(test);
return NGX_ERROR;
}
// 对齐elts空间
elts = ngx_align_ptr(elts, ngx_cacheline_size);
// 初始化buckets表,test[i]表示bucket大小
for (i = 0; i < size; i++) {
if (test[i] == sizeof(void *)) {
continue;
}
buckets[i] = (ngx_hash_elt_t *) elts;
elts += test[i];
}
// 重置各个bucket大小
for (i = 0; i < size; i++) {
test[i] = 0;
}
// 将键值对存放到对应的bucket中
for (n = 0; n < nelts; n++) {
if (names[n].key.data == NULL) {
continue;
}
key = names[n].key_hash % size;
elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);
elt->value = names[n].value;
elt->len = (u_short) names[n].key.len;
ngx_strlow(elt->name, names[n].key.data, names[n].key.len);
test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));
}
// end with NULL
for (i = 0; i < size; i++) {
if (buckets[i] == NULL) {
continue;
}
elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);
elt->value = NULL;
}
ngx_free(test);
hinit->hash->buckets = buckets;
hinit->hash->size = size;
#if 0
for (i = 0; i < size; i++) {
ngx_str_t val;
ngx_uint_t key;
elt = buckets[i];
if (elt == NULL) {
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: NULL", i);
continue;
}
while (elt->value) {
val.len = elt->len;
val.data = &elt->name[0];
key = hinit->key(val.data, val.len);
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: %p \"%V\" %ui", i, elt, &val, key);
elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
}
}
#endif
return NGX_OK;
}
hash表的查找通过ngx_hash_find
函数完成,基本思路是先找到对应的bucket,然后在bucket中进行线性查找。
void *
ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
ngx_uint_t i;
ngx_hash_elt_t *elt;
#if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
#endif
// find bucket
elt = hash->buckets[key % hash->size];
if (elt == NULL) {
return NULL;
}
// find corresponding ngx_hash_elt_t
while (elt->value) {
if (len != (size_t) elt->len) {
goto next;
}
for (i = 0; i < len; i++) {
if (name[i] != elt->name[i]) {
goto next;
}
}
return elt->value;
next:
elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
continue;
}
return NULL;
}
至于Radix树、红黑树等,之后可以拿单独的一篇博客来做分析。
时间: 2024-11-12 04:45:44