Nginx源码分析---hash结构ngx_hash_t(v1.0.4)

来源:互联网 发布:php 防止xss和sql注入 编辑:程序博客网 时间:2024/04/27 18:06

0. 

本文继续介绍nginx的数据结构——hash结构。

 

链表实现文件:文件:./src/core/ngx_hash.h/.c.表示nginx-1.0.4代码目录,本文为/usr/src/nginx-1.0.4

 

1. hash结构

 

nginxhash结构比其listarrayqueue等结构稍微复杂一些,下图是hash相关数据结构图。下面一一介绍。

 

1.1 ngx_hash_t结构

 

nginxhash结构为ngx_hash_thash元素结构为ngx_hash_elt_t,定义如下。

  1. typedef struct {               //hash元素结构   
  2.     void             *value;   //value,即某个key对应的值,即<key,value>中的value   
  3.     u_short           len;     //name长度   
  4.     u_char            name[1]; //某个要hash的数据(在nginx中表现为字符串),即<key,value>中的key   
  5. } ngx_hash_elt_t;  
  6.   
  7. typedef struct {               //hash结构   
  8.     ngx_hash_elt_t  **buckets; //hash桶(有size个桶)   
  9.     ngx_uint_t        size;    //hash桶个数   
  10.   
  11. } ngx_hash_t;  

其中,sizeof(ngx_hash_t) = 8sizeof(ngx_hash_elt_t) = 8。实际上,ngx_hash_elt_t结构中的name字段就是ngx_hash_key_t结构中的key。这在ngx_hash_init()函数中可以看到,请参考后续的分析。该结构在模块配置解析时经常使用。

 

1.2 ngx_hash_init_t结构

 

nginxhash初始化结构是ngx_hash_init_t,用来将其相关数据封装起来作为参数传递给ngx_hash_init()ngx_hash_wildcard_init()函数。这两个函数主要是在http相关模块中使用,例如ngx_http_server_names()函数(优化http Server Names)ngx_http_merge_types()函数(合并httptype)ngx_http_fastcgi_merge_loc_conf()函数(合并FastCGI Location Configuration)等函数或过程用到的参数、局部对象/变量等。这些内容将在后续的文章中讲述。

 

ngx_hash_init_t结构如下。sizeof(ngx_hash_init_t)=28

  1. typedef struct {                    //hash初始化结构   
  2.     ngx_hash_t       *hash;         //指向待初始化的hash结构   
  3.     ngx_hash_key_pt   key;          //hash函数指针   
  4.   
  5.     ngx_uint_t        max_size;     //bucket的最大个数   
  6.     ngx_uint_t        bucket_size;  //每个bucket的空间   
  7.   
  8.     char             *name;         //该hash结构的名字(仅在错误日志中使用)   
  9.     ngx_pool_t       *pool;         //该hash结构从pool指向的内存池中分配   
  10.     ngx_pool_t       *temp_pool;    //分配临时数据空间的内存池   
  11. } ngx_hash_init_t;  

1.3 ngx_hash_key_t结构

 

该结构也主要用来保存要hash的数据,即键-值对<key,value>,在实际使用中,一般将多个键-值对保存在ngx_hash_key_t结构的数组中,作为参数传给ngx_hash_init()ngx_hash_wildcard_init()函数。其定义如下。

  1. typedef struct {                    //hash key结构   
  2.     ngx_str_t         key;          //key,为nginx的字符串结构   
  3.     ngx_uint_t        key_hash;     //由该key计算出的hash值(通过hash函数如ngx_hash_key_lc())   
  4.     void             *value;        //该key对应的值,组成一个键-值对<key,value>   
  5. } ngx_hash_key_t;  
  6.   
  7. typedef struct {                    //字符串结构   
  8.     size_t      len;                //字符串长度   
  9.     u_char     *data;               //字符串内容   
  10. } ngx_str_t;  

其中,sizeof(ngx_hash_key_t) = 16。一般在使用中,value指针可能指向静态数据区(例如全局数组、常量字符串)、堆区(例如动态分配的数据区用来保存value)等。可参考本文后面的例子。

 

关于ngx_table_elt_t结构和ngx_hash_keys_arrays_t结构,因其对于hash结构本身没有太大作用,主要是为模块配置、referer合法性验证等设计的数据结构,例如httpcore模块、map模块、referer模块、SSI filter模块等,此处不再讲述,将在后续的文章中介绍。

1.4 hash的逻辑结构

ngx_hash_init_t结构引用了ngx_pool_t结构,因此本文参考nginx-1.0.4源码分析—内存池结构ngx_pool_t及内存管理一文画出相关结构的逻辑图,如下。注:本文采用UML的方式画出该图。



2. hash操作

 

2.1 NGX_HASH_ELT_SIZE

 

NGX_HASH_ELT_SIZE宏用来计算上述ngx_hash_elt_t结构大小,定义如下。

  1. #define NGX_HASH_ELT_SIZE(name)         \      //该参数name即为ngx_hash_elt_t结构指针   
  2.     (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *))) //以4字节对齐  

32位平台上,sizeof(void*)=4(name)->key.len即是ngx_hash_elt_t结构中name数组保存的内容的长度,其中的"+2"是要加上该结构中len字段(u_short类型)的大小。

 

因此,NGX_HASH_ELT_SIZE(name)=4+ngx_align((name)->key.len + 2, 4),该式后半部分即是(name)->key.len+24字节对齐的大小。

 

2.2 hash函数

 

nginx-1.0.4提供的hash函数有以下几种。

  1. #define ngx_hash(key, c)   ((ngx_uint_t) key * 31 + c)  //hash宏   
  2. ngx_uint_t ngx_hash_key(u_char *data, size_t len);  
  3. ngx_uint_t ngx_hash_key_lc(u_char *data, size_t len);   //lc表示lower case,即字符串转换为小写后再计算hash值   
  4. ngx_uint_t ngx_hash_strlow(u_char *dst, u_char *src, size_t n);  

