Content

0. 序

1. 数组结构

1.1 ngx_array_t结构

1.2 ngx_array_t的逻辑结构

2. 数组操作

2.1 创建数组

2.2 销毁数组

2.3 添加1个元素

3. 一个例子

3.1 代码

3.2 如何编译

3.3 运行结果

4. 小结


0. 序

本文开始介绍nginx的容器,先从最简单的数组开始。

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

1. 数组结构

1.1 ngx_array_t结构

nginx的数组结构为ngx_array_t,定义如下。

1. struct ngx_array_s {
2. void *elts; //数组数据区起始位置
3. ngx_uint_t nelts; //实际存放的元素个数
4. size_t size; //每个元素大小
5. ngx_uint_t nalloc; //数组所含空间个数,即实际分配的小空间的个数
6. ngx_pool_t *pool; //该数组在此内存池中分配
7. };
8. typedef struct ngx_array_s ngx_array_t;


sizeof(ngx_array_t)=20。由其定义可见,nginx的数组也要从内存池中分配。将分配nalloc个大小为size的小空间,实际分配的大小为(nalloc * size)。详见下文的分析。

1.2 ngx_array_t的逻辑结构

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


 2. 数组操作

数组操作共有5个,如下。

1. //创建数组
2. ngx_array_t*ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
3. //销毁数组
4. voidngx_array_destroy(ngx_array_t *a);
5. //向数组中添加元素
6. void*ngx_array_push(ngx_array_t *a);
7. void*ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);
8. //初始化数组
9. staticngx_inline ngx_int_t
10. ngx_array_init(ngx_array_t*array, ngx_pool_t *pool, ngx_uint_t n, size_t size)


因实现都很简单,本文简单分析前3个函数。

2.1 创建数组

创建数组的操作实现如下,首先分配数组头(20B),然后分配数组数据区,两次分配均在传入的内存池(pool指向的内存池)中进行。然后简单初始化数组头并返回数组头的起始位置。

1. ngx_array_t*
2. ngx_array_create(ngx_pool_t*p, ngx_uint_t n, size_t size)
3. {
4. ngx_array_t *a;
5. a = ngx_palloc(p,sizeof(ngx_array_t)); //从内存池中分配数组头
6. if (a == NULL) {
7. return NULL;
8. }
9. a->elts = ngx_palloc(p,n * size); //接着分配n*size大小的区域作为数组数据区
10. if (a->elts == NULL) {
11. return NULL;
12. }
13. a->nelts = 0; //初始化
14. a->size = size;
15. a->nalloc = n;
16. a->pool = p;
17. return a; //返回数组头的起始位置
18. }


创建数组后内存池的物理结构图如下。


 2.2 销毁数组

销毁数组的操作实现如下,包括销毁数组数据区和数组头。这里的销毁动作实际上就是修改内存池的last指针,并没有调用free等释放内存的操作,显然,这种维护效率是很高的。

1. void
2. ngx_array_destroy(ngx_array_t*a)
3. {
4. ngx_pool_t *p;
5. p = a->pool;
6. if ((u_char *) a->elts+ a->size * a->nalloc == p->d.last) { //先销毁数组数据区
7. p->d.last -=a->size * a->nalloc; //设置内存池的last指针
8. }
9. if ((u_char *) a +sizeof(ngx_array_t) == p->d.last) { //接着销毁数组头
10. p->d.last = (u_char*) a; //设置内存池的last指针
11. }
12. }


2.3 添加1个元素

向数组添加元素的操作有两个,ngx_array_push和ngx_array_push_n,分别添加一个和多个元素。

但实际的添加操作并不在这两个函数中完成,例如ngx_array_push返回可以在该数组数据区中添加这个元素的位置,ngx_array_push_n则返回可以在该数组数据区中添加n个元素的起始位置,而添加操作即在获得添加位置之后进行,如后文的例子。

