nginx源码解析之数据结构篇

在上一篇分析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_keyngx_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树、红黑树等,之后可以拿单独的一篇博客来做分析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值