hash函数都很简单,以上3个函数都会调用ngx_hash宏,该宏返回一个()整数。此处介绍第一个函数,定义如下。

  1. ngx_uint_t  
  2. ngx_hash_key(u_char *data, size_t len)  
  3. {  
  4.     ngx_uint_t  i, key;  
  5.   
  6.     key = 0;  
  7.   
  8.     for (i = 0; i < len; i++) {  
  9.         key = ngx_hash(key, data[i]);  
  10.     }  
  11.   
  12.     return key;  
  13. }  

因此,ngx_hash_key函数的计算可表述为下列公式。

  1. Key[0] = data[0]  
  2. Key[1] = data[0]*31 + data[1]  
  3. Key[2] = (data[0]*31 + data[1])*31 + data[2]  
  4. ...  
  5. Key[len-1] = ((((data[0]*31 + data[1])*31 + data[2])*31) ... data[len-2])*31 + data[len-1]  

key[len-1]即为传入的参数data对应的hash值。

 

2.3 hash初始化

 

hash初始化由ngx_hash_init()函数完成,其names参数是ngx_hash_key_t结构的数组,即键-值对<key,value>数组,nelts表示该数组元素的个数。因此,在调用该函数进行初始化之前,ngx_hash_key_t结构的数组是准备好的,如何使用,可以采用nginxngx_array_t结构,详见本文后面的例子。

 

该函数初始化的结果就是将names数组保存的键-值对<key,value>,通过hash的方式将其存入相应的一个或多个hash(即代码中的buckets)中,该hash过程用到的hash函数一般为ngx_hash_key_lc等。hash桶里面存放的是ngx_hash_elt_t结构的指针(hash元素指针),该指针指向一个基本连续的数据区。该数据区中存放的是经hash之后的键-值对<key',value'>,即ngx_hash_elt_t结构中的字段<name,value>。每一个这样的数据区存放的键-值对<key',value'>可以是一个或多个。

 

此处有几个问题需要说明。

 

问题1:为什么说是基本连续?

——用NGX_HASH_ELT_SIZE宏计算某个hash元素的总长度时,存在以sizeof(void*)对齐的填补(padding)。因此将names数组中的键-值对<key,value>中的key拷贝到ngx_hash_elt_t结构的name[1]数组中时,已经为该hash元素分配的空间不会完全被用完,故这个数据区是基本连续的。这一点也可以参考本节后面的结构图或本文后面的例子。

 

问题2:这些基本连续的数据区从哪里分配的?

——当然是从该函数的第一个参数ngx_hash_init_tpool字段指向的内存池中分配的。

 

问题3<key',value'><key,value>不同的是什么?

——key保存的仅仅是个指针,而key'却是key拷贝到name[1]的结果。而valuevalue'都是指针。如1.3节说明,value指针可能指向静态数据区(例如全局数组、常量字符串)、堆区(例如动态分配的数据区用来保存value)等。可参考本文后面的例子。

 

问题4:如何知道某个键-值对<key,value>放在哪个hash桶中?

——key = names[n].key_hash % size; 代码中的这个计算是也。计算结果key即是该键要放在那个hash桶的编号(0size-1)

 