1. void *
2. ngx_array_push(ngx_array_t*a)
3. {
4. void *elt, *new;
5. size_t size;
6. ngx_pool_t *p;
7. if (a->nelts ==a->nalloc) { //数组数据区满
8. /* the arrayis full */
9. size = a->size *a->nalloc; //计算数组数据区的大小
10. p = a->pool;
11. if ((u_char *)a->elts + size == p->d.last //若内存池的last指针指向数组数据区的末尾
12. &&p->d.last + a->size <= p->d.end) //且内存池未使用的区域可以再分配一个size大小的小空间
13. {
14. /*
15. * the array allocation is the lastin the pool
16. * and there is space for newallocation
17. */
18. p->d.last +=a->size; //分配一个size大小的小空间(a->size为数组一个元素的大小)
19. a->nalloc++; //实际分配小空间的个数加1
20. } else {
21. /* allocate a new array */
22. new =ngx_palloc(p, 2 * size); //否则,扩展数组数据区为原来的2倍
23. if (new == NULL) {
24. return NULL;
25. }
26. ngx_memcpy(new,a->elts, size);//将原来数据区的内容拷贝到新的数据区
27. a->elts = new;
28. a->nalloc *= 2; //注意:此处转移数据后,并未释放原来的数据区,内存池将统一释放
29. }
30. }
31. elt = (u_char *)a->elts + a->size * a->nelts; //数据区中实际已经存放数据的子区的末尾
32. a->nelts++; //即最后一个数据末尾,该指针就是下一个元素开始的位置
33. return elt; //返回该末尾指针,即下一个元素应该存放的位置
34. }


由此可见,向数组中添加元素实际上也是在修该内存池的last指针(若数组数据区满)及数组头信息,即使数组满了,需要扩展数据区内容,也只需要内存拷贝完成,并不需要数据的移动操作,这个效率也是相当高的。

下图是向数组中添加10个整型元素后的一个例子。代码可参考下文的例子。当然,数组元素也不仅限于例子的整型数据,也可以是其他类型的数据,如结构体等。


3. 一个例子

理解并掌握开源软件的最好方式莫过于自己写一些​​测试​​代码,或者改写软件本身,并进行调试来进一步理解开源软件的原理和设计方法。本节给出一个创建内存池并从中分配一个数组的简单例子。

3.1代码

1. /**
2. * ngx_array_t test, to test ngx_array_create, ngx_array_push
3. */
4. #include <stdio.h>
5. #include "ngx_config.h"
6. #include "ngx_conf_file.h"
7. #include "nginx.h"
8. #include "ngx_core.h"
9. #include "ngx_string.h"
10. #include "ngx_palloc.h"
11. #include "ngx_array.h"
12. volatile ngx_cycle_t *ngx_cycle;
13. void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
14. const char *fmt, ...)
15. {
16. }
17. void dump_pool(ngx_pool_t* pool)
18. {
19. while (pool)
20. {
21. printf("pool = 0x%x\n", pool);
22. printf(" .d\n");
23. printf(" .last = 0x%x\n", pool->d.last);
24. printf(" .end = 0x%x\n", pool->d.end);
25. printf(" .next = 0x%x\n", pool->d.next);
26. printf(" .failed = %d\n", pool->d.failed);
27. printf(" .max = %d\n", pool->max);
28. printf(" .current = 0x%x\n", pool->current);
29. printf(" .chain = 0x%x\n", pool->chain);
30. printf(" .large = 0x%x\n", pool->large);
31. printf(" .cleanup = 0x%x\n", pool->cleanup);
32. printf(" .log = 0x%x\n", pool->log);
33. printf("available pool memory = %d\n\n", pool->d.end - pool->d.last);
34. pool = pool->d.next;
35. }
36. }
37. void dump_array(ngx_array_t* a)
38. {
39. if (a)
40. {
41. printf("array = 0x%x\n", a);
42. printf(" .elts = 0x%x\n", a->elts);
43. printf(" .nelts = %d\n", a->nelts);
44. printf(" .size = %d\n", a->size);
45. printf(" .nalloc = %d\n", a->nalloc);
46. printf(" .pool = 0x%x\n", a->pool);
47. printf("elements: ");
48. int *ptr = (int*)(a->elts);
49. for (; ptr < (int*)(a->elts + a->nalloc * a->size); )
50. {
51. printf("0x%x ", *ptr++);
52. }
53. printf("\n");
54. }
55. }
56. int main()
57. {
58. ngx_pool_t *pool;
59. int i;
60. printf("--------------------------------\n");
61. printf("create a new pool:\n");
62. printf("--------------------------------\n");
63. pool = ngx_create_pool(1024, NULL);
64. dump_pool(pool);
65. printf("--------------------------------\n");
66. printf("alloc an array from the pool:\n");
67. printf("--------------------------------\n");
68. ngx_array_t *a = ngx_array_create(pool, 10, sizeof(int));
69. dump_pool(pool);
70. for (i = 0; i < 10; i++)
71. {
72. int *ptr = ngx_array_push(a);
73. *ptr = i + 1;
74. }
75. dump_array(a);
76. ngx_array_destroy(a);
77. ngx_destroy_pool(pool);
78. return 0;
79. }


