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

时间: 2024-11-12 04:45:44

nginx源码解析之数据结构篇的相关文章

nginx源码解析之内存池

nginx自身实现了内存池,所有内存分配都是基于内存池来操作.基本思想是预申请一段内存空间,低于指定大小的内存(小段内存)直接从内存池中申请,超过指定大小的内存(大段内存)直接调用malloc申请.相关代码在os/unix/ngx_alloc.{c,h}和core/ngx_palloc.{c,h}. os/unix/ngx_alloc.{c,h}文件封装了内存分配的系统调用,其中: ngx_alloc调用malloc申请一段内存空间 ngx_calloc调用malloc申请一段内存空间,再调用m

[nginx源码解析]配置解析(main作用域)

下面我们就以一个简单配置(nginx默认配置文件)来进行分析整个配置解析过程,同时会附图 配置文件如下(nginx默认生成配置文件) worker_processes 1; daemon off; events { worker_connections 1024 ; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { list

[nginx源码解析]配置解析(event作用域)

处理events 其中符合NGX_EVENT_MODULE有两个模块分别是ngx_event_core_module.ngx_epoll_module 核心代码 ngx_modules[i]->ctx_index = ngx_event_max_module++;//设置模块内部索引 } ctx = ngx_pcalloc(cf->pool, sizeof(void *)); if (ctx == NULL) { return NGX_CONF_ERROR; } *ctx = ngx_pcal

ceph RGW接口源码解析--Rados数据操作

RGW业务处理流程: http reqest --> apache 转 FastCgi module FastCgi module --> radosgw  通过socket请求实现(未确定是否有其它方式) radosgw --> ceph集群  通过socket实现,调用rados接口 1.数据结构 在Rgw_common.h定义了基本的数据结构,并实现了 decode.encode序列化,方便对rados访问 //桶的权限创建资料 struct RGWBucketInfo  {  r

菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 31h, 2014 1.哈希表ngx_hash_t的优势和特点 哈希表是一种典型的以空间换取时间的数据结构,在没有冲突的情况下,对任意元素的插入.索引.删除的时间复杂度都是O(1).这样优秀的时间复杂度是通过将元素的key值以hash方法f映射到哈希表中的某一个位置来访问记录来实现的,即键值为key的元素必定存储在哈希

菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t[转]

菜鸟nginx源码剖析数据结构篇(九) 内存池ngx_pool_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 11th, 2014 今天是一年一度的光棍节,还没有女朋友的程序猿童鞋不妨new一个出来,内存管理一直是C/C++中最棘手的部分,远不止new/delete.malloc/free这么简单.随着代码量的递增,程序结构复杂度的提高.今天我们就一起研究一下以精巧著

菜鸟nginx源码剖析数据结构篇(十一) 共享内存ngx_shm_t[转]

菜鸟nginx源码剖析数据结构篇(十一) 共享内存ngx_shm_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 14th, 2014 1.共享内存 共享内存是Linux下提供的最基本的进程通信方法,它通过mmap或者shmget系统调用在内存中创建了一块连续的线性地址空间,而通过munmap或者shmdt系统调用释放这块内存,使用共享内存的好处是多个进程使用同一块内存

菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表ngx_chain_t[转]

菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 6th, 2014 1.缓冲区链表结构ngx_chain_t和ngx_buf_t nginx的缓冲区链表如下图所示,ngx_chain_t为链表,ngx_buf_t为缓冲区结点: 2.源代码位置 头文件:http://trac.nginx.org/ng

菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表ngx_chain_t

菜鸟nginx源码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 6th, 2014 1.缓冲区链表结构ngx_chain_t和ngx_buf_t nginx的缓冲区链表如下图所示,ngx_chain_t为链表,ngx_buf_t为缓冲区结点: 2.源代码位置 头文件:http://trac.nginx.org/ng