该函数代码如下。一些疑点、难点的解释请参考//后笔者所加的注释,也可参考本节的hash结构图。

  1. //nelts是names数组中(实际)元素的个数   
  2. ngx_int_t  
  3. ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)  
  4. {  
  5.     u_char          *elts;  
  6.     size_t           len;  
  7.     u_short         *test;  
  8.     ngx_uint_t       i, n, key, size, start, bucket_size;  
  9.     ngx_hash_elt_t  *elt, **buckets;  
  10.   
  11.     for (n = 0; n < nelts; n++) {  //检查names数组的每一个元素,判断桶的大小是否够分配   
  12.         if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))  
  13.         {   //有任何一个元素,桶的大小不够为该元素分配空间,则退出   
  14.             ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,  
  15.                           "could not build the %s, you should "  
  16.                           "increase %s_bucket_size: %i",  
  17.                           hinit->name, hinit->name, hinit->bucket_size);  
  18.             return NGX_ERROR;  
  19.         }  
  20.     }  
  21.   
  22.     //分配2*max_size个字节的空间保存hash数据(该内存分配操作不在nginx的内存池中进行,因为test只是临时的)   
  23.     test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);  
  24.     if (test == NULL) {  
  25.         return NGX_ERROR;  
  26.     }  
  27.   
  28.     bucket_size = hinit->bucket_size - sizeof(void *); //一般sizeof(void*)=4   
  29.   
  30.     start = nelts / (bucket_size / (2 * sizeof(void *))); //   
  31.     start = start ? start : 1;  
  32.   
  33.     if (hinit->max_size > 10000 && hinit->max_size / nelts < 100) {  
  34.         start = hinit->max_size - 1000;  
  35.     }  
  36.   
  37.     for (size = start; size < hinit->max_size; size++) {  
  38.   
  39.         ngx_memzero(test, size * sizeof(u_short));  
  40.   
  41.         //标记1:此块代码是检查bucket大小是否够分配hash数据   
  42.         for (n = 0; n < nelts; n++) {  
  43.             if (names[n].key.data == NULL) {  
  44.                 continue;  
  45.             }  
  46.   
  47.             //计算key和names中所有name长度,并保存在test[key]中   
  48.             key = names[n].key_hash % size; //若size=1,则key一直为0   
  49.             test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));  
  50.   
  51.             if (test[key] > (u_short) bucket_size) {//若超过了桶的大小,则到下一个桶重新计算   
  52.                 goto next;  
  53.             }  
  54.         }  
  55.   
  56.         goto found;  
  57.   
  58.     next:  
  59.   
  60.         continue;  
  61.     }  
  62.   
  63.     //若没有找到合适的bucket,退出   
  64.     ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,  
  65.                   "could not build the %s, you should increase "  
  66.                   "either %s_max_size: %i or %s_bucket_size: %i",  
  67.                   hinit->name, hinit->name, hinit->max_size,  
  68.                   hinit->name, hinit->bucket_size);  
  69.   
  70.     ngx_free(test);  
  71.   
  72.     return NGX_ERROR;  
  73.   
  74. found:  //找到合适的bucket   
  75.   
  76.     for (i = 0; i < size; i++) {  //将test数组前size个元素初始化为4   
  77.         test[i] = sizeof(void *);  
  78.     }  
  79.   
  80.     /** 标记2:与标记1代码基本相同,但此块代码是再次计算所有hash数据的总长度(标记1的检查已通过) 
  81.         但此处的test[i]已被初始化为4,即相当于后续的计算再加上一个void指针的大小。 
  82.      */  
  83.     for (n = 0; n < nelts; n++) {  
  84.         if (names[n].key.data == NULL) {  
  85.             continue;  
  86.         }  
  87.   
  88.         //计算key和names中所有name长度,并保存在test[key]中   
  89.         key = names[n].key_hash % size; //若size=1,则key一直为0   
  90.         test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));  
  91.     }  
  92.   
  93.      //计算hash数据的总长度   
  94.     len = 0;  
  95.   
  96.     for (i = 0; i < size; i++) {  
  97.         if (test[i] == sizeof(void *)) {//若test[i]仍为初始化的值4,即没有变化,则继续   
  98.             continue;  
  99.         }  
  100.   
  101.         //对test[i]按ngx_cacheline_size对齐(32位平台,ngx_cacheline_size=32)   
  102.         test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));  
  103.   
  104.         len += test[i];  
  105.     }  
  106.   
  107.     if (hinit->hash == NULL) {//在内存池中分配hash头及buckets数组(size个ngx_hash_elt_t*结构)   
  108.         hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)  
  109.             + size * sizeof(ngx_hash_elt_t *));  
  110.         if (hinit->hash == NULL) {  
  111.             ngx_free(test);  
  112.             return NGX_ERROR;  
  113.         }  
  114.   
  115.         //计算buckets的启示位置(在ngx_hash_wildcard_t结构之后)   
  116.         buckets = (ngx_hash_elt_t **)  
  117.             ((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));  
  118.   
  119.     } else {  //在内存池中分配buckets数组(size个ngx_hash_elt_t*结构)   
  120.         buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));  
  121.         if (buckets == NULL) {  
  122.             ngx_free(test);  
  123.             return NGX_ERROR;  
  124.         }  
  125.     }  
  126.   
  127.     //接着分配elts,大小为len+ngx_cacheline_size,此处为什么+32?——下面要按32字节对齐   
  128.     elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);  
  129.     if (elts == NULL) {  
  130.         ngx_free(test);  
  131.         return NGX_ERROR;  
  132.     }  
  133.   
  134.      //将elts地址按ngx_cacheline_size=32对齐   
  135.     elts = ngx_align_ptr(elts, ngx_cacheline_size);  
  136.   
  137.     for (i = 0; i < size; i++) {  //将buckets数组与相应elts对应起来   
  138.         if (test[i] == sizeof(void *)) {  
  139.             continue;  
  140.         }  
  141.   
  142.         buckets[i] = (ngx_hash_elt_t *) elts;  
  143.         elts += test[i];  
  144.   
  145.     }  
  146.   
  147.     for (i = 0; i < size; i++) {  //test数组置0   
  148.         test[i] = 0;  
  149.     }  
  150.   
  151.     for (n = 0; n < nelts; n++) { //将传进来的每一个hash数据存入hash表   
  152.         if (names[n].key.data == NULL) {  
  153.             continue;  
  154.         }  
  155.   
  156.         //计算key,即将被hash的数据在第几个bucket,并计算其对应的elts位置   
  157.         key = names[n].key_hash % size;  
  158.         elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);  
  159.   
  160.         //对ngx_hash_elt_t结构赋值   
  161.         elt->value = names[n].value;  
  162.         elt->len = (u_short) names[n].key.len;  
  163.   
  164.         ngx_strlow(elt->name, names[n].key.data, names[n].key.len);  
  165.   
  166.         //计算下一个要被hash的数据的长度偏移   
  167.         test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));  
  168.     }  
  169.   
  170.     for (i = 0; i < size; i++) {  
  171.         if (buckets[i] == NULL) {  
  172.             continue;  
  173.         }  
  174.   
  175.         //test[i]相当于所有被hash的数据总长度   
  176.         elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);  
  177.   
  178.         elt->value = NULL;  
  179.     }  
  180.   
  181.     ngx_free(test);  //释放该临时空间   
  182.   
  183.     hinit->hash->buckets = buckets;  
  184.     hinit->hash->size = size;  
  185.   
  186.     return NGX_OK;  
  187. }  

所谓的hash数据长度即指ngx_hash_elt_t结构被赋值后的长度。nelts个元素存放在names数组中,调用该函数对hash进行初始化之后,这nelts个元素被保存在sizehash桶指向的ngx_hash_elts_t数据区,这些数据区中共保存了neltshash元素。即hash(buckets)存放的是ngx_hash_elt_t数据区的起始地址,以该起始地址开始的数据区存放的是经hash之后的hash元素,每个hash元素的最后是以name[0]为开始的字符串,该字符串就是names数组中某个元素的key,即键值对<key,value>中的key,然后该字符串之后会有几个字节的因对齐产生的padding

 