3.2如何编译

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

1. CXX = gcc
2. CXXFLAGS +=-g -Wall -Wextra
3. NGX_ROOT =/usr/src/nginx-1.0.4
4. TARGETS =ngx_array_t_test
5. TARGETS_C_FILE= $(TARGETS).c
6. CLEANUP = rm-f $(TARGETS) *.o
7. all:$(TARGETS)
8. clean:
9. $(CLEANUP)
10. CORE_INCS =-I. \
11. -I$(NGX_ROOT)/src/core \
12. -I$(NGX_ROOT)/src/event \
13. -I$(NGX_ROOT)/src/event/modules \
14. -I$(NGX_ROOT)/src/os/unix \
15. -I$(NGX_ROOT)/objs \
16. NGX_PALLOC =$(NGX_ROOT)/objs/src/core/ngx_palloc.o
17. NGX_STRING =$(NGX_ROOT)/objs/src/core/ngx_string.o
18. NGX_ALLOC =$(NGX_ROOT)/objs/src/os/unix/ngx_alloc.o
19. NGX_ARRAY =$(NGX_ROOT)/objs/src/core/ngx_array.o
20. $(TARGETS):$(TARGETS_C_FILE)
21. $(CXX) $(CXXFLAGS) $(CORE_INCS) $(NGX_PALLOC) $(NGX_STRING)$(NGX_ALLOC) $(NGX_ARRAY) $^ -o $@

3.3运行结果

1. # ./ngx_array_t_test
2. -------------------------------- create a new pool:
3. -------------------------------- pool = 0x860b020 .d .last = 0x860b048
4. .end = 0x860b420
5. .next = 0x0
6. .failed = 0 .max = 984
7. .current = 0x860b020
8. .chain = 0x0
9. .large = 0x0
10. .cleanup = 0x0
11. .log = 0x0 available pool memory = 984
12. -------------------------------- alloc an array from the pool:
13. -------------------------------- pool = 0x860b020 .d .last = 0x860b084
14. .end = 0x860b420
15. .next = 0x0
16. .failed = 0 .max = 984
17. .current = 0x860b020
18. .chain = 0x0
19. .large = 0x0
20. .cleanup = 0x0
21. .log = 0x0 available pool memory = 924
22. array = 0x860b048 .elts = 0x860b05c
23. .nelts = 10
24. .size = 4
25. .nalloc = 10
26. .pool = 0x860b020 elements: 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa

该例子中内存池和数组的(内存)物理结构可参考2.3节的图。

4. 小结

本文针对nginx-1.0.4的容器——数组结构进行了较为全面的分析,包括数组相关​​数据结构​​,数组的创建、销毁,以及向数组中添加元素等。最后通过一个简单例子向读者展示nginx数组的创建、添加元素和销毁操作,同时借此向读者展示编译测试代码的方法。

敬请关注后续的分析。谢谢!