一个典型的经初始化后的hash物理结构如下。具体的可参考后文的例子。


2.4 hash查找

 

hash查找操作由ngx_hash_find()函数完成,代码如下。//后的注释为笔者所加。

  1. //由key,name,len信息在hash指向的hash table中查找该key对应的value   
  2. void *  
  3. ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)  
  4. {  
  5.     ngx_uint_t       i;  
  6.     ngx_hash_elt_t  *elt;  
  7.   
  8.     elt = hash->buckets[key % hash->size];//由key找到所在的bucket(该bucket中保存其elts地址)   
  9.   
  10.     if (elt == NULL) {  
  11.         return NULL;  
  12.     }  
  13.   
  14.     while (elt->value) {  
  15.         if (len != (size_t) elt->len) {  //先判断长度   
  16.             goto next;  
  17.         }  
  18.   
  19.         for (i = 0; i < len; i++) {  
  20.             if (name[i] != elt->name[i]) {  //接着比较name的内容(此处按字符匹配)   
  21.                 goto next;  
  22.             }  
  23.         }  
  24.   
  25.         return elt->value;  //匹配成功,直接返回该ngx_hash_elt_t结构的value字段   
  26.   
  27.     next:  
  28.         //注意此处从elt->name[0]地址处向后偏移,故偏移只需加该elt的len即可,然后在以4字节对齐   
  29.         elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,  
  30.                                                sizeof(void *));  
  31.         continue;  
  32.     }  
  33.   
  34.     return NULL;  
  35. }  

查找操作相当简单,由key直接计算所在的bucket,该bucket中保存其所在ngx_hash_elt_t数据区的起始地址;然后根据长度判断并用name内容匹配,匹配成功,其ngx_hash_elt_t结构的value字段即是所求。

 

3. 一个例子

 

本节给出一个创建内存池并从中分配hash结构、hash桶、hash元素并将键-值对<key,value>加入该hash结构的简单例子。

 

在该例中,将完成这样一个应用,将给定的多个url及其ip组成的二元组<url,ip>作为<key,value>,初始化时对这些<url,ip>进行hash,然后根据给定的url查找其对应的ip地址,若没有找到,则给出相关提示信息。以此向读者展示nginxhash使用方法。

3.1代码

  1. /** 
  2.  * ngx_hash_t test 
  3.  * in this example, it will first save URLs into the memory pool, and IPs saved in static memory. 
  4.  * then, give some examples to find IP according to a URL. 
  5.  */  
  6.   
  7. #include <stdio.h>   
  8. #include "ngx_config.h"   
  9. #include "ngx_conf_file.h"   
  10. #include "nginx.h"   
  11. #include "ngx_core.h"   
  12. #include "ngx_string.h"   
  13. #include "ngx_palloc.h"   
  14. #include "ngx_array.h"   
  15. #include "ngx_hash.h"   
  16.   
  17. #define Max_Num 7   
  18. #define Max_Size 1024   
  19. #define Bucket_Size 64  //256, 64   
  20.   
  21. #define NGX_HASH_ELT_SIZE(name)               \   
  22.     (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))  
  23.   
  24. /* for hash test */  
  25. static ngx_str_t urls[Max_Num] = {  
  26.     ngx_string("www.baidu.com"),  //220.181.111.147   
  27.     ngx_string("www.sina.com.cn"),  //58.63.236.35   
  28.     ngx_string("www.google.com"),  //74.125.71.105   
  29.     ngx_string("www.qq.com"),  //60.28.14.190   
  30.     ngx_string("www.linuxidc.com"),  //123.103.14.237   
  31.     ngx_string("www.sohu.com"),  //219.234.82.50   
  32.     ngx_string("www.baidu.org.tw")  //117.40.196.26   
  33. };  
  34.   
  35. static char* values[Max_Num] = {  
  36.     "220.181.111.147",  
  37.     "58.63.236.35",  
  38.     "74.125.71.105",  
  39.     "60.28.14.190",  
  40.     "123.103.14.237",  
  41.     "219.234.82.50",  
  42.     "117.40.196.26"  
  43. };  
  44.   
  45. #define Max_Url_Len 15   
  46. #define Max_Ip_Len 15   
  47.   
  48. #define Max_Num2 2   
  49.   
  50. /* for finding test */  
  51. static ngx_str_t urls2[Max_Num2] = {  
  52.     ngx_string("www.china.com"),  //60.217.58.79   
  53.     ngx_string("www.linuxidc.net")  //117.79.157.242   
  54. };  
  55.   
  56. ngx_hash_t* init_hash(ngx_pool_t *pool, ngx_array_t *array);  
  57. void dump_pool(ngx_pool_t* pool);  
  58. void dump_hash_array(ngx_array_t* a);  
  59. void dump_hash(ngx_hash_t *hash, ngx_array_t *array);  
  60. ngx_array_t* add_urls_to_array(ngx_pool_t *pool);  
  61. void find_test(ngx_hash_t *hash, ngx_str_t addr[], int num);  
  62.   
  63. /* for passing compiling */  
  64. volatile ngx_cycle_t  *ngx_cycle;  
  65. void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...)  
  66. {  
  67. }  
  68.   
  69. int main(/* int argc, char **argv */)  
  70. {  
  71.     ngx_pool_t *pool = NULL;  
  72.     ngx_array_t *array = NULL;  
  73.     ngx_hash_t *hash;  
  74.   
  75.     printf("--------------------------------\n");  
  76.     printf("create a new pool:\n");  
  77.     printf("--------------------------------\n");  
  78.     pool = ngx_create_pool(1024, NULL);  
  79.   
  80.     dump_pool(pool);  
  81.   
  82.     printf("--------------------------------\n");  
  83.     printf("create and add urls to it:\n");  
  84.     printf("--------------------------------\n");  
  85.     array = add_urls_to_array(pool);  //in fact, here should validate array   
  86.     dump_hash_array(array);  
  87.   
  88.     printf("--------------------------------\n");  
  89.     printf("the pool:\n");  
  90.     printf("--------------------------------\n");  
  91.     dump_pool(pool);  
  92.   
  93.     hash = init_hash(pool, array);  
  94.     if (hash == NULL)  
  95.     {  
  96.         printf("Failed to initialize hash!\n");  
  97.         return -1;  
  98.     }  
  99.   
  100.     printf("--------------------------------\n");  
  101.     printf("the hash:\n");  
  102.     printf("--------------------------------\n");  
  103.     dump_hash(hash, array);  
  104.     printf("\n");  
  105.   
  106.     printf("--------------------------------\n");  
  107.     printf("the pool:\n");  
  108.     printf("--------------------------------\n");  
  109.     dump_pool(pool);  
  110.   
  111.     //find test   
  112.     printf("--------------------------------\n");  
  113.     printf("find test:\n");  
  114.     printf("--------------------------------\n");  
  115.     find_test(hash, urls, Max_Num);  
  116.     printf("\n");  
  117.   
  118.     find_test(hash, urls2, Max_Num2);  
  119.   
  120.     //release   
  121.     ngx_array_destroy(array);  
  122.     ngx_destroy_pool(pool);  
  123.   
  124.     return 0;  
  125. }  
  126.   
  127. ngx_hash_t* init_hash(ngx_pool_t *pool, ngx_array_t *array)  
  128. {  
  129.     ngx_int_t result;  
  130.     ngx_hash_init_t hinit;  
  131.   
  132.     ngx_cacheline_size = 32;  //here this variable for nginx must be defined   
  133.     hinit.hash = NULL;  //if hinit.hash is NULL, it will alloc memory for it in ngx_hash_init   
  134.     hinit.key = &ngx_hash_key_lc;  //hash function   
  135.     hinit.max_size = Max_Size;  
  136.     hinit.bucket_size = Bucket_Size;  
  137.     hinit.name = "my_hash_sample";  
  138.     hinit.pool = pool;  //the hash table exists in the memory pool   
  139.     hinit.temp_pool = NULL;  
  140.   
  141.     result = ngx_hash_init(&hinit, (ngx_hash_key_t*)array->elts, array->nelts);  
  142.     if (result != NGX_OK)  
  143.         return NULL;  
  144.   
  145.     return hinit.hash;  
  146. }  
  147.   
  148. void dump_pool(ngx_pool_t* pool)  
  149. {  
  150.     while (pool)  
  151.     {  
  152.         printf("pool = 0x%x\n", pool);  
  153.         printf("  .d\n");  
  154.         printf("    .last = 0x%x\n", pool->d.last);  
  155.         printf("    .end = 0x%x\n", pool->d.end);  
  156.         printf("    .next = 0x%x\n", pool->d.next);  
  157.         printf("    .failed = %d\n", pool->d.failed);  
  158.         printf("  .max = %d\n", pool->max);  
  159.         printf("  .current = 0x%x\n", pool->current);  
  160.         printf("  .chain = 0x%x\n", pool->chain);  
  161.         printf("  .large = 0x%x\n", pool->large);  
  162.         printf("  .cleanup = 0x%x\n", pool->cleanup);  
  163.         printf("  .log = 0x%x\n", pool->log);  
  164.         printf("available pool memory = %d\n\n", pool->d.end - pool->d.last);  
  165.         pool = pool->d.next;  
  166.     }  
  167. }  
  168.   
  169. void dump_hash_array(ngx_array_t* a)  
  170. {  
  171.     char prefix[] = "          ";  
  172.   
  173.     if (a == NULL)  
  174.         return;  
  175.   
  176.     printf("array = 0x%x\n", a);  
  177.     printf("  .elts = 0x%x\n", a->elts);  
  178.     printf("  .nelts = %d\n", a->nelts);  
  179.     printf("  .size = %d\n", a->size);  
  180.     printf("  .nalloc = %d\n", a->nalloc);  
  181.     printf("  .pool = 0x%x\n", a->pool);  
  182.   
  183.     printf("  elements:\n");  
  184.     ngx_hash_key_t *ptr = (ngx_hash_key_t*)(a->elts);  
  185.     for (; ptr < (ngx_hash_key_t*)(a->elts + a->nalloc * a->size); ptr++)  
  186.     {  
  187.         printf("    0x%x: {key = (\"%s\"%.*s, %d), key_hash = %-10ld, value = \"%s\"%.*s}\n",   
  188.             ptr, ptr->key.data, Max_Url_Len - ptr->key.len, prefix, ptr->key.len,   
  189.             ptr->key_hash, ptr->value, Max_Ip_Len - strlen(ptr->value), prefix);  
  190.     }  
  191.     printf("\n");  
  192. }  
  193.   
  194. /** 
  195.  * pass array pointer to read elts[i].key_hash, then for getting the position - key 
  196.  */  
  197. void dump_hash(ngx_hash_t *hash, ngx_array_t *array)  
  198. {  
  199.     int loop;  
  200.     char prefix[] = "          ";  
  201.     u_short test[Max_Num] = {0};  
  202.     ngx_uint_t key;  
  203.     ngx_hash_key_t* elts;  
  204.     int nelts;  
  205.   
  206.     if (hash == NULL)  
  207.         return;  
  208.   
  209.     printf("hash = 0x%x: **buckets = 0x%x, size = %d\n", hash, hash->buckets, hash->size);  
  210.   
  211.     for (loop = 0; loop < hash->size; loop++)  
  212.     {  
  213.         ngx_hash_elt_t *elt = hash->buckets[loop];  
  214.         printf("  0x%x: buckets[%d] = 0x%x\n", &(hash->buckets[loop]), loop, elt);  
  215.     }  
  216.     printf("\n");  
  217.   
  218.     elts = (ngx_hash_key_t*)array->elts;  
  219.     nelts = array->nelts;  
  220.     for (loop = 0; loop < nelts; loop++)  
  221.     {  
  222.         char url[Max_Url_Len + 1] = {0};  
  223.   
  224.         key = elts[loop].key_hash % hash->size;  
  225.         ngx_hash_elt_t *elt = (ngx_hash_elt_t *) ((u_char *) hash->buckets[key] + test[key]);  
  226.   
  227.         ngx_strlow(url, elt->name, elt->len);  
  228.         printf("  buckets %d: 0x%x: {value = \"%s\"%.*s, len = %d, name = \"%s\"%.*s}\n",   
  229.             key, elt, (char*)elt->value, Max_Ip_Len - strlen((char*)elt->value), prefix,   
  230.             elt->len, url, Max_Url_Len - elt->len, prefix); //replace elt->name with url   
  231.   
  232.         test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&elts[loop]));  
  233.     }  
  234. }  
  235.   
  236. ngx_array_t* add_urls_to_array(ngx_pool_t *pool)  
  237. {  
  238.     int loop;  
  239.     char prefix[] = "          ";  
  240.     ngx_array_t *a = ngx_array_create(pool, Max_Num, sizeof(ngx_hash_key_t));  
  241.   
  242.     for (loop = 0; loop < Max_Num; loop++)  
  243.     {  
  244.         ngx_hash_key_t *hashkey = (ngx_hash_key_t*)ngx_array_push(a);  
  245.         hashkey->key = urls[loop];  
  246.         hashkey->key_hash = ngx_hash_key_lc(urls[loop].data, urls[loop].len);  
  247.         hashkey->value = (void*)values[loop];  
  248.         /** for debug 
  249.         printf("{key = (\"%s\"%.*s, %d), key_hash = %-10ld, value = \"%s\"%.*s}, added to array\n", 
  250.             hashkey->key.data, Max_Url_Len - hashkey->key.len, prefix, hashkey->key.len, 
  251.             hashkey->key_hash, hashkey->value, Max_Ip_Len - strlen(hashkey->value), prefix); 
  252.         */  
  253.     }  
  254.   
  255.     return a;      
  256. }  
  257.   
  258. void find_test(ngx_hash_t *hash, ngx_str_t addr[], int num)  
  259. {  
  260.     ngx_uint_t key;  
  261.     int loop;  
  262.     char prefix[] = "          ";  
  263.   
  264.     for (loop = 0; loop < num; loop++)  
  265.     {  
  266.         key = ngx_hash_key_lc(addr[loop].data, addr[loop].len);  
  267.         void *value = ngx_hash_find(hash, key, addr[loop].data, addr[loop].len);  
  268.         if (value)  
  269.         {  
  270.             printf("(url = \"%s\"%.*s, key = %-10ld) found, (ip = \"%s\")\n",   
  271.                 addr[loop].data, Max_Url_Len - addr[loop].len, prefix, key, (char*)value);  
  272.         }  
  273.         else  
  274.         {  
  275.             printf("(url = \"%s\"%.*s, key = %-10d) not found!\n",   
  276.                 addr[loop].data, Max_Url_Len - addr[loop].len, prefix, key);  
  277.         }  
  278.     }  
  279. }  

3.2如何编译

 

请参考nginx-1.0.4源码分析—内存池结构ngx_pool_t及内存管理一文。本文编写的makefile文件如下。

view plaincopy to clipboardprint?
  1. CXX = gcc  
  2. CXXFLAGS += -g -Wall -Wextra  
  3.   
  4. NGX_ROOT = /usr/src/nginx-1.0.4  
  5.   
  6. TARGETS = ngx_hash_t_test  
  7. TARGETS_C_FILE = $(TARGETS).c  
  8.   
  9. CLEANUP = rm -f $(TARGETS) *.o  
  10.   
  11. all: $(TARGETS)  
  12.   
  13. clean:  
  14.     $(CLEANUP)  
  15.   
  16. CORE_INCS = -I. \  
  17.     -I$(NGX_ROOT)/src/core \  
  18.     -I$(NGX_ROOT)/src/event \  
  19.     -I$(NGX_ROOT)/src/event/modules \  
  20.     -I$(NGX_ROOT)/src/os/unix \  
  21.     -I$(NGX_ROOT)/objs \  
  22.   
  23. NGX_PALLOC = $(NGX_ROOT)/objs/src/core/ngx_palloc.o  
  24. NGX_STRING = $(NGX_ROOT)/objs/src/core/ngx_string.o  
  25. NGX_ALLOC = $(NGX_ROOT)/objs/src/os/unix/ngx_alloc.o  
  26. NGX_ARRAY = $(NGX_ROOT)/objs/src/core/ngx_array.o  
  27. NGX_HASH = $(NGX_ROOT)/objs/src/core/ngx_hash.o  
  28.   
  29. $(TARGETS): $(TARGETS_C_FILE)  
  30.     $(CXX) $(CXXFLAGS) $(CORE_INCS) $(NGX_PALLOC) $(NGX_STRING) $(NGX_ALLOC) $(NGX_ARRAY) $(NGX_HASH) $^ -o $@  

3.3 运行结果

3.3.1 bucket_size=64字节

bucket_size=64字节时,运行结果如下。

view plaincopy to clipboardprint?
  1. # ./ngx_hash_t_test  
  2. --------------------------------  
  3. create a new pool:  
  4. --------------------------------  
  5. pool = 0x8870020  
  6.   .d  
  7.     .last = 0x8870048  
  8.     .end = 0x8870420  
  9.     .next = 0x0  
  10.     .failed = 0  
  11.   .max = 984  
  12.   .current = 0x8870020  
  13.   .chain = 0x0  
  14.   .large = 0x0  
  15.   .cleanup = 0x0  
  16.   .log = 0x0  
  17. available pool memory = 984  
  18.   
  19. --------------------------------  
  20. create and add urls to it:  
  21. --------------------------------  
  22. array = 0x8870048  
  23.   .elts = 0x887005c  
  24.   .nelts = 7  
  25.   .size = 16  
  26.   .nalloc = 7  
  27.   .pool = 0x8870020  
  28.   elements:  
  29.     0x887005c: {key = ("www.baidu.com"  , 13), key_hash = 270263191 , value = "220.181.111.147"}  
  30.     0x887006c: {key = ("www.sina.com.cn", 15), key_hash = 1528635686, value = "58.63.236.35"   }  
  31.     0x887007c: {key = ("www.google.com" , 14), key_hash = -702889725, value = "74.125.71.105"  }  
  32.     0x887008c: {key = ("www.qq.com"     , 10), key_hash = 203430122 , value = "60.28.14.190"   }  
  33.     0x887009c: {key = ("www.linuxidc.com"    , 11), key_hash = -640386838, value = "123.103.14.237" }  
  34.     0x88700ac: {key = ("www.sohu.com"   , 12), key_hash = 1313636595, value = "219.234.82.50"  }  
  35.     0x88700bc: {key = ("www.baidu.org.tw" , 14), key_hash = 1884209457, value = "117.40.196.26"  }  
  36.   
  37. --------------------------------  
  38. the pool:  
  39. --------------------------------  
  40. pool = 0x8870020  
  41.   .d  
  42.     .last = 0x88700cc  
  43.     .end = 0x8870420  
  44.     .next = 0x0  
  45.     .failed = 0  
  46.   .max = 984  
  47.   .current = 0x8870020  
  48.   .chain = 0x0  
  49.   .large = 0x0  
  50.   .cleanup = 0x0  
  51.   .log = 0x0  
  52. available pool memory = 852  
  53.   
  54. --------------------------------  
  55. the hash:  
  56. --------------------------------  
  57. hash = 0x88700cc: **buckets = 0x88700d8, size = 3  
  58.   0x88700d8: buckets[0] = 0x8870100  
  59.   0x88700dc: buckets[1] = 0x8870140  
  60.   0x88700e0: buckets[2] = 0x8870180  
  61.   
  62.   buckets 1: 0x8870140: {value = "220.181.111.147", len = 13, name = "www.baidu.com"  }  
  63.   buckets 2: 0x8870180: {value = "58.63.236.35"   , len = 15, name = "www.sina.com.cn"}  
  64.   buckets 1: 0x8870154: {value = "74.125.71.105"  , len = 14, name = "www.google.com" }  
  65.   buckets 2: 0x8870198: {value = "60.28.14.190"   , len = 10, name = "www.qq.com"     }  
  66.   buckets 0: 0x8870100: {value = "123.103.14.237" , len = 11, name = "www.linuxidc.com"    }  
  67.   buckets 0: 0x8870114: {value = "219.234.82.50"  , len = 12, name = "www.sohu.com"   }  
  68.   buckets 0: 0x8870128: {value = "117.40.196.26"  , len = 14, name = "www.baidu.org.tw" }  
  69.   
  70. --------------------------------  
  71. the pool:  
  72. --------------------------------  
  73. pool = 0x8870020  
  74.   .d  
  75.     .last = 0x88701c4  
  76.     .end = 0x8870420  
  77.     .next = 0x0  
  78.     .failed = 0  
  79.   .max = 984  
  80.   .current = 0x8870020  
  81.   .chain = 0x0  
  82.   .large = 0x0  
  83.   .cleanup = 0x0  
  84.   .log = 0x0  
  85. available pool memory = 604  
  86.   
  87. --------------------------------  
  88. find test:  
  89. --------------------------------  
  90. (url = "www.baidu.com"  , key = 270263191 ) found, (ip = "220.181.111.147")  
  91. (url = "www.sina.com.cn", key = 1528635686) found, (ip = "58.63.236.35")  
  92. (url = "www.google.com" , key = -702889725) found, (ip = "74.125.71.105")  
  93. (url = "www.qq.com"     , key = 203430122 ) found, (ip = "60.28.14.190")  
  94. (url = "www.linuxidc.com"    , key = -640386838) found, (ip = "123.103.14.237")  
  95. (url = "www.sohu.com"   , key = 1313636595) found, (ip = "219.234.82.50")  
  96. (url = "www.baidu.org.tw" , key = 1884209457) found, (ip = "117.40.196.26")  
  97.   
  98. (url = "www.china.com"  , key = -1954599725) not found!  
  99. (url = "www.linuxidc.net"   , key = -1667448544) not found!  

以上结果是bucket_size=64字节的输出。由该结果可以看出,对于给定的7url,程序将其分到了3bucket中,详见该结果。该例子的hash物理结构图如下。


3.3.2 bucket_size=256字节

bucket_size=256字节时,运行结果如下。
view plaincopy to clipboardprint?
  1. # ./ngx_hash_t_test  
  2. --------------------------------  
  3. create a new pool:  
  4. --------------------------------  
  5. pool = 0x8b74020  
  6.   .d  
  7.     .last = 0x8b74048  
  8.     .end = 0x8b74420  
  9.     .next = 0x0  
  10.     .failed = 0  
  11.   .max = 984  
  12.   .current = 0x8b74020  
  13.   .chain = 0x0  
  14.   .large = 0x0  
  15.   .cleanup = 0x0  
  16.   .log = 0x0  
  17. available pool memory = 984  
  18.   
  19. --------------------------------  
  20. create and add urls to it:  
  21. --------------------------------  
  22. array = 0x8b74048  
  23.   .elts = 0x8b7405c  
  24.   .nelts = 7  
  25.   .size = 16  
  26.   .nalloc = 7  
  27.   .pool = 0x8b74020  
  28.   elements:  
  29.     0x8b7405c: {key = ("www.baidu.com"  , 13), key_hash = 270263191 , value = "220.181.111.147"}  
  30.     0x8b7406c: {key = ("www.sina.com.cn", 15), key_hash = 1528635686, value = "58.63.236.35"   }  
  31.     0x8b7407c: {key = ("www.google.com" , 14), key_hash = -702889725, value = "74.125.71.105"  }  
  32.     0x8b7408c: {key = ("www.qq.com"     , 10), key_hash = 203430122 , value = "60.28.14.190"   }  
  33.     0x8b7409c: {key = ("www.linuxidc.com"    , 11), key_hash = -640386838, value = "123.103.14.237" }  
  34.     0x8b740ac: {key = ("www.sohu.com"   , 12), key_hash = 1313636595, value = "219.234.82.50"  }  
  35.     0x8b740bc: {key = ("www.baidu.org.tw" , 14), key_hash = 1884209457, value = "117.40.196.26"  }  
  36.   
  37. --------------------------------  
  38. the pool:  
  39. --------------------------------  
  40. pool = 0x8b74020  
  41.   .d  
  42.     .last = 0x8b740cc  
  43.     .end = 0x8b74420  
  44.     .next = 0x0  
  45.     .failed = 0  
  46.   .max = 984  
  47.   .current = 0x8b74020  
  48.   .chain = 0x0  
  49.   .large = 0x0  
  50.   .cleanup = 0x0  
  51.   .log = 0x0  
  52. available pool memory = 852  
  53.   
  54. --------------------------------  
  55. the hash:  
  56. --------------------------------  
  57. hash = 0x8b740cc: **buckets = 0x8b740d8, size = 1  
  58.   0x8b740d8: buckets[0] = 0x8b740e0  
  59.   
  60.   buckets 0: {value = "220.181.111.147", len = 13, name = "www.baidu.com"  }  
  61.   buckets 0: {value = "58.63.236.35"   , len = 15, name = "www.sina.com.cn"}  
  62.   buckets 0: {value = "74.125.71.105"  , len = 14, name = "www.google.com" }  
  63.   buckets 0: {value = "60.28.14.190"   , len = 10, name = "www.qq.com"     }  
  64.   buckets 0: {value = "123.103.14.237" , len = 11, name = "www.linuxidc.com"    }  
  65.   buckets 0: {value = "219.234.82.50"  , len = 12, name = "www.sohu.com"   }  
  66.   buckets 0: {value = "117.40.196.26"  , len = 14, name = "www.baidu.org.tw" }  
  67.   
  68. --------------------------------  
  69. the pool:  
  70. --------------------------------  
  71. pool = 0x8b74020  
  72.   .d  
  73.     .last = 0x8b7419c  
  74.     .end = 0x8b74420  
  75.     .next = 0x0  
  76.     .failed = 0  
  77.   .max = 984  
  78.   .current = 0x8b74020  
  79.   .chain = 0x0  
  80.   .large = 0x0  
  81.   .cleanup = 0x0  
  82.   .log = 0x0  
  83. available pool memory = 644  
  84.   
  85. --------------------------------  
  86. find test:  
  87. --------------------------------  
  88. (url = "www.baidu.com"  , key = 270263191 ) found, (ip = "220.181.111.147")  
  89. (url = "www.sina.com.cn", key = 1528635686) found, (ip = "58.63.236.35")  
  90. (url = "www.google.com" , key = -702889725) found, (ip = "74.125.71.105")  
  91. (url = "www.qq.com"     , key = 203430122 ) found, (ip = "60.28.14.190")  
  92. (url = "www.linuxidc.com"    , key = -640386838) found, (ip = "123.103.14.237")  
  93. (url = "www.sohu.com"   , key = 1313636595) found, (ip = "219.234.82.50")  
  94. (url = "www.baidu.org.tw" , key = 1884209457) found, (ip = "117.40.196.26")  
  95.   
  96. (url = "www.linuxidc.com"  , key = -1954599725) not found!  
  97. (url = "www.linuxidc.net"   , key = -1667448544) not found!  

以上结果是bucket_size=256字节的输出。由给结果可以看出,对于给定的7url,程序将其放到了1bucket中,即ngx_hash_init()函数中的size=1,因这7url的总长度只有140,因此,只需size=1bucket,即buckets[0]

 

下表是ngx_hash_init()函数在计算过程中的一些数据。物理结构图省略,可参考上图。

 

url

计算长度

test[0]的值

www.baidu.com

4+ngx_align(13+2,4)=20

20

www.sina.com.cn

4+ngx_align(15+2,4)=24

44

www.google.com

4+ngx_align(14+2,4)=20

64

www.qq.com

4+ngx_align(10+2,4)=16

80

www.linuxidc.com

4+ngx_align(11+2,4)=20

100

www.sohu.com

4+ngx_align(12+2,4)=20

120

www.baidu.org.tw

4+ngx_align(14+2,4)=20

140

 

4. 小结

本文针对nginx-1.0.4hash结构进行了较为全面的分析,包括hash结构、hash元素结构、hash初始化结构等,hash操作主要包括hash初始化、hash查找等。最后通过一个简单例子向读者展示nginxhash使用方法,并给出详细的运行结果,且画出hash的物理结构图,以此向图这展示hash的设计、原理;同时借此向读者展示编译测试nginx代码的方法。